Skip to content

feat(raven-oncall): let the on-call instance add the machine the owner describes - #545

Merged
0xKT merged 1 commit into
mainfrom
feat/oncall_connection_add
Sep 20, 2026
Merged

0xKT merged 1 commit into
mainfrom
feat/oncall_connection_add

Conversation

@silverLXT

Copy link
Copy Markdown
Contributor

Summary

An on-call instance that finds its connection registry empty asks the owner
for the machine. Until 2026-09-06 the host consumed that answer: a check before
every spawn read the registry and, when it held nothing, ran
raven ops connection add --non-interactive from what the owner had typed in
conversation. That check went with the pre-dispatch registry gate, and the
plugin's own reply kept only half of the exchange -- it asked the owner to run
the command themselves in a terminal. Measured 2026-09-20: the owner answered
the question in the TUI and nothing wrote the row.

The write now belongs to the instance that needs the machine. A new tool,
ops_connection_add, takes the owner's answer and files the row through
functions in the plugin's own registry module (probe, write,
write_ssh_alias, mirroring the CLI the way the reader already does). The
machine is reached before anything is written; its cores, memory and devices
are read off it rather than typed. All of it is in-process function calls --
no raven ops connection add child process, no venv path to find.

The ask is cut to what only the owner knows:

  • required: what they call the machine, whether it is this computer or one
    reached over ssh, and an address for the latter;
  • port, username and key path are taken when given. Left out, ssh -G
    resolves them the way the owner's own terminal would, and the keys it names
    are probed in turn, keeping the one that connects. A username ssh cannot
    resolve is refused, not guessed;
  • installed software, work directories and the budget unit are optional;
    concurrency is kept only on a row that reports no cores or devices.

Only the on-call plugin (agents/raven-oncall/plugins/oncall-flow), its
prompt, the heat2d demo walk-through and tests change. No DAG, host or
dispatch code is touched.

Type

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

Verification

  • pytest tests/test_agents_oncall_flow_*.py tests/test_ops_connections.py tests/test_cli_ops_connection_commands.py -q
    -- 789 passed. Four new cases in tests/test_agents_oncall_flow_connection_add.py:
    a reached machine is written, listed by describe() and given an ssh alias;
    an unreachable one writes nothing; an ssh row with no address is refused
    before any probe; a port/user/key the owner left out come from the
    ssh -G resolution and the key that actually connected is the one stored.

  • ruff check and ruff format --check on the touched files -- clean;
    pre-commit run --files <touched> -- all hooks passed.

  • Unmocked, against a temporary registry file: transport=local on this
    laptop reached it and read 12 cores; transport=ssh with only host, port
    and user given reached the 32-core box with the second of ssh's default
    keys and read 32 cores / 232 GB; the same call with the user omitted was
    refused (Permission denied) and wrote nothing.

  • Relevant tests pass locally

  • Relevant lint / type checks pass locally

  • User-facing docs or screenshots are updated when needed

Risk

  • Security impact considered -- the key path is stored, never the key;
    the model is told not to invent an address, port or key, and a row lands
    only after the machine answered with that exact way in. The tool writes
    one managed Host <id> block into ~/.ssh/config, the same block the CLI
    writes today.
  • Backward compatibility considered -- the registry file shape is
    unchanged; existing rows are not rewritten; a file with entries the reader
    skips is refused rather than rewritten without them.
  • Rollback path is clear for risky changes -- remove the manifest row and
    the tool module; the reader-side functions are unused without them.

Related Issues

N/A

@silverLXT
silverLXT force-pushed the feat/oncall_connection_add branch from 2f68ced to 73725ad Compare September 20, 2026 08:28

@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: isolate candidate SSH keys and restore the on-call launcher parity contracts.

Two blocking findings are marked inline.

I covered the repository rules and canonical Machine terminology, the full diff, surrounding transport/registry/tool-factory callers, relevant history and the CLI parity implementation, backward compatibility for existing registry rows, test changes (no existing assertions were weakened), and plugin wiring/architecture.

Verification:

  • uv run pytest -q tests/test_agents_oncall_flow_connection_add.py tests/test_agents_oncall_flow_connections.py tests/test_agents_oncall_flow_tools.py tests/test_agents_oncall_flow_watcher.py tests/test_agents_oncall_launcher.py: 63 passed, 2 failed.
  • GitHub's unit shard reports the same two failures.
  • git diff --check github/main...HEAD: passed.
  • scripts/check_source_language.py github/main..HEAD: passed.
  • scripts/check_large_files.py github/main..HEAD: passed.

Comment thread agents/raven-oncall/plugins/oncall-flow/raven-plugin.toml

@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: both prior blockers remain, and the frozen A-side fixture must be restored.

The only delta updates the vendored-fork prompt snapshot. It leaves the candidate-key probe unchanged and leaves the B-side visible-tool contract stale; the focused suite is still red. The new inline finding explains why changing the frozen oracle is not a valid parity update.

I rechecked the full diff, repository rules and canonical Machine terminology, the changed fixture and its documented role, the transport/registry callers, relevant history, backward compatibility, plugin architecture/wiring, and whether tests were weakened to appear green.

Verification:

  • uv run pytest -q tests/test_agents_oncall_flow_connection_add.py tests/test_agents_oncall_flow_connections.py tests/test_agents_oncall_flow_tools.py tests/test_agents_oncall_flow_watcher.py tests/test_agents_oncall_launcher.py: 64 passed, 1 failed (test_the_products_tool_face_equals_the_forks_config_intent).
  • GitHub's unit shard reports the same failure.
  • git diff --check github/main...HEAD: passed.
  • scripts/check_source_language.py github/main..HEAD: passed.
  • scripts/check_large_files.py github/main..HEAD: passed.

@silverLXT
silverLXT force-pushed the feat/oncall_connection_add branch from 73725ad to df6f203 Compare September 20, 2026 08:40

@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: isolate candidate probes from every other configured SSH identity.

The parity and frozen-oracle findings are fixed and their threads are resolved. The remaining SSH fix excludes agent identities but still permits all default and ssh_config IdentityFile entries, so a different configured key can authenticate and be credited to the current candidate; I added the measured ssh -G result to the existing thread. No additional findings.

I rechecked the full diff, repository rules and canonical Machine terminology, changed transport/tool callers, relevant history, backward compatibility, plugin architecture, and the revised tests for weakened assertions. The B-side parity exceptions remain explicit and guarded.

Verification:

  • uv run pytest -q tests/test_agents_oncall_flow_connection_add.py tests/test_agents_oncall_flow_connections.py tests/test_agents_oncall_flow_tools.py tests/test_agents_oncall_flow_watcher.py tests/test_agents_oncall_launcher.py: 67 passed.
  • git diff --check github/main...HEAD: passed.
  • scripts/check_source_language.py github/main..HEAD: passed.
  • scripts/check_large_files.py github/main..HEAD: passed.

…r describes

An on-call instance that finds its connection registry empty asks the owner
for the machine. Until 2026-09-06 the host consumed that answer: a check
before every spawn read the registry and, when it held nothing, ran
`raven ops connection add --non-interactive` from what the owner had typed
in conversation. That check went with the pre-dispatch registry gate, and the
plugin's own reply kept only half of the exchange -- it asked the owner to run
the command themselves in a terminal. Measured 2026-09-20: the owner answered
the question in the TUI and nothing wrote the row.

The write now belongs to the instance that needs the machine. A new tool,
`ops_connection_add`, takes the owner's answer and files the row through
functions in the plugin's own registry module (`probe`, `write`,
`write_ssh_alias`, mirroring the CLI as the reader already does): the machine
is reached before anything is written, and its cores, memory and devices are
read off it rather than typed.

The ask is cut to what only the owner knows. Required: what they call the
machine, whether it is this computer or one reached over ssh, and an address
for the latter. Port, username and key path are taken when given; left out,
`ssh -G` resolves them the way the owner's own terminal would and the keys it
names are probed in turn, keeping the one that connects. A username ssh cannot
resolve is refused, not guessed. Installed software, work directories and the
budget unit are optional; concurrency is kept only on a row that reports no
cores or devices, where admission has nothing else to go on.

A key this tool picks has to be shown to be the one that opened the session,
and that takes reading no ssh config at all. `-i` is a preference rather than
a restriction, and `IdentitiesOnly=yes` is defined as excluding what an agent
or a provider adds, not what the config names. Measured with OpenSSH 9.9p2
against a config carrying two `IdentityFile` lines: `-i cand` with
IdentitiesOnly and no agent still resolves to three identities. A candidate is
therefore probed with `-F /dev/null` alongside both options, through an opt-in
parameter on the ssh runner; credited otherwise, the registry would hold a path
that merely looked verified and would stop working the day that agent or config
changed. The cost is that an alias or a jump host is not in play while
candidates are tried, so a machine reached that way is refused with a line
asking for the key path, and a path the owner named is probed the way a job
will reach the machine, agent included.

The empty-registry reply, the tool prompt and the heat2d demo walk-through say
the new shape. Both on-call parity contracts are updated on the product side:
the visible-tool set names the new tool, and the prompt guard keeps byte
equality with the frozen fork through an explicit list of this side's
additions. The frozen A-side fixture is a record and is left untouched.

Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
@silverLXT
silverLXT force-pushed the feat/oncall_connection_add branch from df6f203 to 84d46ec Compare September 20, 2026 08:51

@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 remaining SSH finding is fixed: the candidate probe now reads no SSH config, disables agent identities, and uses IdentitiesOnly=yes; OpenSSH's effective configuration contains only the candidate key. The regression test measures that behavior directly. All threads I opened are resolved, and I found no new issue in this revision.

I covered the new delta and full diff, repository rules and canonical Machine terminology, transport/tool callers and relevant history, backward compatibility, plugin architecture, and the revised test behavior.

Verification:

  • uv run pytest -q tests/test_agents_oncall_flow_connection_add.py tests/test_agents_oncall_flow_connections.py tests/test_agents_oncall_flow_tools.py tests/test_agents_oncall_flow_watcher.py tests/test_agents_oncall_launcher.py: 66 passed.
  • git diff --check github/main...HEAD: passed.
  • scripts/check_source_language.py github/main..HEAD: passed.
  • scripts/check_large_files.py github/main..HEAD: passed.

@silverLXT
silverLXT requested a review from 0xKT September 20, 2026 08:55
@0xKT
0xKT merged commit e52d1f6 into main Sep 20, 2026
25 checks passed
@0xKT
0xKT deleted the feat/oncall_connection_add branch September 20, 2026 09:18
LivXue pushed a commit that referenced this pull request Sep 24, 2026
… can add a machine (#690)

## 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` (#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 #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.
- #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)

---------

Co-authored-by: xiaotian.luo <xiaotian.luo@thetahealth.ai>
Co-authored-by: Claude (claude-opus-5-5) <noreply@anthropic.com>
LivXue pushed a commit that referenced this pull request Oct 12, 2026
…r describes (#545)

## Summary

An on-call instance that finds its connection registry empty asks the
owner
for the machine. Until 2026-09-06 the host consumed that answer: a check
before
every spawn read the registry and, when it held nothing, ran
`raven ops connection add --non-interactive` from what the owner had
typed in
conversation. That check went with the pre-dispatch registry gate, and
the
plugin's own reply kept only half of the exchange -- it asked the owner
to run
the command themselves in a terminal. Measured 2026-09-20: the owner
answered
the question in the TUI and nothing wrote the row.

The write now belongs to the instance that needs the machine. A new
tool,
`ops_connection_add`, takes the owner's answer and files the row through
functions in the plugin's own registry module (`probe`, `write`,
`write_ssh_alias`, mirroring the CLI the way the reader already does).
The
machine is reached before anything is written; its cores, memory and
devices
are read off it rather than typed. All of it is in-process function
calls --
no `raven ops connection add` child process, no venv path to find.

The ask is cut to what only the owner knows:

- required: what they call the machine, whether it is this computer or
one
  reached over ssh, and an address for the latter;
- port, username and key path are taken when given. Left out, `ssh -G`
resolves them the way the owner's own terminal would, and the keys it
names
are probed in turn, keeping the one that connects. A username ssh cannot
  resolve is refused, not guessed;
- installed software, work directories and the budget unit are optional;
  concurrency is kept only on a row that reports no cores or devices.

Only the on-call plugin (`agents/raven-oncall/plugins/oncall-flow`), its
prompt, the heat2d demo walk-through and tests change. No DAG, host or
dispatch code is touched.

## Type

- [ ] Fix
- [x] Feature
- [ ] Docs
- [ ] CI / tooling
- [ ] Refactor
- [ ] Other

## Verification

- `pytest tests/test_agents_oncall_flow_*.py
tests/test_ops_connections.py tests/test_cli_ops_connection_commands.py
-q`
-- 789 passed. Four new cases in
`tests/test_agents_oncall_flow_connection_add.py`:
a reached machine is written, listed by `describe()` and given an ssh
alias;
an unreachable one writes nothing; an ssh row with no address is refused
  before any probe; a port/user/key the owner left out come from the
`ssh -G` resolution and the key that actually connected is the one
stored.
- `ruff check` and `ruff format --check` on the touched files -- clean;
  `pre-commit run --files <touched>` -- all hooks passed.
- Unmocked, against a temporary registry file: `transport=local` on this
laptop reached it and read 12 cores; `transport=ssh` with only host,
port
and user given reached the 32-core box with the second of ssh's default
keys and read 32 cores / 232 GB; the same call with the user omitted was
  refused (`Permission denied`) and wrote nothing.

- [x] Relevant tests pass locally
- [x] Relevant lint / type checks pass locally
- [x] User-facing docs or screenshots are updated when needed

## Risk

- [x] Security impact considered -- the key path is stored, never the
key;
the model is told not to invent an address, port or key, and a row lands
only after the machine answered with that exact way in. The tool writes
one managed `Host <id>` block into `~/.ssh/config`, the same block the
CLI
  writes today.
- [x] Backward compatibility considered -- the registry file shape is
unchanged; existing rows are not rewritten; a file with entries the
reader
  skips is refused rather than rewritten without them.
- [x] Rollback path is clear for risky changes -- remove the manifest
row and
  the tool module; the reader-side functions are unused without them.

## Related Issues

N/A

Co-authored-by: xiaotian.luo <xiaotian.luo@thetahealth.ai>
Co-authored-by: Claude (claude-opus-5) <noreply@anthropic.com>
LivXue pushed a commit that referenced this pull request Oct 12, 2026
… can add a machine (#690)

## 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` (#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 #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.
- #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)

---------

Co-authored-by: xiaotian.luo <xiaotian.luo@thetahealth.ai>
Co-authored-by: Claude (claude-opus-5-5) <noreply@anthropic.com>
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