Repository navigation
fix(*): let raven's own sub-agents pick a model when their menu came back empty - #620
Conversation
…t model The agent hub's model pill on one of Raven's own acp rows (Raven-PPT here) read "follows the main Raven" and could not be opened: the row's model rule was keyed off its transport alone, so an acp row could only pick from the choices its handshake advertised, and this row's stored handshake advertised none. subagents.update would have refused any value for the same reason. Nothing re-measures that empty menu, because the snapshot's fingerprint covers only the launch config, which has not moved. Decide the rule per row, in one place read by both the listing and the write. An acp row still picks from its advertised menu; one of raven's own that advertised none reports model_source "raven" and is validated and stored through the existing host-catalogue branch, so the page offers the same provider menu the built-in row gets and the stored id is the canonical provider-qualified one the child's own config.set accepts. A third party that advertised none is still taken at its word. The page needs no change: it already draws the host picker for a "raven" row. The model_source description on the wire says the same, so both generated clients are regenerated; the enum is unchanged. Co-authored-by: Claude (claude-fable-5-1) <noreply@anthropic.com>
…l menu A capability snapshot is invalidated only by its launch-config fingerprint, and a measured-empty model menu is kept as a fact. For one of raven's own acp agents that is a handshake taken before this raven's providers reached it, and it survived every boot and connect: the row here had measured zero models two weeks before its siblings measured fifty-five. Treat a ready snapshot of raven's own agent with a measured, empty menu as wanting another measurement, both on connect (capabilities_wanted) and in the boot backfill. Scoped to raven's own agents on purpose: a third party that really offers none would otherwise be relaunched at every boot to be told so again. Co-authored-by: Claude (claude-fable-5-1) <noreply@anthropic.com>
gloryfromca
left a comment
There was a problem hiding this comment.
No blockers; this can merge as far as I am concerned.
Reviewed the full diff plus the capability-snapshot, model-dispatch, RPC, and web-picker callers. I also checked the relevant history, backward compatibility of the optional wire field, AGENTS.md and CONTEXT architecture constraints, and whether existing tests were weakened; the new cases preserve the third-party behavior while covering Raven-owned empty menus and remeasurement.
Verification: uv run pytest tests/test_rpc_subagents.py tests/test_rpc_subagents_model.py tests/test_subagent_probe.py -x (193 passed); uv run pytest tests/test_rpc_schema_match.py -x (392 passed); npm test -- --run src/features/extAgents/ExtAgentsPage.test.tsx (36 passed); npm run lint -- --quiet (passed); source-language check and git diff --check passed.
…e, bound the re-measure The listing's model_source is a menu, not a gate: one of raven's own acp rows now accepts an advertised choice or a host-qualified id under either rule, because it runs on this host's providers whatever its handshake advertised. Before, the boot re-measure could flip the rule from raven to agent under an open sheet and refuse the pick the reader had just been offered. A third-party row keeps the membership-only check. The refusal for an own row names both vocabularies. On the page, a host id an own row kept across a re-measure is drawn with its provider label and clear control instead of as a raw string, and a refused model write refetches the listing so the sheet repaints from the current rule rather than offering a retry of a write whose vocabulary is gone. The empty-menu re-measure of an own row is bounded: only when the host holds a usable provider credential, and once per row per process, so a child that genuinely advertises no model is not relaunched at every boot and connect. Co-authored-by: Claude (claude-fable-5-1) <noreply@anthropic.com>
|
Third commit pushed,
Tests: own row with a menu accepts a host id and still accepts an advertised value; an id neither vocabulary holds is refused with a message naming both; third-party row with a menu still refuses a host id; a row told twice it has no menu is not re-measured a third time in one process; a key-less host re-measures nothing; Served page: the boot re-measure still runs exactly once and restores the 55 choices; a pick made from the host picker opened before the re-measure and sent after it lands (stored PR description updated (three commits). |
gloryfromca
left a comment
There was a problem hiding this comment.
No blockers; suggestions only, and they are marked inline.
The new revision closes the listing/write race coherently: Raven-owned ACP rows accept either advertised or host-qualified model IDs, the UI preserves the host provider badge after remeasurement, failed writes refresh the held listing, and empty-menu remeasurement is bounded without changing the stale/auth/old-snapshot paths. I found no new inline defect; the only remaining item is the author's explicitly deferred follow-up that subagents.instance.set_model still validates per-instance choices against the advertised menu rather than also accepting host IDs.
I reviewed the delta and rechecked its capability-snapshot and model-dispatch callers, wire/generated-client compatibility, UI state flow, relevant history, AGENTS.md and CONTEXT architecture constraints, and test changes. Verification: the related Python and RPC/schema set passed 589 tests; the two web suites passed 47 tests; web lint, source-language validation, and git diff --check passed.
…back empty (#620) ## Summary On the agent hub, the Model pill of one of Raven's own acp rows (Raven-PPT on the reporting machine) read "follows the main Raven" and could not be opened. The row's model rule was keyed off its transport alone: an acp row could only pick from the choices its handshake advertised, this row's stored handshake advertised none, and `subagents.update` would have refused any value for the same reason. Nothing re-measures such a menu, because a capability snapshot is invalidated only by its launch-config fingerprint, which had not moved. Three commits: 1. `fix(rpc)`: the model rule is decided per row, in one helper read by both the listing and the write. An acp row still picks from its advertised menu; one of raven's own that advertised none reports `model_source: "raven"` and is validated and stored through the existing host-catalogue branch, so the page offers the same provider menu the built-in row gets (no page code change) and stores the canonical provider-qualified id the child's own `config.set` accepts. A third party that advertised none is still refused. The `model_source` description says the same on the wire; the enum is unchanged and both generated clients are regenerated. 2. `fix(subagents)`: a ready snapshot of raven's own agent with a measured, empty menu wants another measurement, on connect and in the boot backfill. Scoped to raven's own agents so a third party that really offers none is not relaunched every boot. 3. `fix(*)`: found by an adversarial pass over the served page. The boot re-measure could give the row its menu while the reader had the host picker open, and the write then refused the pick it had just offered ("has no model ...; it offers 55", with a Retry of the same doomed write). The listing's rule is now a menu, not a gate: one of raven's own accepts an advertised choice or a host-qualified id under either rule, because it runs on this host's providers whatever its handshake advertised; a third-party row keeps the membership-only check, and an own row's refusal names both vocabularies. On the page, a host id kept across a re-measure is drawn with its provider label and clear control instead of as a raw string, and a refused model write refetches the listing so the sheet repaints from the current rule. The empty-menu re-measure is bounded: only when the host holds a usable provider credential, and once per row per process, so a child that genuinely advertises no model is not relaunched at every boot and connect. Not in this PR (follow-up): the task pane's per-instance model picker keeps the old rule (`SubagentManager.set_instance_model` validates against the advertised menu only), so a host id picked for an own row on the hub is refused by `subagents.instance.set_model`. Known limit: a product launched on its own key does not inherit the host providers, so a host model picked for it is refused by the child and only logged (`acp_agent._say_model_refused`). ## Type - [x] Fix - [ ] Feature - [ ] Docs - [ ] CI / tooling - [ ] Refactor - [ ] Other ## Verification - `uv run pytest tests/test_rpc_subagents.py tests/test_rpc_subagents_model.py tests/test_rpc_schema_match.py tests/test_agent_schemas.py tests/test_subagent_probe.py tests/test_subagent_acp.py -q` -- 817 passed after the first two commits. New: listing rule (own + empty -> `raven`, third party + empty -> `agent`, advertised menu kept verbatim), update accepts/refuses through the host branch for an own row, a third-party row with none is still refused; `capabilities_wanted` and the boot backfill re-measure own + ready + empty exactly once. The rule tests fail against the previous `subagents.py`; the probe tests fail against the previous `probe.py`. - The new tests rerun with an empty `HOME` -- 84 passed. - `uv run pytest tests/test_rpc_subagents.py tests/test_rpc_subagents_model.py tests/test_subagent_probe.py tests/test_rpc_schema_match.py tests/test_subagent_acp.py -q` -- 804 passed after the third commit. New: an own row WITH a menu takes a host id and still takes an advertised value; an id neither vocabulary holds is refused with a message naming both; a third-party row with a menu still refuses a host id; a row told twice it has no menu is not re-measured a third time in the same process; a key-less host re-measures nothing at boot or connect; `capabilities_wanted` answers false on a key-less home and true once a provider key is written. The update cases fail against the second commit's `subagents.py`; each bound of the probe change was shown by a targeted mutation to red exactly its own test. - `npm test --prefix ui-web -- src/features/extAgents` -- 95 passed (new: an `agent` row storing a host id outside its choices shows the split id under its provider label and still clears; a refused model pick repaints from the listing). - `npm test --prefix ui-web` -- 188 files, 2467 passed. - `npm run gen:check --prefix ui-web`, `npm run lint:rpc --prefix ui-tui`, `uv run ruff check raven tests`, `npm run type-check --prefix ui-web`, `npm run lint --prefix ui-web` -- clean. - Served page with an isolated agent home carrying a copy of the real capability snapshot (Raven-PPT measured with zero choices): before, `subagents.list` reports `model_source: "agent"` and the pill is disabled; after, it reports `"raven"`, the pill opens the host picker, a pick is stored on the row as a provider-qualified id and shown with a clear control, the clear puts it back; a sibling with a measured menu and a third-party row with none are unchanged. Second pass, Raven-Code's stored menu emptied so the boot re-measure fires: the re-measure runs once and restores 55 choices; a pick made from the host picker opened before the re-measure and sent after it lands (stored `openrouter/z-ai/glm-5.3-flash`, pill shows the model and provider with the clear control, no Retry); a host id stored before the re-measure reads "anthropic/claude-opus-5 . OpenRouter" after it; refusals carry the combined sentence; the third-party row keeps its disabled pill. - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [ ] User-facing docs or screenshots are updated when needed ## Risk - [x] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes `model_source: "raven"` now means "picks from raven's catalogue" rather than "is the built-in row"; the agent hub page is its only reader. A stored host id on an own acp row is an ordinary provider-qualified model id, readable by older code as a value outside the advertised menu (shown and clearable). The boot re-measure costs one child handshake per process for an own agent that keeps advertising no menu, and none on a host without provider credentials. Rollback: revert the three commits in reverse order. ## Related Issues N/A --------- Co-authored-by: Claude (claude-fable-5-1) <noreply@anthropic.com>
Summary
On the agent hub, the Model pill of one of Raven's own acp rows (Raven-PPT on the reporting machine) read "follows the main Raven" and could not be opened. The row's model rule was keyed off its transport alone: an acp row could only pick from the choices its handshake advertised, this row's stored handshake advertised none, and
subagents.updatewould have refused any value for the same reason. Nothing re-measures such a menu, because a capability snapshot is invalidated only by its launch-config fingerprint, which had not moved.Three commits:
fix(rpc): the model rule is decided per row, in one helper read by both the listing and the write. An acp row still picks from its advertised menu; one of raven's own that advertised none reportsmodel_source: "raven"and is validated and stored through the existing host-catalogue branch, so the page offers the same provider menu the built-in row gets (no page code change) and stores the canonical provider-qualified id the child's ownconfig.setaccepts. A third party that advertised none is still refused. Themodel_sourcedescription says the same on the wire; the enum is unchanged and both generated clients are regenerated.fix(subagents): a ready snapshot of raven's own agent with a measured, empty menu wants another measurement, on connect and in the boot backfill. Scoped to raven's own agents so a third party that really offers none is not relaunched every boot.fix(*): found by an adversarial pass over the served page. The boot re-measure could give the row its menu while the reader had the host picker open, and the write then refused the pick it had just offered ("has no model ...; it offers 55", with a Retry of the same doomed write). The listing's rule is now a menu, not a gate: one of raven's own accepts an advertised choice or a host-qualified id under either rule, because it runs on this host's providers whatever its handshake advertised; a third-party row keeps the membership-only check, and an own row's refusal names both vocabularies. On the page, a host id kept across a re-measure is drawn with its provider label and clear control instead of as a raw string, and a refused model write refetches the listing so the sheet repaints from the current rule. The empty-menu re-measure is bounded: only when the host holds a usable provider credential, and once per row per process, so a child that genuinely advertises no model is not relaunched at every boot and connect.Not in this PR (follow-up): the task pane's per-instance model picker keeps the old rule (
SubagentManager.set_instance_modelvalidates against the advertised menu only), so a host id picked for an own row on the hub is refused bysubagents.instance.set_model. Known limit: a product launched on its own key does not inherit the host providers, so a host model picked for it is refused by the child and only logged (acp_agent._say_model_refused).Type
Verification
uv run pytest tests/test_rpc_subagents.py tests/test_rpc_subagents_model.py tests/test_rpc_schema_match.py tests/test_agent_schemas.py tests/test_subagent_probe.py tests/test_subagent_acp.py -q-- 817 passed after the first two commits. New: listing rule (own + empty ->raven, third party + empty ->agent, advertised menu kept verbatim), update accepts/refuses through the host branch for an own row, a third-party row with none is still refused;capabilities_wantedand the boot backfill re-measure own + ready + empty exactly once. The rule tests fail against the previoussubagents.py; the probe tests fail against the previousprobe.py.The new tests rerun with an empty
HOME-- 84 passed.uv run pytest tests/test_rpc_subagents.py tests/test_rpc_subagents_model.py tests/test_subagent_probe.py tests/test_rpc_schema_match.py tests/test_subagent_acp.py -q-- 804 passed after the third commit. New: an own row WITH a menu takes a host id and still takes an advertised value; an id neither vocabulary holds is refused with a message naming both; a third-party row with a menu still refuses a host id; a row told twice it has no menu is not re-measured a third time in the same process; a key-less host re-measures nothing at boot or connect;capabilities_wantedanswers false on a key-less home and true once a provider key is written. The update cases fail against the second commit'ssubagents.py; each bound of the probe change was shown by a targeted mutation to red exactly its own test.npm test --prefix ui-web -- src/features/extAgents-- 95 passed (new: anagentrow storing a host id outside its choices shows the split id under its provider label and still clears; a refused model pick repaints from the listing).npm test --prefix ui-web-- 188 files, 2467 passed.npm run gen:check --prefix ui-web,npm run lint:rpc --prefix ui-tui,uv run ruff check raven tests,npm run type-check --prefix ui-web,npm run lint --prefix ui-web-- clean.Served page with an isolated agent home carrying a copy of the real capability snapshot (Raven-PPT measured with zero choices): before,
subagents.listreportsmodel_source: "agent"and the pill is disabled; after, it reports"raven", the pill opens the host picker, a pick is stored on the row as a provider-qualified id and shown with a clear control, the clear puts it back; a sibling with a measured menu and a third-party row with none are unchanged. Second pass, Raven-Code's stored menu emptied so the boot re-measure fires: the re-measure runs once and restores 55 choices; a pick made from the host picker opened before the re-measure and sent after it lands (storedopenrouter/z-ai/glm-5.3-flash, pill shows the model and provider with the clear control, no Retry); a host id stored before the re-measure reads "anthropic/claude-opus-5 . OpenRouter" after it; refusals carry the combined sentence; the third-party row keeps its disabled pill.Relevant tests pass locally
Relevant lint / type checks pass locally
User-facing docs or screenshots are updated when needed
Risk
model_source: "raven"now means "picks from raven's catalogue" rather than "is the built-in row"; the agent hub page is its only reader. A stored host id on an own acp row is an ordinary provider-qualified model id, readable by older code as a value outside the advertised menu (shown and clearable). The boot re-measure costs one child handshake per process for an own agent that keeps advertising no menu, and none on a host without provider credentials. Rollback: revert the three commits in reverse order.Related Issues
N/A