Skip to content

feat(j5): a steer or restart reaches an agent on a peer server - #563

Open
Jacksondr5 wants to merge 2 commits into
j5code/spawn-519from
j5code/remote-steer-519
Open

Jacksondr5 wants to merge 2 commits into
j5code/spawn-519from
j5code/remote-steer-519

Conversation

@Jacksondr5

@Jacksondr5 Jacksondr5 commented Oct 12, 2026 •

Copy link
Copy Markdown
Owner

Problem

j5_send_message can ask for a message to steer or restart the receiver's turn, but only for an agent on the same server (#553). Jackson decided on 2026-10-11 that it reaches agents on peer servers too (#519, the decision record).

This is the third PR of a stack: #553, then #557, then this one. With it, everything decided in #519 is built, so it closes the issue once the stack merges.

Closes #519

What changed

On the wire. A peer delivery gains one optional field, landing: the mode (steer or restart) and the broadest permission and interaction modes the sender could touch when it sent. It is an additive change, so the peer protocol version stays 2 and an updated server keeps working with one that is not.

On the receiving server. Only that server knows its agent's modes, so it applies upstream's rule when the message arrives: an agent that runs above the sender's limit refuses the delivery with a new final answer, landing_refused. An accepted one keeps the request on its received record, and local delivery then lands it exactly as a local steer or restart does, including upstream's second check at dispatch.

On the sending server.

  • The sender is told about a refusal by the existing not-delivered notice, with the reason and the way out: send it again without mode.
  • A server now advertises a landing capability. A peer that does not advertise it would drop the field and queue the message without saying so, so the tool refuses the send in its result and names the server to update.
  • A peer's capabilities were recorded when it was added and on a poll, but not for a peer this server calls directly. The roster answer now carries them and each roster read records them, so a peer updated after it was added is believed. The tool reads the roster when it resolves a remote recipient, so the check uses a fresh answer.

Attachments still reach an agent on this server only.

Things a reviewer should know

  • The receiving server reads modes from upstream's orchestration_v2_projection_threads table, by SQL. The inbound service has no orchestrator dependency today and I did not add one for two columns. Other J5 reads use that table the same way.
  • An agent the receiving server cannot find modes for is not refused at receipt. Upstream's check at dispatch still applies to it.
  • The wording version in envelopes.v1.json goes from 24 to 25 for the send tool's description.
  • The permission rule itself was confirmed by Jackson on 2026-10-12, over the 2026-09-14 and 2026-09-16 rulings that permissions do not gate these actions between Peer Agents: J5 follows upstream on permissions and carries no divergence there. The tool definition records it.

Review so far

An Astra peer reviewed the first commit and found two defects, fixed in the second. The receiving server read the agent's modes from the frozen pre-V2 thread table, so a current thread was never refused; my test hid it by creating that table itself, and it now runs upstream's migrations and seeds the real one. And a peer that rolled back to an older build kept the capability its newer build had advertised; a roster answer that names no capabilities now clears it.

Tests

  • PeerRoundTrip.test.ts, two servers with real ledgers and delivery workers: a steer crosses with its limit and reaches the receiving server's transport as a steer; a restart for an agent raised above the limit is refused, and nothing is recorded or delivered there. The same test pins the HTTP answer for the refusal and the notice wording.
  • handlers.test.ts: the tool refuses a steer to a peer that does not advertise the capability, and sends the caller's limit to one that does.
  • PeerDirectory.test.ts: a roster read records what the peer says it can do, and forgets it when the answer no longer says.
  • PeerHttp.test.ts and PeerStoreService.test.ts: the hello, the roster answer and the poll answer advertise the capability.

No unit test drives a steer through the polling link mode. A polled delivery is built by the same buildPeerDeliveryBody and refused through the same peerDeliveryRefusal, and the polling steer was verified live, below.

Live testing

Verdict: pass. A tester agent ran the whole stack (#553, #557, #563) on a live-test server with real agents on Claude Haiku and GPT Luna, following the j5-live-test skill. The full matrix ran on 6b5d4c25a7, a focused retest on 19016b3f48 after one wording fix, and a last check of the listing on e4900a23f7, the head of the stack. Report: test-reports/j5code-remote-steer-519.md.

What ran live for this PR, on two peered test servers on one machine:

  • Direct link: an agent on Local listed Remote's agents by naming the server, sent one a plain message, and steered one that was mid-turn. The steer entered the remote agent's running turn.
  • The refusal: a sender in a narrower mode steered a remote agent in Full access. The send was accepted locally, Remote refused it, and three seconds later the sender got the platform notice: "Remote refused the steer or restart: the recipient runs with broader permissions than you do. Send it again without mode, to wait its turn." The remote agent never saw the message.
  • Polling link: with Remote polling Local, a steer entered the remote agent's running turn.
  • Restart across servers: in the wording retest, a restart from Local interrupted and restarted the remote agent's turn, from both Haiku and Luna.

Not exercised live:

  • A peer that does not advertise the capability. Both test servers run the same build. The tool's refusal for an older peer is covered by a unit test.
  • A real second machine, a relay or a tunnel, and an unreachable peer are outside what the rig can do.

Nothing needs testing by hand.

Upstream impact

No upstream-owned file is edited. FORK.md case 26 records the new path. The read of upstream's thread projection table is noted above.

Checklist

  • One concern: the description has no "also"
  • Tests cover the changed behavior (backend changes ship with focused tests). Two gaps are named above.
  • UI changes: before/after screenshots above, and a video for motion or interaction. Not applicable: no UI changes.
  • Upstream-owned files: each one is recorded in FORK.md (case text and file-table row) in this PR
  • Upstream product: any change to what upstream's product does has a human decision linked above and a register entry in docs/j5/product/upstream.md
  • Surfaces: entry points, clients, providers, contracts, reverse states, connection modes (see AGENTS.md)
  • Live testing: the section above says what ran live and what did not
  • Docs: definitions under docs/j5/product/ and user docs rewritten where this changes them

Surfaces that applied:

  • Contracts: PeerDeliveryRequest, PeerCapabilities and PeerRosterResponse in packages/contracts, each with an optional addition.
  • Connection modes: both peer link modes carry the field. A mixed pair of updated and older servers is the case the capability exists for.
  • Agents: the send tool's description says a steer or restart reaches a peer server, and what a refusal looks like.

Surfaces that did not apply: no client or settings change, and no provider adapter.

Built by Claude Opus 5.5 in Claude Code.

🤖 Generated with Claude Code

@vercel

vercel Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
j5-code Ready Ready Preview Oct 12, 2026 7:32am UTC

Request Review

Jacksondr5 and others added 2 commits October 12, 2026 03:25
j5_send_message's steer and restart reached agents on this server only. They
now cross to a peer server (#519):

- The request and the sender's permission limit travel as `landing` on the
  peer delivery, two optional fields, so the protocol version stays 2.
- The receiving server applies upstream's rule, since only it knows its
  agent's modes: an agent above the limit refuses the delivery with
  `landing_refused`, and the sender gets the not-delivered notice.
- A server advertises a `landing` capability. An older peer would queue the
  message and say nothing, so the tool refuses the send in its result.
- A roster answer now carries capabilities, so a peer updated after it was
  added is believed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ent modes, and a rolled-back peer loses its capability

Review found that the permission check on a received steer or restart read
the frozen pre-V2 thread table, so a current thread was never refused. It
reads upstream's V2 projection now, and the test seeds upstream's real schema.

A peer that went back to an older build kept the landing capability its newer
build had advertised. A roster answer that names no capabilities clears it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch was successfully deployed

1 active deployment
Preview — e4900a23 Deployed Oct 12, 2026 by vercel[bot]
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.

1 participant