Skip to content

fix(*): refresh an acp agent's model menu from a dispatch reply - #1038

Merged
0xKT merged 7 commits into
mainfrom
fix/acp_model_menu_refresh
Oct 21, 2026
Merged

0xKT merged 7 commits into
mainfrom
fix/acp_model_menu_refresh

Conversation

@LivXue

@LivXue LivXue commented Oct 21, 2026 •

Copy link
Copy Markdown
Member

Summary

An acp agent's card offers a model picker built from the configOptions entry with category: "model" that the agent returned on session/new when it was last measured. That measurement is cached in the capability snapshot, which has no age-based expiry and is invalidated only when the launch fingerprint (command, cwd, env) changes. It was re-taken only by a Test, a Connect, or the boot backfill, and the backfill skips a row that is already ready and every row on a registered machine. Every dispatch already re-learned the mode menu from the session reply it holds, but not the model menu that arrives in the same reply. So a card could go on offering model ids the agent had stopped serving weeks earlier.

What changes:

  • The existing per-dispatch relearn (renamed relearn_session_modes -> relearn_session_menus, backend hook _relearn_modes -> _relearn_menus) now reads the model menu off the same reply, with the probe's own _read_session_model_choices, so grouped choices are parsed exactly as the handshake parses them. It runs at the same two points as before: after session/new and after a successful session/load on the dispatch path.
  • No extra handshake: nothing opens a session, starts a process, or calls verify, probe or ping. The menus are read from the reply the dispatch was already waiting for.
  • Each menu is judged on its own. A reply with no configOptions, no model entry, an empty or value-less model entry, or the adapter's catalog-unread flag keeps the recorded model menu instead of clearing it. Raven's own ACP server sends configOptions on session/new only, so a resumed session reports modes alone and must not erase the models.
  • A refreshed menu is written to the snapshot store, so subagents.list returns it on the next read, and the agent table is rebuilt through the same listener the mode relearn already used. A row recorded before the menu existed becomes measured once a reply carries one.
  • models.availableModels handling is unchanged, and so is _model_rule: third-party rows stay agent, Raven's own rows stay raven (host provider catalogue), fixed rows still have no menu.
  • Writes go through a new SnapshotStore.revise, which applies only the fields the reply reported onto the stored row read inside the file lock, and stands down when that row carries another measurement stamp. This closes two overwrite paths that also applied to the mode relearn before this change: a backend built before a Test or boot verify could write its older record over the newer one (status, detail and fingerprint included), and a second backend of the same agent whose reply carried only modes could restore the model menu the first had just refreshed. The stamp check is the same newest-measurement-wins rule the boot backfill already applies to its own late verdicts.
  • When it stands down, revise writes nothing and hands back the newer stored record, which the backend adopts through its existing hook (and rebuilds the agent table, as for any moved record). The reply that was refused may predate that measurement, so its menus are never written over it; the backend's next reply is judged against the stamp actually stored and refreshes normally. Without this, a backend no rebuild reached (a failed hot-apply, or another process sharing the snapshot file) refused every later reply until it was replaced. The handed-back record is flagged stale when it was measured under another launch config than the backend's.

A row whose stored model is no longer in the refreshed menu was checked and needs no change: the card shows the raw id with its clear control, the picker opens on the new menu, and subagents.update validates model only when one is sent, so a new pick or a clear both go through. Only re-sending the dropped id is refused, which is correct because the agent no longer takes it. A dispatch with that id still runs; the agent's refusal is logged once per value as before.

Limits that remain:

  • The snapshot still has no age-based expiry. A row that is never dispatched keeps its menu until a Test, a Connect or a launch-config change.
  • The menu is whatever the pinned ACP adapter version reports; refreshing it does not change which adapter version runs.
  • A route that sends no model menu (for Raven's own server, session/load) refreshes nothing for models.
  • The first reply that reaches a backend after a newer measurement is not applied; that backend adopts the measurement, and only its next reply refreshes the menus.

Deliberately not done: an empty model entry is read as "not reported" rather than "the agent now offers none". Clearing on an empty but present value would erase the picker on every resumed session whose reply carries the current value with no options.

Windows test portability: both the RPC dispatch fixture and the shared ACP stub helper now quote the interpreter and script tokens with command_quote. The existing RPC test copies its stub to a temporary path containing spaces, so this launch requirement is tested on every host. No production behavior changes in this correction.

Type

  • Fix
  • Feature
  • Docs
  • CI / tooling
  • Refactor
  • Other

Verification

The historical author runs below used Python 3.12.13 and --all-extras serially, with imports checked against that checkout. The native Windows follow-up below records what was rerun for the latest test correction and rebase:

  • uv run --frozen --python 3.12 --all-extras pytest -n 0 tests/test_acp_unprompted.py tests/test_rpc_instances.py tests/test_rpc_subagents.py tests/test_rpc_subagents_model.py tests/test_subagent_acp.py tests/test_subagent_acp_mcp_lifecycle.py tests/test_subagent_acp_remote.py tests/test_subagent_host_env.py tests/test_subagent_manager.py tests/test_subagent_mirasim.py tests/test_subagent_probe.py tests/test_subagent_third_party.py tests/test_acp_methods.py -> 1525 passed, 0 failed.
  • New tests were red before the fix: the menu rewrite, independent model/mode relearn, unmeasured-to-measured, the newer-measurement stand-down, the per-field write, the stale flag, the end-to-end dispatch against the stub agent, and subagents.list after a real dispatch all failed on their assertions; the keep-on-silence and catalog-unread tests passed before and after, as guards. After review, three more were red before the adoption change: the next reply after a stand-down, the stale flag on an adopted record, and the same sequence through the backend's own hook against the real store. The stand-down test was tightened from "returns None" to "returns exactly the newer record and leaves the snapshot file byte-identical".
  • Mutation, each applied alone and restored: dropping the new-menu write turns 11 tests red; letting an empty menu clear the stored one turns 4 red; ignoring the catalog-unread flag turns 1 red; removing the stamp check turns 4 red; writing the caller's whole record back turns 1 red; dropping the stale flag on a revised record turns 1 red; not handing back the newer record turns 4 red; handing it back without the cross-config stale flag turns 1 red.
  • The dispatch test asserts exactly one agent launch, one initialize and one session/new, with verify, record and ping patched to fail if called.
  • ruff check and ruff format --check over the lint targets: clean. ty check: all checks passed. lint-imports: 10 contracts kept, 0 broken.
  • scripts/check_commit_messages.py origin/main..HEAD, scripts/check_large_files.py origin/main..HEAD, scripts/check_source_language.py origin/main...HEAD, commitlint --from origin/main: all exit 0.
  • The branch has been rebased onto the latest integration base. Range-diff confirmed the five existing patches and the new test correction are unchanged by rebase; current-head native validation is listed below.

Those historical runs did not include the full Python suite, tests/integration/, or native Windows. No ui-web code or wire field changed, so no ui-web tests were added.

Native Windows follow-up (Windows 11 build 26300, Python 3.13.12, UTF-8 mode):

  • In a dedicated checkout whose path and virtualenv interpreter path both contain spaces, the RPC dispatch test and the real ACP model-menu dispatch test each failed before the correction: Windows launch received an executable path truncated at the first space. Both passed after quoting the tokens. The RPC test now always uses a spaced temporary script path.
  • After rebase and uv sync --frozen --python 3.13 --all-packages --extra dev, ran these exact commands ($evidenceDir is an external report directory):
uv run --frozen --no-sync pytest tests/test_subagent_acp.py tests/test_rpc_subagents.py tests/test_subagent_probe.py -k 'relearn or model_menu or has_model_menu or verify_missing_snapshots or model_choices or stood_down or newer_record' -n 0 -q --tb=short --idle-ceiling-strict --basetemp "$evidenceDir/rebased-focused" --junitxml "$evidenceDir/rebased-focused.xml"
uv run --frozen --no-sync pytest tests/test_rpc_subagents_model.py tests/test_utils_commands.py -n 0 -q --tb=short --idle-ceiling-strict --basetemp "$evidenceDir/rebased-model-commands" --junitxml "$evidenceDir/rebased-model-commands.xml"

Results: 23 passed / 561 deselected, then 111 passed; both exit 0, with no failures, errors or skips, and the strict idle gate enabled. Imports resolved to this worktree; profile and OAuth directories were isolated.

  • Full-PR pre-commit, production ty, import contracts (10 kept), source-language, large-file, commit-message, pinned commitlint and diff-whitespace checks passed. No repository check or threshold was disabled.
  • The previous remote head's diff-coverage gate failed at 87.50% (35/40 changed executable lines), below the 90% threshold. This test-launch correction adds no production line; the coverage follow-up below closes that gap.
  • No full-repository native suite, remote SSH integration, live model, or browser run was performed for this correction.

Diff coverage follow-up (Linux, Python 3.12.13, --all-extras, serial, imports checked against this checkout):

  • The five uncovered changed lines were two failure branches of the menu revision, both added by this PR: a newer stored row this build cannot read back (its stamp still forbids writing over it, and there is no record to hand back), and a snapshot file that cannot be written (logged once, nothing learned). Two tests now pin them by observable results: the snapshot file stays byte-identical, the relearn returns nothing, the backend keeps the record it holds and rebuilds no table, and the write failure appears in the log. No production line changed.

  • Mutation, each alone and restored: letting the unreadable newer row fall through, or handing back the caller's own record for it, turns its test red; letting the write failure raise, or reporting it as written, turns the other red; dropping the relearn's own check for "nothing revised" turns both red. Removing the stamp check now turns 5 tests red, not adopting the newer record 4, writing the whole record back 1.

  • Coverage measured with CI's settings (--cov=raven --cov-branch) over the relevant subset, then python scripts/coverage_gate.py --coverage-json <that run's json> diff --base-ref origin/main --threshold 90 against the same base CI used: 100.00% (40/40 executable changed lines). Control, the same run with only the two new tests deselected: 87.50% (35/40), the same five lines CI reported, so the local measurement reproduces CI's.

  • pytest -n 0 over tests/test_acp_unprompted.py tests/test_rpc_instances.py tests/test_rpc_subagents.py tests/test_rpc_subagents_model.py tests/test_subagent_acp.py tests/test_subagent_acp_mcp_lifecycle.py tests/test_subagent_acp_remote.py tests/test_subagent_host_env.py tests/test_subagent_manager.py tests/test_subagent_mirasim.py tests/test_subagent_probe.py tests/test_subagent_third_party.py tests/test_acp_methods.py: 1528 passed, 0 failed, on the rebased head (the earlier 1525 was counted before the rebase, which brought in tests of its own).

  • ruff check and format, ty, import contracts (10 kept), commit-message, commitlint, large-file and source-language checks passed. Only the relevant subset ran under coverage locally; CI's full sharded run decides the gate.

  • Relevant tests pass locally

  • Relevant lint / type checks pass locally

  • User-facing docs or screenshots are updated when needed

No user-facing docs describe when the model picker refreshes, so none were changed.

Risk

User-visible: an acp agent's model picker now follows the agent after a dispatch instead of only after a Test. The mode relearn also gains the stamp check and per-field write, so in the two overwrite cases above it now stands down or writes less than before. A backend that stands down adopts the newer stored record wholesale and triggers one table rebuild, which brings the table to the same record a rebuild would read anyway. relearn_session_menus replaces relearn_session_modes in the module's exports; nothing else in the tree imported the old name; the dated mode-tier design spec now names the function by its current name and keeps the line numbers of the revision it is pinned to. One file write happens only when a reply's menu differs from the cached one, as before.

  • Security impact considered
  • Backward compatibility considered
  • Rollback path is clear for risky changes

Security: no new input surface; the reply is parsed by the existing tolerant readers. Compatibility: the snapshot file format is unchanged. Rollback: revert the squash commit; snapshots written by this change load unchanged on the previous code.

Related Issues

N/A

@gloryfromca gloryfromca left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

No blockers; this can merge as far as I am concerned.

I reviewed the full github/main...HEAD diff and the three commits, then traced the capability snapshot store, atomic-update semantics, ACP session/new / session/load response shapes, backend callers, roster/list consumers, and the existing probe/backfill concurrency rules. I also checked backward compatibility, the repository rules and context map, and confirmed the tests were extended rather than weakened. The partial locked revision preserves unrelated concurrent menu updates and stands down for a genuinely newer measurement.

Verification:

  • PYTHONPATH=plugins-dist/everos-memory uv run pytest tests/test_subagent_acp.py tests/test_subagent_probe.py tests/test_rpc_subagents.py -x: 581 passed.
  • A plain uv run pytest ... attempt stopped during fixture setup because the default dev environment does not install the raven_everos workspace package.
  • An uv run --all-extras pytest ... attempt could not build datrie because this runner has no gcc; no tests ran in that attempt.

@LivXue

LivXue commented Oct 21, 2026

Copy link
Copy Markdown
Member Author

Two follow-ups from an independent review of the previous head, both pushed as new commits on top (no history rewritten).

N1, a backend that stood down stayed refused. After SnapshotStore.revise stood down over a newer measurement stamp, the backend kept its older record and never re-read the store, so every later reply was refused the same way until the backend was rebuilt. In-process this self-heals (a Test hot-applies, the boot backfill refreshes the table), but a failed hot-apply or another process sharing the snapshot file left it refusing.

Fix: on a stand-down revise still writes nothing, and now hands back the newer stored record. The relearn returns it and the backend adopts it through its existing hook, so the next reply is judged against the stamp actually stored. The refused reply's menus are never written over the newer record, since that reply may predate it. The handed-back record is flagged stale when it was measured under another launch config than the backend's.

Evidence:

  • Red before the change, green after: the next reply after a stand-down refreshes the menu onto the newer record and keeps its verdict and stamp; the adopted record carries the cross-config stale flag; the same sequence through the backend's _relearn_menus against the real store. The existing stand-down test was tightened from "returns None" to "returns exactly the newer record and the snapshot file is byte-identical".
  • Mutation, each alone and restored: not handing back the newer record turns 4 tests red; handing it back without the stale flag turns 1 red; removing the stamp check turns 4 red (including "an older record does not overwrite a newer measurement"); writing the caller's whole record back turns 1 red ("writes only the menu its reply reported").
  • Relevant subset (13 test files, serial, py3.12, all extras): 1525 passed, 0 failed. ruff, ty, lint-imports (10 kept), commit, large-file, source-language and PR-body checks all pass.

N2, the mode-tier design spec named the old function. It now names relearn_session_menus and notes it was relearn_session_modes at the revision the table is pinned to. The line numbers there (:428, :451) were left alone: they are exactly right at that pinned revision, so moving them to today's numbers would make the measured table false for the revision it names. No test was added for this prose edit; no gate reads that file.

Not covered: native Windows (CI only), and only two or three processes on one machine for the shared-file case.

@LivXue
LivXue requested a review from gloryfromca October 21, 2026 03:56

@gloryfromca gloryfromca left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

No blockers; this can merge as far as I am concerned.

I reviewed the delta from 58b7360d86ff, the full branch diff, the two new commits, and the N1/N2 follow-up note. I traced the revised store return contract through relearn_session_menus, the backend adoption hook, snapshot staleness, the probe/backfill writers, and the menu consumers. I also checked the relevant history, backward compatibility, AGENTS.md and context-map constraints, architecture boundaries, and that the tests were strengthened rather than weakened.

The stand-down now returns the differently stamped record without writing the refused reply over it, and the backend adopts that record so its next menu response can revise against the stored stamp. A changed launch fingerprint remains correctly marked stale for the existing backend. The spec rename also retains the intentionally revision-pinned line references.

Verification: PYTHONPATH=plugins-dist/everos-memory uv run pytest tests/test_subagent_acp.py tests/test_subagent_probe.py tests/test_rpc_subagents.py -x - 584 passed.

@LivXue

LivXue commented Oct 21, 2026

Copy link
Copy Markdown
Member Author

Windows compatibility review (head e6fb8ba5fdaad9b4b00c94d50cd50c369e088801, base 05cf9c35e6f2f9e4211f516b77ba1930f185c2c9).

Scope: Inspected all 7 changed files (docs/specs/2026-09-03-subagent-mode-tiers-design.md, raven/acp_client/acp_agent.py, raven/acp_client/capabilities.py, raven/agent/subagent/probe.py, tests/test_rpc_subagents.py, tests/test_subagent_acp.py, tests/test_subagent_probe.py) and verified exact-head source extraction and reverse patch applicability on native Windows (verify-source.ps1).

Findings

  • No actionable Windows compatibility issue was found in the inspected scope. SnapshotStore.revise updates capability snapshot rows through raven.utils.atomic_io.atomic_update, which serializes writers with a sidecar portalocker lock (LockFileEx on Windows), writes UTF-8 via a temporary file while preserving Windows DACLs, and atomically replaces the target file via os.replace (or leaves the snapshot file untouched and returns the newer stored record when standing down over a newer measurement stamp). relearn_session_menus operates purely on parsed session response dictionaries and immutable CapabilitySnapshot dataclasses without introducing platform-specific path or process assumptions.
  • Non-blocking test hygiene note (tests/test_rpc_subagents.py:3222): test_list_shows_the_model_menu_a_dispatch_relearned constructs "command": f"{sys.executable} {stub}" without command_quote (matching the existing stub_config helper in tests/test_subagent_acp.py:61). This passes when the Python interpreter and checkout paths have no spaces, though quoting path tokens with command_quote would keep the test resilient under spaced Windows paths.

Verification and limitations

  • Native Windows 11 / Python 3.13.12 (uv run pytest ... -n 0 -x):
    • 23 passed, 0 failed across the menu-relearn, model-menu, snapshot-revision, stand-down adoption, and real child-process acp_stub_server.py dispatch tests in tests/test_subagent_acp.py, tests/test_rpc_subagents.py, and tests/test_subagent_probe.py.
    • 26 passed, 0 failed across tests/test_rpc_subagents_model.py.
  • Remote SSH/ACP subagent integration tests were not run locally on this host.
Exact verification commands
# suite-v2: 23 passed, 0 failed, 0 errors, 0 skipped; exit 0
# Working directory: C:\Users\livxu\AppData\Local\Temp\raven-windows-review\bec6f99f3e954eb2bfaa1c73a4997ceb\pr1038\source\head-v2
uv run pytest tests/test_subagent_acp.py tests/test_rpc_subagents.py tests/test_subagent_probe.py -k 'relearn or model_menu or has_model_menu or verify_missing_snapshots or model_choices or stood_down or newer_record' -n 0 -x --tb=short --basetemp 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\bec6f99f3e954eb2bfaa1c73a4997ceb\checks\1038-suite-v2\pytest' --junitxml 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\bec6f99f3e954eb2bfaa1c73a4997ceb\checks\1038-suite-v2\junit.xml'
# model-v2: 26 passed, 0 failed, 0 errors, 0 skipped; exit 0
# Working directory: C:\Users\livxu\AppData\Local\Temp\raven-windows-review\bec6f99f3e954eb2bfaa1c73a4997ceb\pr1038\source\head-v2
uv run pytest tests/test_rpc_subagents_model.py -n 0 -x --tb=short --basetemp 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\bec6f99f3e954eb2bfaa1c73a4997ceb\checks\1038-model-v2\pytest' --junitxml 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\bec6f99f3e954eb2bfaa1c73a4997ceb\checks\1038-model-v2\junit.xml'

The model menu on an acp agent's card is read from the session/new
handshake and cached in the capability snapshot, which never expires by
age; only a Test, a Connect or the boot backfill re-measured it. Every
dispatch already re-learned the mode menu from the session reply it
holds, but not the model menu beside it in the same reply, so a card
could keep offering model ids the agent had long stopped serving.

The relearn path (now relearn_session_menus) reads both menus off that
reply with the probe's own parsers, grouped choices included, and opens
no session and starts no process to do it. Each menu is judged on its
own, and a reply that carries no model entry, an empty one, or one the
adapter flagged as unread keeps the recorded menu rather than clearing
it. A menu recorded as unmeasured becomes measured once a reply carries
one. The models.availableModels key and the model_source rule are
unchanged.

The write now goes through SnapshotStore.revise, which stands down when
the stored row carries a newer measurement stamp than the record the
backend was built from. A Test or boot verify that lands while a
dispatch is in flight on the previous backend is no longer overwritten
by that backend's older record; this protects the mode relearn too.
Backends of one agent each hold the record they were built from, and a
relearn on one does not reach the others' copies. Writing a backend's
whole copy back meant a second backend, whose reply reported only
modes, restored the model menu the first had just refreshed.

SnapshotStore.revise now applies just the fields the reply reported,
onto the row as read inside the file lock, and still stands down when
that row carries another measurement stamp. The stale flag of the
caller's record is kept on what it returns, since re-reading menus is
not a handshake. A redundant model_menu_measured clause is dropped: an
unmeasured menu is always empty, so any reported menu already differs.
Raven's own ACP server attaches configOptions to session/new only;
session/load and the resume route carry the modes alone. The relearn
docstring claimed both menus arrive on every route.
…ement

When a relearn stood down because the stored row carried a newer
measurement stamp, the backend kept the older record and never re-read
the store, so every later reply was refused the same way until the
backend was rebuilt. Hot-apply after a Test and the boot backfill rebuild
it in-process, but a failed hot-apply or another process sharing the
snapshot file left it refusing for good.

SnapshotStore.revise now returns the record it stood down for, together
with whether it wrote, and still writes nothing in that case: the reply
may predate that measurement, so its menus are not taken over it. The
relearn hands that record back, the backend adopts it through its
existing hook, and the next reply is judged against the stamp actually
stored. The handed-back record is flagged stale when it was measured
under another launch config than the caller's.
The measured table keeps its line numbers, which are those of the
revision it is pinned to, and notes the name that revision used.
Use the platform command quoting helper for Python and stub paths. Keep the
RPC dispatch fixture at a path with spaces so unquoted script arguments fail
on every test host, while the shared ACP fixture supports spaced checkouts.
@LivXue
LivXue force-pushed the fix/acp_model_menu_refresh branch from e6fb8ba to a224ddc Compare October 21, 2026 04:35
@LivXue

LivXue commented Oct 21, 2026 •

Copy link
Copy Markdown
Member Author

Fixed the spaced-Windows-path test issue in a224ddc2b7baf09e30cc51f4da165501d25a0f86, following the note in #1038 (comment).

I reproduced both real-process dispatch tests on Windows 11 build 26300 / Python 3.13.12 from a checkout with spaces: the unquoted interpreter path was truncated before launch. Both command builders now use command_quote for the interpreter and script. The RPC fixture also copies the existing stub to a spaced temporary filename, retaining its original handshake/menu assertions while pinning this path case on every host.

After the required rebase: the menu/snapshot/dispatch selection passed 23 tests (561 deselected), and tests/test_rpc_subagents_model.py tests/test_utils_commands.py passed 111. Both uv run --frozen --no-sync pytest ... -n 0 -q --tb=short --idle-ceiling-strict runs exited 0 with no failures, errors or skips. Exact commands are in the updated description. No live model, remote SSH, browser or full native suite was run.

The previous tip e6fb8ba5f maps to 52c105ed0; all five existing patches are equivalent after rebase. The previous diff-coverage failure (87.50% against 90%) is recorded separately, with no threshold change.

CI follow-up: all four unit shards and all three Windows jobs passed. The diff-coverage gate remains at 87.50% (35/40 changed executable lines), below the 90% threshold, the same percentage reported before this Windows test correction. It remains a separate cross-platform coverage limitation; no coverage policy was changed.

@gloryfromca gloryfromca left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

No blockers; this can merge as far as I am concerned.

I reviewed the new test-only commit and the PR diff after its rebase onto the updated github/main. I checked the shared command_quote / command_argv contract on POSIX and Windows, the affected ACP launch callers, the strengthened spaced-path regression, compatibility with the rebased base, repository naming and source rules, architecture boundaries, and test integrity. The quoting change is scoped to fixtures and directly covers the Windows-review follow-up without changing production behavior.

Verification: PYTHONPATH=plugins-dist/everos-memory uv run pytest tests/test_subagent_acp.py tests/test_subagent_probe.py tests/test_rpc_subagents.py -x - 584 passed.

@LivXue

LivXue commented Oct 21, 2026

Copy link
Copy Markdown
Member Author

Windows compatibility review (head a224ddc2b7baf09e30cc51f4da165501d25a0f86, base d9c0a1d74222825ad621a0fd138b691c0edd537d).

Scope: Rechecked the complete 7-file diff after the rebase onto d9c0a1d74222825ad621a0fd138b691c0edd537d and the spaced-path quoting update (a224ddc2b7ba) against the previously reviewed head e6fb8ba5fdaad9b4b00c94d50cd50c369e088801. Exact-head archive extraction, all 7 changed file blobs, and reverse patch applicability were verified on native Windows (verify-source.ps1).

Findings

  • No Windows compatibility issue was found in the inspected scope. The non-blocking test hygiene note from the previous review is resolved in a224ddc2b7ba: both tests/test_rpc_subagents.py:3225 (using a spaced acp stub server.py path in tmp_path) and stub_config in tests/test_subagent_acp.py:59 now quote the interpreter and stub script paths via command_quote, ensuring real child-process launches round-trip through command_argv on Windows even when paths contain spaces.

Verification and limitations

  • Native Windows 11 / Python 3.13.12 (uv run pytest ... -n 0 -x):
    • 23 passed, 0 failed across the menu-relearn, model-menu, snapshot-revision, stand-down adoption, and spaced-path child-process acp_stub_server.py dispatch tests in tests/test_subagent_acp.py, tests/test_rpc_subagents.py, and tests/test_subagent_probe.py.
    • 111 passed, 0 failed across tests/test_rpc_subagents_model.py and tests/test_utils_commands.py.
  • Remote SSH/ACP subagent integration tests were not run locally on this host.
Exact verification commands
# suite: 23 passed, 0 failed, 0 errors, 0 skipped; exit 0
# Working directory: C:\Users\livxu\AppData\Local\Temp\raven-windows-review\f852247ffea44090ac4a3eb8f196eca7\pr1038\source\head
uv run pytest tests/test_subagent_acp.py tests/test_rpc_subagents.py tests/test_subagent_probe.py -k 'relearn or model_menu or has_model_menu or verify_missing_snapshots or model_choices or stood_down or newer_record' -n 0 -x --tb=short --basetemp 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\f852247ffea44090ac4a3eb8f196eca7\checks\1038-suite\pytest' --junitxml 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\f852247ffea44090ac4a3eb8f196eca7\checks\1038-suite\junit.xml'
# model: 111 passed, 0 failed, 0 errors, 0 skipped; exit 0
# Working directory: C:\Users\livxu\AppData\Local\Temp\raven-windows-review\f852247ffea44090ac4a3eb8f196eca7\pr1038\source\head
uv run pytest tests/test_rpc_subagents_model.py tests/test_utils_commands.py -n 0 -x --tb=short --basetemp 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\f852247ffea44090ac4a3eb8f196eca7\checks\1038-model\pytest' --junitxml 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\f852247ffea44090ac4a3eb8f196eca7\checks\1038-model\junit.xml'

A newer stored row this build cannot read back is neither written over
nor handed to the backend, and a snapshot file that cannot be written
logs once and reports nothing learned. Both branches were reachable and
right but unexercised, which left the PR's changed lines under the diff
coverage threshold.

@gloryfromca gloryfromca left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

No blockers; this can merge as far as I am concerned.

I reviewed the new coverage-only commit against the full current PR diff. I checked both added cases against SnapshotStore.revise, relearn_session_menus, and the backend adoption hook: an unreadable differently stamped row remains byte-for-byte untouched and is not adopted, while an unwritable cache logs and leaves backend state unchanged. I also checked compatibility, repository test/source rules, architecture boundaries, history, and that the assertions strengthen rather than weaken coverage. No production code changed in this revision.

Verification: PYTHONPATH=plugins-dist/everos-memory uv run pytest tests/test_subagent_acp.py tests/test_subagent_probe.py tests/test_rpc_subagents.py -x - 586 passed.

@LivXue

LivXue commented Oct 21, 2026

Copy link
Copy Markdown
Member Author

Windows compatibility review (head 1759eed0445d03aedcda0f605c0f7983d040aec2, base d9c0a1d74222825ad621a0fd138b691c0edd537d).

Scope: Rechecked the complete 7-file diff against the previously reviewed head a224ddc2b7baf09e30cc51f4da165501d25a0f86. Exact-head archive extraction, all 7 changed file blobs, and reverse patch applicability were verified on native Windows (verify-source.ps1).

Findings

  • No Windows compatibility issue was found in the inspected scope. Compared to a224ddc2b7baf09e30cc51f4da165501d25a0f86, 1759eed0445d only adds two unit tests in tests/test_subagent_acp.py (test_a_newer_row_this_build_cannot_read_is_neither_overwritten_nor_adopted and test_a_snapshot_file_that_cannot_be_written_costs_only_the_refresh). Both tests pass on native Windows (blocker / "caps.json" under an ordinary file raises OSError on Windows directory creation and is caught cleanly by SnapshotStore.revise).

Verification and limitations

  • Native Windows 11 / Python 3.13.12 (uv run pytest ... -n 0 -x):
    • 25 passed, 0 failed across the menu-relearn, model-menu, snapshot-revision, stand-down adoption, unreadable-row, unwritable-store, and spaced-path child-process acp_stub_server.py dispatch tests in tests/test_subagent_acp.py, tests/test_rpc_subagents.py, and tests/test_subagent_probe.py.
    • 111 passed, 0 failed across tests/test_rpc_subagents_model.py and tests/test_utils_commands.py.
  • Remote SSH/ACP subagent integration tests were not run locally on this host.
Exact verification commands
# suite: 25 passed, 0 failed, 0 errors, 0 skipped; exit 0
# Working directory: C:\Users\livxu\AppData\Local\Temp\raven-windows-review\5d49a031e2574684a1e292f8aec11174\pr1038\source\head
uv run pytest tests/test_subagent_acp.py tests/test_rpc_subagents.py tests/test_subagent_probe.py -k 'relearn or model_menu or has_model_menu or verify_missing_snapshots or model_choices or stood_down or newer_record or newer_row or cannot_be_written' -n 0 -x --tb=short --basetemp 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\5d49a031e2574684a1e292f8aec11174\checks\1038-suite\pytest' --junitxml 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\5d49a031e2574684a1e292f8aec11174\checks\1038-suite\junit.xml'
# model: 111 passed, 0 failed, 0 errors, 0 skipped; exit 0
# Working directory: C:\Users\livxu\AppData\Local\Temp\raven-windows-review\5d49a031e2574684a1e292f8aec11174\pr1038\source\head
uv run pytest tests/test_rpc_subagents_model.py tests/test_utils_commands.py -n 0 -x --tb=short --basetemp 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\5d49a031e2574684a1e292f8aec11174\checks\1038-model\pytest' --junitxml 'C:\Users\livxu\AppData\Local\Temp\raven-windows-review\5d49a031e2574684a1e292f8aec11174\checks\1038-model\junit.xml'

@0xKT

0xKT commented Oct 21, 2026

Copy link
Copy Markdown
Member

Not a blocker (review of 1759eed0, merged onto d9c0a1d7): the model menu now follows the dispatch reply, and a backend that stood down no longer keeps refusing. One side effect is worth fixing. Line numbers are in this head merged onto d9c0a1d7.

Severity of this one finding, not a verdict on the pull request. Any blocking findings from this pass are review threads on the changed files; GitHub renders those collapsed, as a file name with no text.

A menu relearn rebuilds every acp backend, and the rebuild loses the record that restores a cleared model pick.

  • When _relearn_menus gets a record back, it adopts it and calls the caps listener (raven/acp_client/acp_agent.py:1260-1281). That listener is SubagentManager.refresh_agents, which builds a fresh backend for every acp row (raven/agent/subagent/registry.py:320). The fresh backends start with empty _model_pushed and _model_baseline (acp_agent.py:1072-1073).
  • So after a rebuild, clearing an instance's model pick and resuming its session sends no restore: _set_model finds nothing pushed and returns (acp_agent.py:2270-2275). The session stays on the old pick, which is what the restore in _set_model exists to prevent.
  • I measured it with the repo's stub ACP agent, a real snapshot file and a real SubagentManager. Each run pushes stub:model-b, then runs a turn with the pick cleared.
    • With no menu drift, the restore to stub:model-a is sent.
    • It is not sent after a reply whose model menu differs from the stored one.
    • It is not sent after a stand-down adoption, where a Test recorded by another process sharing the snapshot file means nothing is written here. That includes an adoption under another launch config.
    • It is not sent when any of these happens on a different agent, since the rebuild covers the whole table.
  • On main d9c0a1d7 every one of these restores. A reworded mode menu loses the restore on both trees, so the mechanism predates this PR; the PR adds the model-menu and stand-down triggers.
  • Two ways out: keep the per-session push records somewhere a rebuild cannot drop them, or have a relearn update the snapshot the table reads without rebuilding the backends.

@gloryfromca

Copy link
Copy Markdown
Member

No blockers; suggestions only, and they are marked inline.

The new independent review changes my standing verdict from clean to nonblocking. I confirmed the mechanism: refresh_agents rebuilds all external backends, each new AcpAgentBackend starts with empty _model_baseline and _model_pushed, and a later clear therefore returns before sending the restore. The existing restore/refusal tests pass, but none places a capability refresh between the push and clear.

I am not duplicating the finding as another review thread; it is already documented in the reviewer comment above and remains a named follow-up. This revision can still merge.

Verification: PYTHONPATH=plugins-dist/everos-memory uv run pytest tests/test_subagent_acp.py -k "clearing_a_model or refused_restore or refused_switch or untouched_session" -x -n 0 - 4 passed, 326 deselected.

@0xKT
0xKT merged commit c429883 into main Oct 21, 2026
24 checks passed
@0xKT
0xKT deleted the fix/acp_model_menu_refresh branch October 21, 2026 07:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants