Skip to content

fix(broker): keep respawned agents and held initial tasks receiving - #1367

Merged
willwashburn merged 4 commits into
mainfrom
fix/respawn-delivery-cursor
Jul 29, 2026
Merged

fix(broker): keep respawned agents and held initial tasks receiving#1367
willwashburn merged 4 commits into
mainfrom
fix/respawn-delivery-cursor

Conversation

@willwashburn

@willwashburn willwashburn commented Jul 27, 2026

Copy link
Copy Markdown
Member

Behavior

An agent released and respawned under the same name receives relay messages. A PTY worker that becomes ready while its inbound delivery is held runs the initial task from its spawn.

Respawn delivery

Release drops the agent's delivery cursor. The engine reuses the agent record across a respawn and does not report a cumulative position (it does not negotiate relay:delivery-cursor-v1), so nothing re-seeds the cursor while the engine's per-agent sequence keeps counting. The next message therefore arrived past the start of the sequence, FleetDeliveryBook::observe classified it as a gap, and plan_fleet_delivery maps a gap to Acknowledge — acked without being surfaced. The message was discarded and the engine never retried, so the agent stayed deaf for the rest of its life. Only the seq == 1 case was accepted without a cursor, which is why first spawns worked and every respawn did not.

An identity confirmed by agent.register now adopts the observed sequence as its starting position, and commit_received seeds the cursor to match so it advances from there. A provisional binding keeps the previous gap response: an unconfirmed second identity still cannot claim a live name mid-sequence.

Adoption is keyed on an explicit AgentDeliveryCursor::has_sequenced_position, not on whether a cursor exists. seq:0 fan-out (reactions, read receipts, action results) creates a cursor without establishing any position, so keying on existence would let one fan-out frame landing before the respawned agent's next real message pin the cursor at zero — and the resumed frame would read as a gap again. The flag is set by seed_cursor or by the first sequenced delivery; fan-out never sets it, and stays deduped by msg_id in the meantime.

Held initial task

The initial task bypasses the delivery-mode pending queue but still lands in the PTY worker's injection queue, where a replayed interactive hold freezes pops. A worker that restarts while explicitly held reaches worker_ready in that state, so its spawn's task sat in the queue until the hold lifted. The task is now released through the hold with a flush_injections exemption scoped to that one delivery; later relay messages keep parking as before.

The exemption has to be task-specific rather than a blanket flush. A supervised restart retains unacknowledged deliveries, so a relay message retried into the new worker before worker_ready would otherwise spend the same allowance and splice into the human's session. flush_injections now carries an optional event_id: with it set the worker records only that id and next_injection_index reaches past anything queued in front of it, which stays parked (FIFO order among the parked entries is preserved once the hold lifts). A requeue after a failed write returns a targeted exemption to the event-id set rather than the counter, a hold boundary clears both, and POST /api/spawned/{name}/flush sends no event_id and keeps the blanket behavior unchanged.

Verification

  • cargo test -p agent-relay-broker --lib — 843 passed
  • cargo clippy -- -D warnings — clean; cargo fmt --all --check — clean
  • npm run typecheck, npm run lint (0 errors), npm run format:check — clean
  • Live against a real broker and the hosted engine: spawn → ping → release → respawn under the same name → ping. The respawned agent received the message and replied; the same sequence produced silence beforehand.

Note: runtime::tests::delivery_retry_transient_blip_emits_failed_event_for_present_worker is timing-sensitive under a loaded machine and flaked once during this work. It passes on main and on this branch; it is unrelated to this change.

Review in cubic

An agent released and respawned under the same name stopped receiving relay
messages. Release drops the agent's delivery cursor, and the engine reuses the
agent record without reporting a cumulative position, so the next message
arrived past the start of the sequence, was classified as a gap, and was
acknowledged without being surfaced — discarding it and stopping the engine
from retrying.

An identity confirmed by `agent.register` now adopts the observed sequence as
its cursor instead of treating a missing position as a gap. A provisional
binding keeps the previous response so an unconfirmed second identity cannot
claim a live name mid-sequence.

A PTY worker that becomes ready while its inbound delivery is held also now
releases the initial task from its spawn through the hold, instead of leaving
it parked in the injection queue until the hold lifts.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@willwashburn
willwashburn requested a review from khaliqgant as a code owner July 27, 2026 16:18
@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d0a0fbfa-2aca-4f17-94cc-174cb508e93d

📥 Commits

Reviewing files that changed from the base of the PR and between 95d6bde and 9529071.

📒 Files selected for processing (1)
  • crates/broker/src/pty_worker.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/broker/src/pty_worker.rs

📝 Walkthrough

Walkthrough

The broker now preserves delivery continuity for authoritative agent respawns and distinguishes seq:0 fan-out from established sequence positions. PTY workers support event-targeted injection flushing, allowing initial tasks to start while interactive holds remain active.

Changes

Broker delivery and worker startup

Layer / File(s) Summary
Authoritative respawn cursor recovery
crates/broker/src/node_control.rs
Authoritative identities adopt continuing engine sequences, preserve cursor state after seq:0 fan-out, and test duplicate, contiguous, and gap behavior.
Targeted injection flush protocol
crates/broker/src/protocol.rs, packages/harness-driver/src/protocol.ts
flush_injections optionally carries an event_id, with serialization tests for omitted and targeted identifiers.
PTY targeted hold exemptions
crates/broker/src/pty_worker.rs
Queued injections can be selected by event ID during interactive holds, while failed requeues restore targeted or blanket exemptions correctly.
PTY initial-task startup wiring
crates/broker/src/runtime/worker_events.rs, CHANGELOG.md
Replayed PTY holds trigger an event-targeted flush for the initial task, and the behavior is documented in the changelog.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers: khaliqgant

Sequence Diagram(s)

sequenceDiagram
  participant BrokerRuntime
  participant PTYWorker
  participant InjectionQueue
  BrokerRuntime->>PTYWorker: worker_ready with replayed manual hold
  BrokerRuntime->>PTYWorker: flush_injections(event_id)
  PTYWorker->>InjectionQueue: find matching queued delivery
  InjectionQueue-->>PTYWorker: start initial task
Loading

Poem

A rabbit guards each sequenced stream,
Respawned messages flow downstream.
A held task gets a targeted cue,
Then hops through the PTY queue.
Gaps and duplicates settle in place—
While carrots mark the broker’s trace.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is detailed, but it does not follow the required Summary, Test Plan, and Screenshots template sections. Add the required ## Summary, ## Test Plan with checklist items, and ## Screenshots sections, or clearly mark non-applicable items.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is specific and matches the main fix for respawned agents and held initial tasks.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/respawn-delivery-cursor

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: efb9309ac4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/broker/src/node_control.rs Outdated
Comment thread crates/broker/src/runtime/worker_events.rs

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Open in Devin Review

claude added 2 commits July 29, 2026 14:56
Both sides added entries under `### Fixed` in CHANGELOG.md; keep main's
telemetry/identity fixes alongside this branch's respawn-delivery and
held initial-task fixes.
Two P1s from review on the respawn fix.

Restart adoption survived only while the respawned identity had no
cursor at all. A `seq:0` fan-out frame — a reaction, a read receipt, an
`action.completed` result — creates a cursor without establishing any
position, so one landing before the agent's next real message put the
cursor at zero and the resumed frame (seq 43) read as a gap again:
acked, never surfaced, agent deaf. Adoption is now keyed on an explicit
`has_sequenced_position`, set by a seeded cursor or the first sequenced
delivery, instead of on the cursor's existence.

The `flush_injections` that releases a (re)spawn's initial task through
a replayed interactive hold granted an allowance for the worker's whole
queue. A supervised restart retains unacknowledged deliveries, so a
relay message retried into the new worker before `worker_ready` could
spend that allowance and splice into the human's session. The frame now
carries the initial task's `event_id` and the worker releases only that
delivery — popping it past anything queued in front, which stays parked.
A blanket flush (`POST /api/spawned/{name}/flush`) is unchanged.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/broker/src/pty_worker.rs`:
- Around line 968-982: In the blanket flush branch around active_injection,
preserve an existing targeted exemption instead of unconditionally clearing
inj.targeted_hold_exemption. Only mark the injection as blanket-exempt when it
is not already targeted-exempt, so restore_hold_exemption can reinsert the
delivery event_id into hold_exempt_event_ids if its write is requeued.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 5eca119e-bb7d-4acd-8036-c8bf39e4ce49

📥 Commits

Reviewing files that changed from the base of the PR and between efb9309 and 95d6bde.

📒 Files selected for processing (6)
  • CHANGELOG.md
  • crates/broker/src/node_control.rs
  • crates/broker/src/protocol.rs
  • crates/broker/src/pty_worker.rs
  • crates/broker/src/runtime/worker_events.rs
  • packages/harness-driver/src/protocol.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • CHANGELOG.md
  • crates/broker/src/runtime/worker_events.rs

Comment thread crates/broker/src/pty_worker.rs
… flush

A blanket `flush_injections` arriving while the initial task was in flight
downgraded its targeted exemption. If that injection's write then failed,
`restore_hold_exemption` returned the credit to the shared counter instead
of to the delivery's own event id, so it could be spent on whatever else
was queued — the failure mode the targeted exemption exists to prevent.
The blanket branch now only grants `hold_exempt` and leaves an existing
targeted exemption alone.
@willwashburn
willwashburn merged commit 1e80918 into main Jul 29, 2026
40 checks passed
@willwashburn
willwashburn deleted the fix/respawn-delivery-cursor branch July 29, 2026 16:17
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.

2 participants