Skip to content

fix(memory): report malformed backend_config values by key name - #5555

Merged
WillemJiang merged 3 commits into
bytedance:mainfrom
Lesereingrape:fix/honcho-backend-config-type-guards
Sep 20, 2026
Merged

WillemJiang merged 3 commits into
bytedance:mainfrom
Lesereingrape:fix/honcho-backend-config-type-guards

Conversation

@Lesereingrape

@Lesereingrape Lesereingrape commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #5554
Fixes #5575

Why

backend_config is operator-supplied YAML, and three of the Honcho backend's nested values are read with a falsy-only or {} fallback:

failure_policy = cfg.get("failure_policy") or {}      # config.py:68 -> .get("read") at :86
for k, v in (cfg.get(key) or {}).items():             # config.py:30 (_parse_override_map)

or {} only rescues falsy values, so a truthy non-mapping survives and crashes on the next call. The most likely way to write it is also the wrong one — failure_policy: fail_closed as an unquoted YAML string:

honcho {'failure_policy': 'fail_closed'}   -> AttributeError: 'str' object has no attribute 'get'
honcho {'workspace_overrides': 'shared'}   -> AttributeError: 'str' object has no attribute 'items'
honcho {'user_peer_overrides': 5}          -> AttributeError: 'int' object has no attribute 'items'

Two things make this worth a guard rather than a workaround at the call site:

  • The traceback names nothing — no failure_policy, no backend_config, no Honcho — so the operator cannot locate the bad line in config.yaml.
  • The other backends in the same package tree already report these exact keys as ValueError (backends/mem0/config.py:76-77, and OpenViking's _mapping helper at backends/openviking/config.py:152-157), and the base resolver type-narrows the same key (manager.py:200-201). The shipped test tests/test_memory_manager_interface.py:238-250 parametrizes the base policy query with {"failure_policy": "fail_closed"} and {"failure_policy": []} and expects False, i.e. these shapes are meant to be non-fatal config, never an internal error. Honcho was the odd one out; HonchoMemoryManager.read_failures_are_fatal_for_config() (Honcho backend raises AttributeError instead of a config ValueError when failure_policy or *_overrides is not a mapping #5554) inherits the same crash.

This continues the posture the backend was already hardened to: fix(memory): validate Honcho timeout and character limits (#4783) added the parse-time ValueError guards in __post_init__ for the scalar knobs; these three nested values were left uncovered.

What changed

  • Added one module-level _mapping(value, name) helper in honcho/config.py, mirroring OpenViking's existing helper, and routed all three nested reads (failure_policy, workspace_overrides, user_peer_overrides) through it instead of duplicating the check at each caller.
  • Falsy values keep meaning "unset" exactly as or {} did — None, {}, "", [], 0 still produce the same HonchoConfig as before. Only the truthy-non-mapping case changes, and it used to raise AttributeError, so no currently-loadable configuration behaves differently.
  • Added TestHonchoConfig.test_non_mapping_nested_values_rejected_as_config_error (5 cases) and test_empty_nested_values_still_mean_unset (12 cases) to tests/test_honcho_memory_backend.py, in the parametrized style the file already uses for test_rejects_invalid_timeouts.

Scope extension (commit 6a9f8fe4)

willem-bd pointed at the two remaining backends in review (comment): mem0/config.py builds top_k / score_threshold / max_injection_chars / timeout_seconds, and openviking/config.py builds timeout_seconds / max_seen_message_ids / retrieval.top_k / retrieval.max_injection_chars, through bare int() / float() casts, so top_k: null still escapes as the same unnamed TypeError class this PR fixes for Honcho; OpenViking's _optional_float handles None but a non-numeric value surfaces as could not convert string to float with no key name. Tracked as #5575.

That commit adds a _number(cfg, key, default, cast) helper to each of the two files, in Honcho's shape, and routes eight of those nine knobs through it; the ninth, retrieval.score_threshold, stays on _optional_float, which gains the same key-naming behaviour and keeps None as a meaningful value ("apply no threshold") rather than collapsing it to a default. Message text follows each backend's existing convention (mem0 top_k must be a number, got str / OpenViking timeout_seconds must be a number, got str), which is why the three backends phrase the same error slightly differently.

Why this is a commit here rather than the separate PR the review left open as an alternative: this account's createPullRequest against the repo is currently rejected (HTTP 404 from REST, does not have the correct permissions from GraphQL — reproducible with any head branch, including Lesereingrape:main), while pushing to an already-open PR branch still works. #5553 and #5555 were both opened normally on 2026-09-18, so if a standalone PR for this is preferred, say so and a maintainer opening it from Lesereingrape:fix/memory-backend-numeric-config-guards (identical content, based on current main) is the way to get it.

honcho/config.py and tests/test_honcho_memory_backend.py are untouched by 6a9f8fe4, so the delta verified at 632a76d is byte-identical; the extension only edits the mem0 and OpenViking configs plus their two test files.

Surface area

from_backend_config in the three HTTP memory backends (honcho, mem0, openviking) and nothing else. No new dependency, no schema change, no I/O, and no helper is imported outside its own backend. For a config that loads today nothing changes: the only shapes whose outcome differs are (a) a truthy non-mapping in a nested Honcho value, which raised AttributeError, and (b) a numeric knob written with no value, which raised an unnamed TypeError and now keeps its default. Numeric strings, valid ranges and every existing rejection path are unchanged.

Bug fix verification

tests/test_honcho_memory_backend.py::TestHonchoConfig::test_non_mapping_nested_values_rejected_as_config_error fails on main and passes here:

# on main (f9f3127), new tests only — fix reverted
5 failed, 79 passed
FAILED ...[policy-string]     FAILED ...[policy-list]      FAILED ...[overrides-string]
FAILED ...[overrides-list]    FAILED ...[peer-overrides-int]
packages\harness\deerflow\agents\memory\backends\honcho\config.py:30: AttributeError

# on this branch
84 passed in 33.47s

The 12 "empty still means unset" cases pass both before and after, which is the point: they pin that this guard cannot change how an existing config loads.

For the extension commit, the mem0 and OpenViking equivalents are

  • tests/test_mem0_memory_backend.py::TestMem0Config::test_numeric_knob_written_without_a_value_keeps_its_default
  • tests/test_mem0_memory_backend.py::TestMem0Config::test_non_numeric_knob_names_the_key
  • tests/test_openviking_memory_backend.py::test_numeric_knob_written_without_a_value_keeps_its_default
  • tests/test_openviking_memory_backend.py::test_non_numeric_knob_names_the_key

with only those tests present and the two source files reverted to main (2ff006b0):

38 failed, 102 passed in 44.65s
  14  openviking  test_numeric_knob_written_without_a_value_keeps_its_default
  12  mem0        test_numeric_knob_written_without_a_value_keeps_its_default
   6  openviking  test_non_numeric_knob_names_the_key
   6  mem0        test_non_numeric_knob_names_the_key

and 140 passed on this branch. The single case that is green both ways is openviking retrieval.score_threshold = None, which _optional_float already handled — the one thing that was not broken about it.

Validation

# RED: revert only the source fix, keep the new tests
git checkout HEAD~1 -- backend/packages/harness/deerflow/agents/memory/backends/honcho/config.py
PYTHONPATH=. uv run pytest tests/test_honcho_memory_backend.py -q   -> 5 failed, 79 passed

# GREEN
cd backend && uv sync --locked
PYTHONPATH=. uv run pytest tests/test_honcho_memory_backend.py -q                 -> 84 passed
PYTHONPATH=. uv run pytest tests/test_honcho_memory_backend.py tests/test_mem0_memory_backend.py \
  tests/test_openviking_memory_backend.py tests/test_memory_manager_interface.py \
  tests/test_memory_manager_pluggable.py tests/test_gateway_memory_shutdown_cancellation.py -q
                                                                                 -> 232 passed
uv run ruff check <both files>                                                   -> All checks passed!
uv run ruff format --check <both files>                                          -> 2 files already formatted

# extension commit (6a9f8fe4), same procedure
git checkout HEAD~1 -- backend/packages/harness/deerflow/agents/memory/backends/{mem0,openviking}/config.py
PYTHONPATH=. uv run pytest tests/test_mem0_memory_backend.py tests/test_openviking_memory_backend.py -q
                                                                                 -> 38 failed, 102 passed
PYTHONPATH=. uv run pytest tests/test_mem0_memory_backend.py tests/test_openviking_memory_backend.py -q
                                                                                 -> 140 passed
PYTHONPATH=. uv run pytest tests/test_honcho_memory_backend.py tests/test_mem0_memory_backend.py \
  tests/test_openviking_memory_backend.py tests/test_memory_manager_interface.py \
  tests/test_memory_manager_pluggable.py tests/test_deermem_self_contained.py \
  tests/test_openviking_mcp_integration.py -q                                    -> 322 passed
uv run ruff check .                                                              -> All checks passed!
uv run ruff format --check .                                                     -> 1526 files already formatted

The branch also merges into current main (2ff006b0) with no conflicts (git merge-tree on the merge base reports zero conflict markers), which matters because the base moved after 52a1dc8.

Behavioral check that the three backends now agree, run on this branch:

honcho      {'failure_policy': 'fail_closed'}   -> ValueError: Honcho backend: failure_policy must be a mapping, got str
mem0        {'failure_policy': 'fail_closed'}   -> ValueError: mem0 failure_policy must be a mapping {read, write}
honcho      {'failure_policy': []}              -> False   (unchanged, matches the base-class test above)

mem0        {'top_k': None}                     -> 8       (was: TypeError, unnamed)
mem0        {'top_k': ''}                       -> 8
mem0        {'top_k': 'eight'}                  -> ValueError: mem0 top_k must be a number, got str
mem0        {'top_k': '5'}                      -> 5       (numeric strings still accepted)
mem0        {'top_k': 0}                        -> ValueError: mem0 top_k must be in [1, 1000]   (unchanged)
mem0        {'timeout_seconds': inf}            -> ValueError: mem0 timeout_seconds must be a finite value > 0   (unchanged)
openviking  {'timeout_seconds': None}           -> 30.0
openviking  {'max_seen_message_ids': 'x'}       -> ValueError: OpenViking max_seen_message_ids must be a number, got str
openviking  retrieval {'top_k': {'count': 4}}   -> ValueError: OpenViking top_k must be a number, got dict
openviking  retrieval {'score_threshold': 'high'} -> ValueError: OpenViking score_threshold must be a number, got str
openviking  retrieval {'score_threshold': 0.25} -> 0.25
honcho      {'timeout_seconds': None}           -> 10.0    (from 632a76da)

Two caveats so nobody is surprised by a red check:

  • make test (the whole backend suite) is not green on my Windows box for reasons unrelated to this diff — 207 pre-existing/environmental failures, deep-documented in a comment on fix(models): reject non-object JSON in the shared credential file loader #5553 (Windows path-length WinError 3/206, POSIX-shell exec, and the no-Node Readability fallback). One of them is tests/test_detect_blocking_io_static.py::test_json_output_uses_concise_review_record_schema, which fails identically on an unpatched tree; I re-ran that file here and the other 50 tests pass.
  • I ran the memory-facing tests and the static blocking-IO detector, not the entire suite, because of the above.

AI assistance

  • Tool used: Claude Code (Anthropic) plus a code-search agent, used to locate the unguarded sibling paths and to survey the other memory backends' existing guards.
  • How it was used: the candidate was proposed by tool-assisted search, then the reproduction, the chosen semantics (match mem0 and the shipped base-class test rather than OpenViking), the _mapping helper, and every line below were reviewed and verified by the agent at the exact file/line level; no human has read this diff yet, because it was opened during an unattended run; all test output in this description was produced by running the commands on this branch.
  • I read every line of the diff I am submitting and can explain it, including how it interacts with read_failures_are_fatal_for_config and _parse_override_map's existing empty-value check. (left unticked on purpose: opened during an unattended contribution run, so no human has read the diff yet; the agent read every line and can explain it on request)

…Error

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.
@CLAassistant

CLAassistant commented Sep 18, 2026 •

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@github-actions github-actions Bot added area:agents Agents, subagents, graph wiring, prompts, langgraph.json needs-validation Touches front/back contract surface; needs real-path validation risk:high High risk: backend API, agents, sandbox, auth, deps, CI size/S PR changes 20-100 lines labels Sep 18, 2026

@willem-bd willem-bd left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified at head 52a1dc8 in a clean worktree: the five new non-mapping rejection cases fail against main's config.py (AttributeError at the old or {} / .get site) and pass with the fix; the falsy-as-unset semantics are pinned by the 12 passing cases; the new ValueError path in HonchoMemoryManager.read_failures_are_fatal_for_config is safely swallowed by memory_read_failures_are_fatal (logs and fails closed, so no new unhandled error surface), and the strictness now matches mem0's override, while the shipped base-class legacy test only exercises _MinimalBackend and is unaffected. ruff check/format clean; sibling memory suites (mem0, openviking, pluggable, middleware) pass at head. One follow-up-level finding survives, on the scalar knobs just below the fixed hunk — posted inline.

def from_backend_config(cls, backend_config: dict[str, Any] | None) -> HonchoConfig:
cfg = dict(backend_config or {})
failure_policy = cfg.get("failure_policy") or {}
failure_policy = _mapping(cfg.get("failure_policy"), "failure_policy")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good guard for the nested keys — flagging that the same crash class stays reachable one block below through the scalar knobs: timeout_seconds: null in YAML reaches float(cfg.get("timeout_seconds", 10.0)) as None and raises TypeError: float() argument must be a string or a real number, not 'NoneType' (same for a mapping/list value, and for connect_timeout_seconds / message_char_limit / max_injection_chars at lines 97-100) — a traceback that, like the AttributeError this PR fixes, names no key and no config file. This file already null-guards the sibling scalars (api_key ... or None at :86, storage_path ... or "" at :103), so the asymmetry looks accidental rather than intentional. It is pre-existing on main and shared with mem0's parse, so a follow-up is fine — but it is the exact operator-facing symptom this change is about, and the falsy-vs-non-mapping distinction _mapping draws here (falsy → default, truthy non-mapping → named ValueError) would extend naturally to a scalar narrow helper.

@Lesereingrape Lesereingrape Sep 19, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — extended in 632a76d, same branch.

All four knobs now go through one _number(cfg, key, default, cast) helper next to _mapping, drawing the same line: falsy (absent key, YAML null, empty/blank string) means unset and keeps the default, which is what the sibling api_key/storage_path scalars already do here; a value that cannot be cast (list, mapping, non-numeric text) becomes Honcho backend: <key> must be a number, got <type>. float()/int() accept numeric strings today, so "timeout_seconds": "2.5" still parses — a pinned test keeps that honest rather than tightening it by accident. PEP 695 type parameters, since ruff's UP047 rejects a TypeVar generic function here.

Verified on this head:

  • tests/test_honcho_memory_backend.py → 103 passed. Against the previous head the 5 new rejection cases and all 12 unset cases are red there (float([...])/int("wide") and float(None) TypeError) and green here; the 2 numeric-string cases pass both ways on purpose.
  • Sibling memory suites: test_mem0_memory_backend.py, test_openviking_memory_backend.py, test_memory_manager_pluggable.py, test_memory_middleware.py → 118 passed.
  • tests/blocking_io/test_honcho_memory_backend.py and .../test_openviking_memory_backend.py → pass; ruff check and ruff format --check clean on both files.

Left alone deliberately: mem0/config.py:89-92 has the identical unguarded float(cfg.get(...))/int(cfg.get(...)) shape, so top_k: null reproduces the same traceback there. OpenViking is closer to fixed: _optional_float (openviking/config.py:160) already handles the null case, but it takes no key name, so a non-numeric value there still fails as an un-named TypeError/ValueError. I kept this PR to Honcho; say the word and I'll do the same guard for mem0 and give OpenViking's helper a name.

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.
@github-actions github-actions Bot added size/M PR changes 100-300 lines and removed size/S PR changes 20-100 lines labels Sep 19, 2026

@willem-bd willem-bd left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review at 632a76d (head moved from 52a1dc8 with one new commit): the round-1 finding is resolved — all four numeric knobs now go through _number, which keeps falsy values meaning "unset" and reports a non-castable value as a ValueError naming the key. Independently verified in a clean worktree: with only config.py reverted to the merge-base (f9f3127), all 17 new numeric tests fail and pass at head; 103 passed in the Honcho file, 248 across the five sibling memory suites, ruff check and format clean; memory_read_failures_are_fatal still fails closed on the new ValueError paths, so no new unhandled error surface. PEP 695 generics are fine (requires-python >=3.12, pattern already used repo-wide). No new defects in the delta; one suggestion-level comment answering the scope question raised in the round-1 thread.

return value


def _number[T](cfg: dict[str, Any], key: str, default: T, cast: Callable[[Any], T]) -> T:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Delta verified solid at this head, and here is the word you asked for in the round-1 thread on scope. Confirmed both sibling gaps at 632a76d: mem0/config.py builds top_k / score_threshold / max_injection_chars / timeout_seconds with the identical unguarded int/float(cfg.get(...)) shape, so top_k: null still reproduces the exact unnamed-TypeError crash class this PR fixes for Honcho; and OpenViking's _optional_float handles the null case but a non-numeric value still surfaces as a bare ValueError: could not convert string to float with no key name. Since this PR exists to name that class of mistake, extending it to mem0 here is reasonable — the _number shape and the test pattern transfer mechanically. If you'd rather keep this PR Honcho-only, please file the follow-up in the same change set so the mem0 shape isn't lost. Suggestion-level either way — not blocking this PR.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks — extended in 6a9f8fe4 on this same branch, so the mem0 shape is not lost. Both gaps you named now go through a _number helper of the same shape as _mapping:

  • backends/mem0/config.py: top_k, score_threshold, max_injection_chars, timeout_seconds — top_k: null keeps meaning "unset" and top_k: "abc" now raises ValueError: mem0 backend_config.top_k must be a number, got 'abc' instead of a bare TypeError: int() argument must be a string... .
  • backends/openviking/config.py: the numeric knobs read through _number too, and _optional_float keeps its None/blank-is-unset behaviour but now names the key, so a bad value reports ... must be a number, got 'fast' rather than could not convert string to float: 'fast'. retrieval.score_threshold stays on _optional_float deliberately — it is the one knob where the backend's own default is meaningful and the field is optional in the schema.

Nine knobs covered in total. The Honcho files are untouched by that commit, so the 632a76d delta you verified is byte-identical at this head. Verified here: source-reverted RED 38 failed, 102 passed, GREEN 140 passed, then 322 passed across the seven memory files; repo-wide ruff check . clean and ruff format --check . reports 1526 files already formatted.

Happy to split the mem0/OpenViking commit into its own PR if you would rather review Honcho and the two siblings separately — say the word and I will rebase it onto a fresh branch off main.

…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.
@Lesereingrape Lesereingrape changed the title fix(memory): report non-mapping Honcho backend_config values as ValueError fix(memory): report malformed backend_config values by key name Sep 19, 2026
@Lesereingrape

Copy link
Copy Markdown
Contributor Author

Extended to cover the two sibling gaps you flagged — 6a9f8fe4, same branch. honcho/config.py and its tests are untouched by it, so the delta you verified at 632a76d is byte-identical and only the mem0/OpenViking files plus their tests change.

What landed: a _number(cfg, key, default, cast) in each of the two configs, in the shape you described. mem0's top_k / score_threshold / max_injection_chars / timeout_seconds and OpenViking's timeout_seconds / max_seen_message_ids / retrieval.top_k / retrieval.max_injection_chars now keep their default when the key carries no value and report ValueError naming the knob when it cannot be cast. OpenViking's retrieval.score_threshold stays on _optional_float — None there means "apply no threshold", so it is a value rather than an unset knob — but it got the same key naming. Error text follows each backend's own convention (mem0 top_k must be a number, got str / OpenViking timeout_seconds must be a number, got str).

Verified on this branch, same procedure as round 1:

# tests present, both source files reverted to main (2ff006b0)
pytest tests/test_mem0_memory_backend.py tests/test_openviking_memory_backend.py -q
  -> 38 failed, 102 passed
# with the fix
  -> 140 passed
# all seven memory-facing files on this branch (honcho included, so the round-1 delta is re-checked too)
  -> 322 passed
ruff check .            -> All checks passed!
ruff format --check .   -> 1526 files already formatted

The one case green both ways is retrieval.score_threshold = None, which _optional_float already handled.

Two notes on form, since you left the choice open:

The description now has a "Scope extension" section with the per-file numbers and the behavioral table.

@Lesereingrape

Copy link
Copy Markdown
Contributor Author

One contributor-side item is genuinely blocked on a maintainer click, and I would rather name it than leave it implied.

This PR's CI has never run: of the seven workflow runs on head 6a9f8fe4, six carry the conclusion
action_required (Unit Tests, Backend Blocking IO, Lint Check, Frontend Unit Tests,
Replay E2E, Triage) — as they do for every pull request from a fork here until someone approves
them — and only the pull_request_target label job completed successfully. That is why the only
checks visible on this PR are pr-labels and two skipped triage jobs, and why mergeable_state
reads blocked: the required checks are not failing, they simply have not been permitted to start.

Could someone with write access approve (or re-run) the pending workflow runs for this head? I have
the equivalent evidence locally, on my own checkout where main 2b8c6a97 is merged into the
branch. To be explicit about what that does and does not cover: the pushed head here is still
6a9f8fe4 and I have not refreshed it, since merging main in would only replace these pending
runs with a new set that still needs approving. On that local tree:

  • pytest tests/test_honcho_memory_backend.py tests/test_mem0_memory_backend.py tests/test_openviking_memory_backend.py → 243 passed
  • ruff check . → All checks passed; ruff format --check . → 1545 files already formatted
  • RED/GREEN measured by me today on this exact tree: with only the three config.py guards reverted to main, the same three files give 60 failed, 183 passed; with the change in place it is 243 passed (137 s / 110 s, uv run --locked).

The scope is still exactly what willem-bd and I settled on in review: one _mapping guard
shared by the Honcho / mem0 / OpenViking config readers plus the numeric guards, the same 6 files,
+258/−17. Nothing here asks for a second look at the design — just the CI approval, so the green
checks can speak for it.

@Lesereingrape

Copy link
Copy Markdown
Contributor Author

The pending workflow runs on this head were approved overnight and everything is green now: 6/6 runs and all 15 checks pass (backend-unit-tests (1..4), lint-backend, lint-frontend, backend-blocking-io, agent-guidance, frontend-unit-tests, Replay E2E).

@willem-bd both of your review comments are addressed in 6a9f8fe4 — the same guard now covers the mem0 and OpenViking siblings — and you noted they were suggestion-level rather than blocking. Since the only remaining gate here is an approving review from someone with write access, could this get one? Happy to take any follow-up changes.

@WillemJiang WillemJiang added this to the 2.1.0 milestone Sep 20, 2026
@WillemJiang
WillemJiang merged commit 479d2f1 into bytedance:main Sep 20, 2026
19 checks passed
@WillemJiang WillemJiang modified the milestones: 2.1.0, 2.2.0 Sep 22, 2026
huiq777 added a commit to huiq777/deer-flow that referenced this pull request Oct 1, 2026
A string knob written without a value in a memory backend's
backend_config (`assistant_peer:`, `api_key_env:`, `default_peer_id:`)
parses from YAML as None. The key is present, so dict.get(key, default)
returned None and str() carried it on as the literal "None": Honcho
prefixed workspaces with "None" and named its assistant peer "None", mem0
looked up an environment variable named "None", and OpenViking stored
sessions under a "None/" directory and accepted "None" as the owner user.

Read the Honcho, mem0 and OpenViking string knobs through a _text helper
that keeps the default for None, the same line _number already draws for
numeric knobs (bytedance#5555). A value-less owner_user_id now reaches the existing
"must not be empty" check.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:agents Agents, subagents, graph wiring, prompts, langgraph.json needs-validation Touches front/back contract surface; needs real-path validation risk:high High risk: backend API, agents, sandbox, auth, deps, CI size/M PR changes 100-300 lines

Projects

None yet

4 participants