Skip to content

Move worker controls into an agent-owned registry - #653

Open
jamiepine wants to merge 2 commits into
jamiepine/background-result-relay-deliveryfrom
jamiepine/agent-worker-registry
Open

Move worker controls into an agent-owned registry#653
jamiepine wants to merge 2 commits into
jamiepine/background-result-relay-deliveryfrom
jamiepine/agent-worker-registry

Conversation

@jamiepine

Copy link
Copy Markdown
Member

Workers now belong to the agent, not the channel that spawned them. ProcessControlRegistry grows from channel cancellation into the single live control plane for workers, which fixes the split that let resident autonomy see retained workers in SQLite while route only searched the current channel's in-memory maps.

Stack:

main
└── #649 jamiepine/opencode-worker-context
    └── #650 jamiepine/resident-autonomy-channel
        └── #651 jamiepine/autonomy-toggle-commands
            └── #652 jamiepine/background-result-relay-delivery
                └── this PR

Design doc: docs/design-docs/agent-worker-registry.md.

What changed

ChannelState loses worker_handles, worker_inputs, worker_injections, reserved_tasks, and active_workers. A channel ID is now spawn provenance and a default result destination, nothing more. Channel shutdown no longer touches worker lifecycle; agent shutdown drains the registry once.

Every registration gets a WorkerRegistrationId, and every callback that mutates live state carries it. A stale task's cleanup can't retire a restored worker that reused the same ID:

pub struct WorkerCallbackContext {
    pub worker_id: WorkerId,
    pub registration_id: WorkerRegistrationId,
}

Interactive operations get their own identity, so a result from an earlier follow-up can't settle a later one, and a follow-up's result goes to whoever asked rather than always to the origin channel:

pub struct WorkerOperationContext {
    pub operation_id: WorkerOperationId,
    pub requester: WorkerRequester,
    pub result_target: WorkerResultTarget,
    pub autonomy_run_id: Option<String>,
}

Spawning runs behind a closed start gate: reserve, construct, register, persist, bind the task, then open the gate. Cancellation arriving mid-sequence either prevents the row entirely or commits a cancelled outcome, and the worker future is never polled on a failure path. Task binding rolls back at the revision the bind produced.

Retained idle builtin and OpenCode workers restore straight into the registry at startup instead of resurrecting their origin channels to hold controls.

Routing distinguishes the cases the agent needs to act on — idle takes a follow-up, running takes an injection, terminal reports its durable outcome, and a durable row without live controls reports unavailable rather than not found.

Worker APIs resolve an explicit agent ID; the cross-agent worker scan is gone. Physical channel deletion is rejected while nonterminal workers reference it.

Testing

Registry, dispatch, and start-gate races are covered: concurrent follow-ups claim an idle worker at most once, close-admission fences reservation promotion, cancellation racing completion commits one terminal outcome, stale callbacks can't mutate a replacement registration, and cancellation before the gate never polls the future.

  • cargo test --lib — 1402 passed
  • just preflight, just gate-pr — passed
  • bunx tsc --noEmit, frontend reconciliation tests — passed

Notes

No migration. Running-worker recovery across restart, a durable operation ledger, and the channel foreign-key contract that still cascades terminal worker history are separate work.

Route now resolves a registry miss against durable state instead of
reporting everything as not found. A terminal worker returns its outcome,
a nonterminal row without live controls returns unavailable, and only an
unknown ID is an error.

Cancellation carries a reason again. The cancel watch channel holds
Option<Arc<str>> rather than bool, so the supervisor records the
requester's wording on the terminal outcome.

Worker tool calls are counted by the hook against the registry entry.
Counting them in the API event forwarder made a control-plane value
depend on a lossy broadcast subscriber.

Channel status carries runtime_state and routable from the registry, so a
page load after a worker goes idle stops rendering it as running.

Also drops the unused release_worker_admission, replaces the
system/cortex admission scope strings with one constant, and returns a
typed ChannelDeletion instead of matching on an error string.
@coderabbitai

coderabbitai Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 23483a7e-8373-4f90-8db4-8047424c91b6

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

@jamiepine
jamiepine marked this pull request as ready for review August 18, 2026 00:55
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