Grapette.L 479d2f10c8
fix(memory): report malformed backend_config values by key name (#5555)
* fix(memory): report non-mapping Honcho backend_config values as ValueError

failure_policy, workspace_overrides and user_peer_overrides were read with a
falsy-only `or {}` fallback, so a truthy non-mapping (a bare string, a YAML
list) reached .get/.items and escaped as a bare AttributeError from inside
backend construction. Route all three through one _mapping helper that keeps
falsy values meaning "unset" and names the offending key as a ValueError, the
posture the mem0 and OpenViking backends already take.

* fix(memory): name the Honcho numeric knobs that cannot be cast

timeout_seconds / connect_timeout_seconds / message_char_limit /
max_injection_chars still reached float() / int() with a YAML null or a
mapping, so the operator got a TypeError naming neither the key nor the
config file. Narrow all four through one _number helper that keeps falsy
values meaning "unset" the way the sibling api_key / storage_path scalars
already do, and reports a value that cannot be cast as a ValueError on the
key. Numeric strings keep parsing, since that is what float/int accept.

* fix(memory): report non-numeric backend_config knobs by name in mem0 and OpenViking

mem0 casts top_k, score_threshold, max_injection_chars and timeout_seconds, and
OpenViking casts timeout_seconds, max_seen_message_ids, retrieval.top_k and
retrieval.max_injection_chars, straight through int()/float(). A knob written
without a value in YAML therefore escapes as "TypeError: int() argument must be
a string..." from inside backend construction, naming neither which knob nor
which file is wrong, and a non-numeric value escapes as the equally anonymous
"could not convert string to float".

Both now resolve numeric knobs through a helper that keeps a value-less key at
its default, the way the same dicts already treat failure_policy and
allow_insecure_http, and turns an uncastable value into a ValueError that names
the knob. OpenViking's score_threshold keeps None as a meaningful value rather
than a default to fall back to.
2026-09-20 16:43:54 +08:00
..

Honcho memory backend

Uses Honcho (self-hosted or hosted, v3 API) as DeerFlow's user-model memory store. Honcho covers the user dimension of memory — long-term user modeling, preferences, and a cross-session working representation — built by Honcho's own server-side deriver. Ingestion is cheap plain message writes; this backend makes no LLM calls locally.

Configuration

memory:
  enabled: true
  injection_enabled: true
  manager_class: honcho
  mode: middleware            # or "tool"
  backend_config:
    base_url: http://localhost:8000
    # api_key: $HONCHO_API_KEY   # hosted Honcho; plain-http + api_key needs allow_insecure_http: true
    workspace_prefix: deerflow-u-   # one isolated workspace per user id
    # workspace_overrides: {}    # map specific user ids to custom workspaces
    # user_peer_overrides: {}    # map specific user ids to custom peer names
    assistant_peer: deerflow
    message_char_limit: 8000
    max_injection_chars: 6000
    timeout_seconds: 10
    connect_timeout_seconds: 3
    failure_policy:
      read: fail_open                  # fail_open | fail_closed

A configured api_key over a plain-http:// base URL is rejected at startup unless you explicitly set allow_insecure_http: true (local-development opt-in, same posture as the mem0 backend). Use HTTPS for any non-local deployment.

Config errors (bad URL, insecure key combination) fail fast at Gateway startup; connectivity is deliberately not probed, so a temporarily unreachable Honcho does not block startup — reads then degrade per failure_policy.read.

Multi-user isolation

Every operation resolves a workspace from user_id: workspace_overrides[user_id] on exact match, else workspace_prefix + <stable id>. The stable id is the sanitized user id plus an 8-hex-char SHA-256 suffix of the original id, so distinct raw ids that sanitize identically cannot silently merge into one workspace. Honcho scopes all queries to a single workspace, so under the default derivation users cannot see each other's memory by construction.

  • A missing or empty user_id fails closed: writes become no-ops and reads return empty — there is never a shared fallback workspace.
  • A workspace_overrides entry deliberately shared across users shares that workspace's search index (search is workspace-scoped; get_context / get_memory stay peer-scoped).
  • Session ids reuse the same collision-resistant derivation (df- + stable id of thread_id).
  • mode: middleware recall is query-less: get_context fetches the user's working representation (up to 25 conclusions) and injects it truncated to max_injection_chars.
  • mode: tool adds query-aware memory_search backed by Honcho's workspace-scoped search, while the passive per-turn write middleware is retained because Honcho's deriver learns from add() writes.

Limitations

  • No DeerMem-style fact CRUD: create_fact/delete_fact/update_fact, import_memory, and Settings-page fact editing are not implemented (get_memory returns a minimal DeerMem-shaped document with the working representation as the workContext summary and an empty fact list). memory_add/memory_update/memory_delete return a clear unsupported-operation error; conversation writes still happen through the retained middleware. DeerMem remains the default backend.
  • No migration of existing DeerMem data.
  • Writes are fire-and-forget per call: a failed write is logged and dropped (at-most-once); nothing is buffered locally, so shutdown_flush is a no-op.
  • agent_name is not mapped — Honcho models the user, not per-agent facts.

Async execution and failure behavior

The Honcho HTTP client is synchronous for compatibility with the MemoryManager contract. DeerFlow offloads it at every async boundary via asyncio.to_thread (the manager's a* methods), so a slow Honcho request never blocks ASGI handlers or SSE heartbeats.

failure_policy.read: fail_open (default) logs a recall failure and continues without new memory context. fail_closed propagates the backend error through prompt construction and aborts the run instead of silently degrading.