Skip to content

refactor(*): one registry reader and one writer, and the coding agent can add a machine - #690

Merged
LivXue merged 8 commits into
mainfrom
refactor/connection_registry_one_copy
Sep 24, 2026
Merged

LivXue merged 8 commits into
mainfrom
refactor/connection_registry_one_copy

Conversation

@silverLXT

Copy link
Copy Markdown
Contributor

Why

In a research -> code -> oncall chain run on 2026-09-22, Raven-Code called
exec(machine="conn_cpu_32c") and got "No connection is registered", on a home
whose connections.json listed seven machines. It then read host/port/key out
of the config and ran a raw ssh root@<ip> -p <port> -i ~/.ssh/id_rsa, putting
the way in into the transcript.

Two gaps:

  1. Read side. raven.ops.connections.store_path() looked only beside the
    instance's own config. A sub-agent runs on a rendered config in its own state
    directory, so it looked in an empty directory. The host already hands every
    sub-agent RAVEN_HOME; the registry just was not looked for there. Only the
    on-call launcher worked around this (it sets RAVEN_CONNECTIONS itself).
  2. Write side. ops_connection_add (feat(raven-oncall): let the on-call instance add the machine the owner describes #545) lived in the on-call plugin. In the
    usual chain the coding agent runs first, meets the empty registry first, and
    had no way to take the owner's answer.

What changes

  • store_path(): env var -> raven_home()/connections.json if it exists ->
    beside the config if THAT exists -> the home (where a first add lands). An
    install that kept its own list beside its config keeps working.
  • New raven/ops/connection_add.py: probe, ssh_defaults, parse_probe,
    write, write_ssh_alias, and ASK_OWNER (the questions relayed to the owner).
    The tool calls these functions directly; raven ops connection add (the
    terminal command for a person) calls the same ones, so the logic exists once.
  • New raven/agent/tools/connection_add.py: ops_connection_add as a trunk tool,
    opt-in via tools.connectionAdd (on in raven-code and raven-oncall, off in
    the other three products, whose pinned tool faces are unchanged).
  • raven.ops.transport.make_ssh_runner(identities_only=) + ISOLATE_IDENTITY,
    moved from the plugin: a key the tool picked itself is probed with that key
    alone (see feat(raven-oncall): let the on-call instance add the machine the owner describes #545 review).
  • On-call plugin: oncall_flow/connections.py imports trunk's reader instead of
    keeping a hand-aligned copy (758 -> 212 lines); keeps only campaign lookups
    (get, display_name, resolve_into, describe). Its own tool file,
    factory and manifest row are removed.
  • Raven-Code guide (TOOLS_CODE.md) gains when to leave this computer: write
    here; verify where the software is; ask the owner for a machine only when
    installing the software here is wrong (GPU, memory) or heavy (compiled solver,
    toolchain, long download); never ssh from an address in the task statement.
  • SHOWN made public in raven.ops.connections (the plugin may not import an
    underscored trunk name; _SHOWN stays as an alias).
  • Docs: docs-site/docs/oncall.md / oncall.zh.md updated for the new lookup
    and the tool.

Not in scope

  • The pre-dispatch registry gate removed in a44664d is NOT restored. Where a
    job runs is still decided inside the agent that runs it; this only makes that
    agent find the owner's registry and able to add to it.
  • fix(agent): exec refuses a typed ssh to a machine the registry knows #552 (exec refuses a typed ssh to a registered machine) is complementary: it
    only fires once the registry is visible, which this makes true for Raven-Code.

Tests

  • tests/test_ops_connection_add.py (moved from the plugin suite, patches trunk
    modules), including the ssh -G measurement that only the candidate key is
    offered under isolation.
  • Five new cases in tests/test_ops_connections.py for the lookup order.
  • Plugin parity tests replaced by an identity pin (the plugin's names ARE trunk's
    objects).
  • Code launcher face ledger gains a machine lane; oncall face unchanged.
  • Full suite: see CI.

@LivXue -- review requested: this touches raven/ops and the RAVEN_CONNECTIONS
handoff described in a44664d.

Co-authored-by: Claude (claude-opus-5-5) noreply@anthropic.com

Generated with Claude Code (https://claude.com/claude-code)

… can add a machine

Two products run work on the owner's machines, and in the usual chain the
coding agent runs first: it builds a case where the solver is and
smoke-tests it there, then hands off to the on-call agent. Yesterday's run
showed the seam between them was broken twice over.

Read side. `raven.ops.connections.store_path()` resolved the registry
"beside the config". A sub-agent runs on a rendered config in a state
directory of its own, so Raven-Code read an empty directory while the
owner's seven machines sat one level up in the home the host had handed it
as RAVEN_HOME; `exec(machine=...)` answered "No connection is registered",
and the model reached for the raw ssh address instead. The path now resolves
to the env var, else the owner's home when a registry is there, else beside
the config when THAT exists, else the home -- which is also where a first
add lands, so what the coding agent writes is what the on-call agent reads.
The on-call launcher's RAVEN_CONNECTIONS pointer keeps working and is no
longer the only thing that does.

Write side. The tool that takes the owner's answer and writes a machine
(`ops_connection_add`, #545) lived in the on-call plugin; the coding agent,
which meets the empty registry first, had no way to consume the answer. The
probe, the writer and the ssh alias now live in `raven.ops.connection_add`,
behind both the tool (`raven/agent/tools/connection_add.py`) and
`raven ops connection add`; the plugin's copies are gone. Key isolation for
a candidate the tool picked itself moved with it, as
`raven.ops.transport.make_ssh_runner(identities_only=)`, and the probe rides
the same transport `exec`'s machine channel uses.

The tool is opt-in per product (`tools.connectionAdd`): on in raven-code and
raven-oncall, off elsewhere, because five product tool faces are pinned and
a lane that never runs work on the owner's machines has no use for a tool
that writes to their ssh config.

The on-call plugin's `connections` module kept a hand-aligned copy of
trunk's reader (eleven functions) on the ground that trunk's module was not
a contracts paper; the plugin already imports trunk's utilities and hooks,
and a third copy for the coding agent was the alternative. It imports the
reader now and keeps only what a campaign needs (lookup by id, display name,
meta resolution, the model-facing listing). The ask the model relays to the
owner is one text (`ASK_OWNER`), shared by the on-call listing reply and the
tool.

Raven-Code's guide gains the rule for leaving this computer at all: write
here; verify where the software is; ask the owner for a machine only when
installing the software here would be wrong (GPU, memory) or heavy (a
compiled solver, a toolchain, a long download); never type an ssh from an
address in the task statement.

Tests: the connection-add suite moves to trunk and patches the kernel
modules; the plugin's two "aligned by hand" parity tests become one
identity pin; five new cases pin the path resolution; the code product's
face ledger gains a machine lane. Complements #552 (exec refuses a typed ssh
to a machine the registry knows), which only fires once the registry is
visible.

Co-authored-by: Claude (claude-opus-5-5) <noreply@anthropic.com>
@silverLXT
silverLXT requested a review from LivXue as a code owner September 23, 2026 03:59

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

Blocking: the shared SSH probe no longer enforces its advertised timeout.

I reviewed the full github/main...HEAD diff and relevant surrounding code, callers, and history. I also checked AGENTS.md plus the Runtime/agent terminology and architecture in CONTEXT-MAP.md, CONTEXT.md, and agents/README.md; backward compatibility of registry-path resolution; the moved writer and both product launch paths; and the test changes for weakening. The home-first precedence is deliberate and covered, and the reader/writer consolidation otherwise preserves the existing contracts.

Verification:

  • uv run pytest tests/test_ops_connection_add.py tests/test_ops_connections.py tests/test_cli_ops_connection_commands.py tests/test_shell_machine_channel.py tests/test_agents_oncall_flow_connections.py tests/test_agents_oncall_flow_watcher.py tests/test_agents_oncall_launcher.py tests/test_agents_code_launcher.py -x - 195 passed.
  • uv run pytest - 19,932 passed, 130 skipped, 74 failed, 283 errors; the failures/errors are outside this diff and are dominated by optional packages/plugins absent from this checkout (for example websockets, raven_everos, raven_design, and raven_ppt).
  • A direct patched call to probe(..., timeout=0.25) confirmed that the remote runner invokes subprocess.run without a timeout.

Once the SSH execution deadline is restored, I see no other blocker in this revision.

Comment thread raven/ops/connection_add.py
…efusals

The shared probe passed its 30 s timeout to `runner_from`, which applied it
only to the local runner; the ssh runner called `subprocess.run` with no
timeout, and `ConnectTimeout` is over once the session is up. A machine that
accepted and then stopped answering held `raven ops connection add` (and the
tool's worker thread) forever, where the old CLI gave up at 30 s (review on
#690).

`make_ssh_runner` takes `cap_seconds`, returns `TIMED_OUT_RC` when it fires
(the code the local runner already used), and `runner_from` passes the cap to
both runners. The probe reads 124 as "no answer within Ns" on both of its ssh
paths. The machine channel now gets its own backstop on ssh too, behind the
wrapper that caps the far side.

Tests drive a stand-in ssh on PATH for the silent, refusing and missing
cases, and cover each refusal and remark the tool can return; diff coverage
on the branch measures 99%.

Co-authored-by: Claude (claude-opus-5-5) <noreply@anthropic.com>

@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 re-read the full github/main...HEAD diff and the change since ac98cb2. I checked the repository rules and Runtime/agent architecture documents, the shared transport callers and history, registry-path and CLI backward compatibility, and the changed tests for weakening. The SSH deadline now reaches the normal and isolated-key probe paths, returns the transport's existing rc 124 on timeout, and is rendered as a no-write refusal. I found no new issue in the revision.

Verification: uv run pytest tests/test_ops_connection_add.py tests/test_ops_connections.py tests/test_cli_ops_connection_commands.py tests/test_shell_machine_channel.py tests/test_agents_oncall_flow_connections.py tests/test_agents_oncall_flow_watcher.py tests/test_agents_oncall_launcher.py tests/test_agents_code_launcher.py -x - 213 passed. I also replied to and resolved my prior timeout thread.

@silverLXT
silverLXT requested a review from 0xKT September 23, 2026 06:29
…egistry_one_copy

# Conflicts:
#	raven/agent/loop/_shared.py
#	raven/agent/loop/wiring.py

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

This revision merges current main; after refreshing github/main, the PR remains the same 28-file registry change. git range-diff shows both authored commits are unchanged from the revision I previously reviewed. I rechecked the full PR diff, repository rules and Runtime/agent architecture constraints, callers and history, backward compatibility, and the tests for weakening, with particular attention to the newer machine-exec guard now on the base. I found no new issue, and my earlier timeout thread remains resolved.

Verification: git diff --check github/main...HEAD passed; uv run pytest tests/test_ops_connection_add.py tests/test_ops_connections.py tests/test_cli_ops_connection_commands.py tests/test_shell_machine_channel.py tests/test_agents_oncall_flow_connections.py tests/test_agents_oncall_flow_watcher.py tests/test_agents_oncall_launcher.py tests/test_agents_code_launcher.py -x - 283 passed.

Comment thread raven/agent/tools/connection_add.py
Comment thread raven/agent/loop/wiring.py
@LivXue

LivXue commented Sep 24, 2026

Copy link
Copy Markdown
Member

Blocking: 1 finding(s) need correction before this revision can merge; see the inline notes.

Scope: I reviewed only the raven/agent/** portion of this PR -- 5 of 28 changed files: the new raven/agent/tools/connection_add.py and its wiring in raven/agent/loop/. The remaining 23 files were read as context but are not covered by this verdict. raven/agent/subagent/ is untouched, and the new registration does not reach spawn or run_subagent_dag: built-in sub-agents assemble their own registry (raven/agent/subagent/backends/raven_loop.py) and are never handed the flag, and both products that turn it on disable both dispatch tools.

What I examined: the new tool against its plugin predecessor (the execution path is unchanged; the model-facing description and success reply now also name exec's machine parameter); every non-test construction of ToolWiring and every caller of build_runtime (the flag reaches acp, the gateway including its rebuild, agent and a2a; replay replaces the registry wholesale); the plugin's _wired gate and face roster that the move retires; the permission tier and tool-search visibility, both keyed by tool name and so unchanged; and the import contracts.

Confirmed findings:

  • Blocker, inline at raven/agent/tools/connection_add.py:207: when the registry is empty at process start, exec's schema is frozen without machine, yet after the first add this reply, the fix(agent): exec refuses a typed ssh to a machine the registry knows #552 refusal and the Raven-Code guide all send the model to exec(machine=...). Reproduced.
  • Nonblocking, inline at raven/agent/loop/wiring.py:944: the tool has no group in the settings table, and the guard for that builds a loop that cannot register it. Reproduced.

Outside the reviewed scope, disclosed here because no inline note can be filed there:

  • Question, raven/ops/connections.py:314-321. Home-first is right for a sub-agent, where the file beside a rendered config is a stale copy, but not obviously for a host started with --config /x/config.json, where the file beside the config is the owner's own list: once any registry appears in raven_home(), that host switches to it. One probe, run at both revisions (a --config host with conn_cpu_32c in /x/connections.json, then a first home registry appears): at the merge base the host reads /x/connections.json before and after; at 257acb1 it reads it before, and afterwards only the home file (['conn_new']). A coding agent's first add for an owner with no registry writes exactly that home file, so the switch no longer needs the owner to run anything. The same shape reaches connections_registry() in agents/raven-oncall/run.py:112-126: an install that kept its own list and has no home file drops that list at the next launch after a coding-agent add (reproduced: both rows still on disk, unread). Separately, the product children of a --config host see its registry neither before nor after this PR, since their launchers render a config of their own and inherit only RAVEN_HOME. docs-site/docs/oncall.md does say a registry beside a config elsewhere is read "when the home has none", so the precedence is documented; the question is whether it is meant to cover a --config host.
  • Nonblocking, CONTEXT.md:339-349. The canonical Machine entry still says rows are registered with raven ops connection add, held "beside the config or wherever RAVEN_CONNECTIONS points", and handed over by the launcher's pointer. After this PR an agent tool writes rows, the store resolves home-first, and, in this PR's own commit message, the launcher's pointer "is no longer the only thing that does". Code-reading.
  • Nonblocking, agents/raven-oncall/plugins/oncall-flow/oncall_flow/connections.py:205-213. _PROBE has no reader left in the tree, and "Mirrors the CLI's probe line" no longer holds: the probe line is raven.ops.connection_add.PROBE_SCRIPT now. Code-reading (a tree-wide grep finds no other reference).

Verification, all at 257acb1 against an extracted head tree, with the imported raven confirmed to come from that tree:

  • lint-imports: 10 contracts kept, 0 broken.
  • pytest tests/test_ops_connection_add.py tests/test_ops_connections.py tests/test_cli_ops_connection_commands.py tests/test_shell_machine_channel.py tests/test_agents_oncall_flow_connections.py tests/test_agents_oncall_flow_watcher.py tests/test_agents_oncall_launcher.py tests/test_agents_code_launcher.py tests/test_tool_capabilities.py tests/test_settings_tool_groups.py tests/test_agent_loop_tool_search.py: 339 passed, 1 failed. The failure, test_shell_machine_channel.py::test_the_table_agrees_with_the_ssh_on_this_computer[tag-value], fails the same way at the merge base on this machine (OpenSSH 8.9p1), so it is local and not this PR's.
  • The two inline findings and the --config A/B were reproduced with the probes described above and in the notes.

Checked and not reported: the plugin's face roster serves only tools with set_context or a scheduler, so retiring adopt for this tool loses nothing; the _wired gate and tools.connectionAdd coincide in the shipped on-call config; trunk's _BUDGET_UNITS matches the plugin's former tuple exactly; the moved connection-add suite grew from 6 cases to 27 with none lost (one renamed). gloryfromca's SSH deadline finding from the earlier round is resolved at this head and is not re-filed. In passing: every ssh runner, trunk's and the plugin's former one alike, inherits the caller's stdin, which under raven acp is the protocol stream. That is identical at the merge base, code-reading only, and not attributable to this PR.

LivXue
LivXue previously requested changes Sep 24, 2026

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

Requesting changes for the blocking finding at raven/agent/tools/connection_add.py:207. The scoped summary and the other notes are in the comments above.

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

Blocking: exec's model-facing schema must gain the machine parameter after the first registry write.

The new review evidence changes my prior clean stance on this revision. I reproduced the first-add transition against ToolRegistry: ExecTool.parameters becomes machine-aware after a row is added, but the registry continues serving the frozen schema admitted at startup, so the provider is not offered the parameter that ops_connection_add tells it to use. Direct dispatch accepts the undeclared argument, but that does not make it a dependable model-facing lane.

The separate settings-table note also holds but remains nonblocking: the coverage guard builds with connection_add=False, and the opt-in tool is absent from toolGroups.json.

Verification: uv run pytest tests/test_ops_connection_add.py tests/test_shell_machine_channel.py tests/test_settings_tool_groups.py tests/test_tool_registry_execute.py -x - 151 passed. A direct before/after registry reproduction showed live_has_machine=True while both the admitted and subsequently rendered definitions remained False. I replied with the measured facts on both new threads; they were opened by another reviewer, so I did not resolve them.

xiaotian.luo and others added 2 commits September 24, 2026 16:25
…onfig's own list

Review findings on the registry-writer PR (2026-09-24).

Blocker: a process that started with no machine kept an `exec` schema without
`machine` after the first `ops_connection_add`, because the registry serves a
tool from the copy it took at admission and `ExecTool` authored no
`to_schema` -- so the reply, the typed-ssh refusal from #552 and the new
Raven-Code guide all sent the model to `machine=<id>` while the schema it was
shown had no such parameter. `ExecTool.to_schema` is authored now, which is
how a tool declares a dynamic shape; the shape moves only when the registry
goes from empty to not.

The registry falls back to the owner's home only when there is none beside the
instance's own config, in `raven.ops.connections.store_path` and in the on-call
launcher's `connections_registry`. Home-first was right for a sub-agent, whose
config sits in a state directory nothing writes, but it silently moved a
`--config /x/config.json` host off `/x/connections.json` as soon as a registry
appeared in the home -- which an agent's first add now writes with no owner
action. A sub-agent still resolves the home: nothing is ever written beside its
rendered config.

Also: `ops_connection_add` joins the settings tool table's `run` group, and the
guard that diffs that table against a loop's registry turns the flag on, so it
can see opt-in tools; `CONTEXT.md`'s Machine entry and both on-call doc pages
describe the lookup and the tool; the plugin's orphaned `_PROBE` is gone; and
#552's ssh-version test asks the client whether `-P` takes a value instead of
looking for the string "tag", which an older client prints as the hostname --
so it never skipped on OpenSSH 8.9.

Co-authored-by: Claude (claude-opus-5-5) <noreply@anthropic.com>

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

Reviewed the new revision and the full PR against the repository rules and Machine architecture. The previous schema blocker is fixed: ExecTool now explicitly declares a dynamic schema, so ToolRegistry.get_definitions() reflects the first real registry write on the next model call. The settings-table follow-up is also addressed by grouping ops_connection_add and enabling the opt-in tool in the coverage guard.

I also checked the added registry-path compatibility correction against its callers and history: an existing config-adjacent registry remains authoritative, while rendered configs without their own registry still fall back to the owner's home. The tests exercise both sides. I found no weakened coverage or backward-compatibility regression in the delta.

Verification: uv run pytest tests/test_ops_connection_add.py tests/test_ops_connections.py tests/test_cli_ops_connection_commands.py tests/test_shell_machine_channel.py tests/test_settings_tool_groups.py tests/test_agents_oncall_flow_connections.py tests/test_agents_oncall_flow_watcher.py tests/test_agents_oncall_launcher.py tests/test_agents_code_launcher.py tests/test_tool_registry_execute.py -x (314 passed). I covered the project rules and domain terminology, the full diff and new delta, relevant callers and history, backward compatibility, test changes, and the one-reader/one-writer architecture.

…s row

The settings Tools page now draws one more switch row (ops_connection_add
joined the run group), so the skeleton test expects 23, and ruff format
wraps the store_path docstring.

Co-authored-by: Claude (claude-opus-5-5) <noreply@anthropic.com>
@silverLXT

Copy link
Copy Markdown
Contributor Author

Addressing the review summary (a525fcf, 5146f0a). Both inline findings are fixed and replied to; the out-of-scope notes:

  • --config question -- you were right, it is not meant to cover that host. The order is now: RAVEN_CONNECTIONS, else connections.json beside the active config when one is there, else the raven home (where a first add lands). A --config /x/config.json host keeps reading /x/connections.json after a home registry appears; a sub-agent still resolves the home, since nothing is ever written beside its rendered config. connections_registry() in agents/raven-oncall/run.py takes the same order, so an install that kept its own list no longer drops it after a coding-agent add. Pinned by test_a_list_beside_the_config_keeps_winning_after_a_home_registry_appears and test_an_own_registry_stays_after_a_home_registry_appears; the old home-wins test is replaced. CHANGELOG and both oncall doc pages say the new order.
  • CONTEXT.md Machine entry -- rewritten: registered by the CLI or by ops_connection_add, and the lookup order above.
  • _PROBE in the on-call plugin -- removed; nothing read it.
  • test_the_table_agrees_with_the_ssh_on_this_computer[tag-value] on OpenSSH 8.9 -- that was fix(agent): exec refuses a typed ssh to a machine the registry knows #552's test and a real bug in it: the skip looked for the string "tag" in ssh -G output, which an older client prints as the hostname, so it never skipped. It now asks the client directly whether -P tag host.invalid resolves to host.invalid; on 9.9 the row runs, on 8.9 it skips.

Locally: the connection, launcher, tool-face and settings suites pass (365), the settings UI suite passes (183), and the full Python suite has one failure, test_rpc_files.py::test_a_host_without_libreoffice_says_so, which fails the same way on main here.

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

Reviewed the revision delta and rechecked it against the full PR, prior findings, repository rules, relevant callers/history, backward compatibility, test integrity, and the one-reader/one-writer architecture. The added blank line is formatter-only, and raising the settings skeleton count from 22 to 23 matches the newly grouped ops_connection_add row; the rendered test passes rather than merely accepting a changed fixture. No prior fix is disturbed.

Verification: uv run pytest tests/test_ops_connections.py tests/test_settings_tool_groups.py tests/test_shell_machine_channel.py -x (124 passed); npm test -- src/features/settings/Skeletons.test.tsx (11 passed).

@LivXue

LivXue commented Sep 24, 2026

Copy link
Copy Markdown
Member

No blockers in the reviewed scope.

This supersedes my summary at 257acb1, which opened Blocking: for the exec schema finding; that finding is fixed. I am leaving my change request in place while the question below is open for the author.

Scope: I reviewed only the raven/agent/** portion of this PR -- 7 of 35 changed files, now including raven/agent/tools/shell.py and raven/agent/tools/machine_exec.py from the fix. The remaining 28 files were read as context but are not covered by this verdict. raven/agent/subagent/ is untouched. ExecTool.to_schema also reaches built-in sub-agents, which construct their own ExecTool (raven/agent/subagent/backends/raven_loop.py:435), but not spawn or run_subagent_dag.

What I examined: the delta since 257acb1. 69a3dc439 merges main (ui-web commits only) and leaves this PR's own patch unchanged: the diff against its merge base, stripped of index and hunk lines, hashes the same before and after. a525fcf9a is the fix; inside the reviewed scope it authors ExecTool.to_schema and corrects the machines_registered docstring. 5146f0a43 restores a formatter blank line and the settings skeleton count. I re-ran the last round's reproductions at this head, beside 257acb1 and, where the code exists there, the merge base.

Last round, now verified at 5146f0a:

  • Blocker, exec schema frozen after a first add: fixed. At the provider, over two real turns with one row written between them, the second call's exec carries machine and the machine sentence (at 257acb1, neither). The new test_the_machine_lane_appears_once_a_first_machine_is_added fails with the override removed. The author resolved the thread; I confirm it.
  • Nonblocking, settings table: fixed; the guard now fails when the entry is removed. The author resolved the thread; I confirm it.
  • Question, home-first under --config: both shapes I reproduced are fixed. A --config host keeps /x/connections.json after a home registry appears, and on-call keeps its own list after a coding-agent add. The fix raises the question below.
  • Nonblocking, CONTEXT.md Machine entry: fixed; it states the lookup order and the tool.
  • Nonblocking, orphaned _PROBE: removed.

Outside the reviewed scope, disclosed here because no inline note can be filed there:

  • Question, raven/ops/connections.py:319-324 and agents/raven-oncall/run.py:125-126. Beside-the-config-first cannot tell a legacy copy beside a rendered product config from a --config host's own list, and it is wrong for the first the way home-first was wrong for the second. Two consequences, reproduced at the merge base, 257acb1 and 5146f0a:
    • (a) On-call with a legacy list of its own and no home registry: a coding-agent add lands in the home while on-call keeps reading its own list, so on-call never sees that machine (after the add it lists ['conn_cpu_32c', 'conn_gpu_2'], without conn_new). For such installs that breaks TOOLS_CODE.md:54: "the machine it writes is the one the on-call agent reads afterwards".
    • (b) A legacy copy beside the rendered config plus the owner's maintained home registry: at 5146f0a both on-call and raven-code read the copy (['conn_cpu_32c']) and lose the row added since (conn_added_since). At 257acb1 both read the home; at the merge base on-call read the home, so for on-call this is a regression.
    • The replaced test, test_the_owners_home_wins_over_a_list_beside_the_config_when_both_exist, guarded (b) ("reading it would hide every machine the owner added since"); its replacement uses the same sub-agent fixture, _rendered_config, to assert the opposite. The premise that nothing is ever written beside a rendered config holds for every writer since 041271b62 (2026-09-01), but the on-call launcher still honours a legacy list in exactly that place ("An install that predates this and keeps its own list stays on it"). Only installs still carrying a pre-2026-09-01 copy are affected. Should the rule key on whether the active config is a rendered product config, rather than on whether a file sits beside it?

Verification, at 5146f0a against an extracted head tree, with the imported raven confirmed to come from that tree:

  • CI: every check passes at this head, including python lint, pre-commit diff and ui rpc contract, which failed at a525fcf.
  • pytest tests/test_ops_connection_add.py tests/test_ops_connections.py tests/test_cli_ops_connection_commands.py tests/test_shell_machine_channel.py tests/test_agents_oncall_flow_connections.py tests/test_agents_oncall_flow_watcher.py tests/test_agents_oncall_launcher.py tests/test_agents_code_launcher.py tests/test_tool_capabilities.py tests/test_settings_tool_groups.py tests/test_agent_loop_tool_search.py tests/test_tool_registry_execute.py: 368 passed, 1 skipped. The skip is test_shell_machine_channel.py:757 on this machine's OpenSSH 8.9, as the commit intends; the same test failed here at 257acb1.
  • Mutation checks: removing ExecTool.to_schema fails test_the_machine_lane_appears_once_a_first_machine_is_added, and removing the toolGroups.json entry fails test_tool_groups_cover_the_default_tools.
  • lint-imports at a525fcf: 10 contracts kept, 0 broken; 5146f0a changes no imports.

Checked and not reported: ExecTool.to_schema re-reads the registry twice per model call (description and parameters); the array changes only when the registry goes from empty to non-empty, or becomes unreadable, so the prompt-cache prefix moves once at that point. ops_declare's connection parameter is a static free-form string, so nothing on the on-call side freezes the same way.

… exists

Review question on #690 (2026-09-24): beside-the-config-first could not tell a
stale copy beside a sub-agent's rendered config from the owner's list beside a
`--config` host's config, and it broke the first the way home-first broke the
second -- a copy read first hid every row added to the home since, and hid the
coding agent's own adds from the on-call agent.

`store_path()` now keys on the process role: a sub-agent (the host launched it
with `RAVEN_SUBAGENT`) reads the owner's home whenever the home holds a
registry, and its copy only when it does not (an install older than the home
registry); any other instance, the `--config` host included, reads the list
beside its config first, then the home. The on-call launcher's
`connections_registry()` goes back to home-first for the same reason.

Tests: the sub-agent fixture now carries `RAVEN_SUBAGENT`; the `--config` case
runs as a host; two new cases pin both reproductions (a stale copy beside a
sub-agent's config loses to the home, and one sub-agent's add reaches another
whose state directory holds a copy). Both fail with the role check removed.

Co-authored-by: Claude (claude-opus-5-5) <noreply@anthropic.com>
@silverLXT

Copy link
Copy Markdown
Contributor Author

On the lookup question (d7c5220): yes, keyed on the process role now, as you suggested.

  • A sub-agent (launched by the host with RAVEN_SUBAGENT, via subagent_role_env) reads the owner's home whenever the home holds a registry. A file beside its rendered config can only be a copy, so it is read only when the home has none -- an install older than the home registry.
  • Any other instance, the --config host included, reads the list beside its config first, then the home.
  • connections_registry() in the on-call launcher is home-first again, for the same reason: its state root holds only copies.

Both of your reproductions are pinned and fail with the role check removed:

  • (b) test_a_sub_agent_reads_the_owners_home_over_a_stale_copy_beside_its_config -- copy [conn_cpu_32c] beside the config, home [conn_cpu_32c, conn_added_since]: the sub-agent reads the home. On-call has the twin test_the_owners_home_wins_over_an_own_copy, which replaces the one-round-old test asserting the opposite.
  • (a) test_what_one_sub_agent_adds_another_reads -- two state directories, the writer's empty and the reader's holding a copy; the coding agent's add is visible to the other sub-agent.

The --config case from the previous round now runs as a host process (RAVEN_SUBAGENT unset) and still keeps /x/connections.json. raven.ops importing raven.agent.subagent.role keeps all 10 import contracts (role.py depends only on os). On this machine the six stale copies you described exist for real (hand-copied into subagent_sessions/raven-code/acp/ on 09-15..17, currently identical to their homes), which is the case (b) guards.

Locally: 658 passed across the connection, launcher, tool-face, settings, tool-search, registry and sub-agent suites; ruff check and format clean.

@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; suggestions only, and they are marked inline.

The role-sensitive lookup resolves the stale-copy versus custom-config ambiguity correctly, and the writer/read-through and launcher cases are covered without weakening prior tests. I rechecked the new delta against the full reviewed PR, repository rules and Machine terminology, callers/history, backward compatibility, and the one-reader/one-writer architecture. The only remaining item is the ordinary ops-command import-cost regression marked inline; it does not prevent merging.

Verification: uv run pytest tests/test_ops_connections.py tests/test_ops_connection_add.py tests/test_agents_oncall_launcher.py tests/test_agents_code_launcher.py tests/test_subagent_host_env.py tests/test_agent_loop_subagent_role.py tests/test_shell_machine_channel.py tests/test_settings_tool_groups.py -x (262 passed).

Comment thread raven/ops/connections.py Outdated
store_path() imported raven.agent.subagent.role, which made raven.ops and
raven.agent import each other and pushed the mutual-pair count to 14, over
the ceiling of 13. The role variable, its reader and its writer now live in
raven.home, which every shelf may import; raven.agent.subagent.role
re-exports them so existing callers are unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

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

The new delta fixes the only outstanding suggestion without changing the role-sensitive registry behavior: the role flag now lives at the lightweight raven.home boundary, while raven.agent.subagent.role preserves its public imports. A clean store_path() call no longer loads the eager sub-agent/provider graph.

I reviewed the delta against the full previously reviewed PR, repository rules and Machine terminology, relevant callers/history, backward compatibility, test integrity, and the one-reader/one-writer architecture. Verification: uv run pytest tests/test_ops_connections.py tests/test_ops_connection_add.py tests/test_agents_oncall_launcher.py tests/test_agents_code_launcher.py tests/test_subagent_host_env.py tests/test_agent_loop_subagent_role.py tests/test_shell_machine_channel.py tests/test_settings_tool_groups.py -x (262 passed).

@LivXue
LivXue merged commit afa469d into main Sep 24, 2026
25 checks passed
@LivXue
LivXue deleted the refactor/connection_registry_one_copy branch September 24, 2026 10:36
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