Skip to content

[codex] add external worker execution - #407

Merged
delete-cloud merged 1 commit into
mainfrom
external-worker-execution
May 31, 2026
Merged

delete-cloud merged 1 commit into
mainfrom
external-worker-execution

Conversation

@delete-cloud

@delete-cloud delete-cloud commented May 31, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • add external_worker execution binding and server control-plane APIs for run request, claim, heartbeat, event upload, approval, and completion
  • add local CLI worker modes: remote local-run and remote worker
  • persist final worker tape entries/results back to the server and recover expired worker leases
  • document the execution control-plane decision in ADR-0051

Validation

  • uv run ruff check src/coding_agent/server/execution_binding.py src/coding_agent/server/binding_resolver.py src/coding_agent/server/schemas.py src/coding_agent/runtime_store.py src/coding_agent/server/session_manager.py src/coding_agent/server/http_server.py src/coding_agent/remote/worker.py src/coding_agent/cli/remote_commands.py tests/ui/test_http_server.py tests/cli/test_remote_client.py tests/coding_agent/test_pg_runtime_store.py
  • uv run ruff format --check src/coding_agent/server/execution_binding.py src/coding_agent/server/binding_resolver.py src/coding_agent/server/schemas.py src/coding_agent/runtime_store.py src/coding_agent/server/session_manager.py src/coding_agent/server/http_server.py src/coding_agent/remote/worker.py src/coding_agent/cli/remote_commands.py tests/ui/test_http_server.py tests/cli/test_remote_client.py tests/coding_agent/test_pg_runtime_store.py
  • uv run pytest tests/ui/test_http_server.py -k "external_worker or cancel_session_turn_returns_cancelling_for_active_turn or prompt_streaming_events" -v
  • uv run pytest tests/cli/test_remote_client.py -k "local_run or external_worker or remote_worker or remote_add_list_remove" -v
  • uv run pytest tests/coding_agent/test_pg_runtime_store.py -v
  • uv run pytest tests/coding_agent -k "pipeline_adapter" -v
  • uv run python -m coding_agent remote local-run --help
  • uv run python -m coding_agent remote worker --help

Summary by CodeRabbit

Release Notes

New Features

  • Added external worker execution mode for distributed task processing
  • New CLI commands for running workers locally (local-run) or continuously (worker)
  • New server endpoints enabling workers to claim tasks, send updates, and report completion

Documentation

  • Added architecture documentation for external worker execution design
  • Added task specification for external worker implementation

Tests

  • Added test coverage for external worker execution and command-line workflows

@coderabbitai

coderabbitai Bot commented May 31, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@delete-cloud, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 33 minutes and 30 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 464efac3-7970-4562-bf67-821acbb7119e

📥 Commits

Reviewing files that changed from the base of the PR and between 269c0d3 and 55e4a6b.

📒 Files selected for processing (13)
  • coding-agent/.opencode/prompts/tasks/external-worker-execution-task-packet.md
  • coding-agent/docs/adr/0051-external-worker-execution-control-plane.md
  • coding-agent/src/coding_agent/cli/remote_commands.py
  • coding-agent/src/coding_agent/remote/worker.py
  • coding-agent/src/coding_agent/runtime_store.py
  • coding-agent/src/coding_agent/server/binding_resolver.py
  • coding-agent/src/coding_agent/server/execution_binding.py
  • coding-agent/src/coding_agent/server/http_server.py
  • coding-agent/src/coding_agent/server/schemas.py
  • coding-agent/src/coding_agent/server/session_manager.py
  • coding-agent/tests/cli/test_remote_client.py
  • coding-agent/tests/coding_agent/test_pg_runtime_store.py
  • coding-agent/tests/ui/test_http_server.py
📝 Walkthrough

Walkthrough

This PR implements a complete external-worker execution mode that separates durable control-plane logic (session/run management on the server) from tool execution (running locally via CLI). It introduces an external_worker binding type, server-side run-claim/heartbeat/completion endpoints, and a local worker implementation that claims runs, executes agent turns, and reports results back to the server.

Changes

External Worker Execution Control Plane

Layer / File(s) Summary
Design and Architecture Documentation
coding-agent/.opencode/prompts/tasks/external-worker-execution-task-packet.md, coding-agent/docs/adr/0051-external-worker-execution-control-plane.md
Task packet and ADR-0051 define external-worker execution model, control-plane responsibilities, worker lifecycle, backward-compatibility (legacy local binding as server_local), and acceptance criteria for server/CLI/UI test coverage.
External Worker Binding Contract and Schemas
coding-agent/src/coding_agent/server/execution_binding.py, coding-agent/src/coding_agent/server/schemas.py
ExecutionBinding.kind extended to include external_worker; new ExternalWorkerBinding dataclass with executor_kind, optional worker_pool, workspace_ref, and provider_instance_id; HTTP request schemas for binding configuration and worker lifecycle (claim/heartbeat/events/complete/approval).
Server-Side Binding Recognition and Resolution
coding-agent/src/coding_agent/server/binding_resolver.py, coding-agent/src/coding_agent/server/http_server.py (imports & binding detection)
DefaultBindingResolver explicitly rejects ExternalWorkerBinding on server (raises ValueError); HTTP server detects external-worker bindings in binding factory and session prompt handler.
Runtime Store Persistence Layer
coding-agent/src/coding_agent/runtime_store.py
Atomic SQL claim operation with FOR UPDATE SKIP LOCKED selects eligible external-worker runs in requested/expired state, transitions them to claimed, and merges claim metadata into JSONB; public claim_external_worker_run method handles concurrency-safe claiming.
Session Manager External Worker Lifecycle
coding-agent/src/coding_agent/server/session_manager.py
Implements run request/claim/heartbeat/event/finalize operations with claim-token hashing and verification; metadata includes execution_binding_kind, executor, worker pool, and workspace ref; stale-lease recovery detects expired claims and marks runs reclaimable; session turn cancellation for external-worker runs updates run status to cancelling instead of task-cancel path.
HTTP Server Endpoints and SSE Prompt Stream
coding-agent/src/coding_agent/server/http_server.py (1766–2657)
/sessions/{id}/prompt routes external-worker sessions through dedicated SSE handler emitting RunRequested event; new endpoints: /worker/runs/claim (assigns run+lease), /worker/runs/{run_id}/heartbeat (renews lease), /worker/runs/{run_id}/events (ingest worker events, broadcast to session), /worker/runs/{run_id}/approval (approval delegation), /worker/runs/{run_id}/complete (finalize with status/tape, emit TurnEnd).
Local Worker Execution Engine
coding-agent/src/coding_agent/remote/worker.py
RemoteWorkerConsumer emits wire events and routes approval requests (auto or HTTP); orchestration functions: run_local_worker_once (create session, request run, claim, execute once), run_worker_loop (poll-claim-execute loop); helpers for session creation, SSE run-request parsing, run claiming, turn execution (agent pipeline + concurrent heartbeat), and JSON serialization (dataclasses, enums, datetimes).
CLI Entry Points
coding-agent/src/coding_agent/cli/remote_commands.py
remote local-run validates repo, resolves worker ID, invokes run_local_worker_once one time; remote worker validates repo, invokes run_worker_loop with --worker-id, --once, --poll-interval options; both exit with worker return code.
Comprehensive Test Coverage
coding-agent/tests/cli/test_remote_client.py, coding-agent/tests/coding_agent/test_pg_runtime_store.py, coding-agent/tests/ui/test_http_server.py
CLI tests verify run_local_worker_once/run_worker_loop called with correct args; runtime store tests validate atomic claiming with metadata merge; HTTP server tests cover prompt→run creation, claim/events/completion flows, approval routing, stale-lease recovery; FakeExternalWorkerRuntimeStore test helper implements in-memory run/event persistence.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • delete-cloud/Coding-Agent-Loop#75: Extends the execution-binding framework introduced in PR #75 by adding the new external_worker binding type and wiring it into server control-plane and local worker flows.

Poem

🐰 A worker hops outside the wall,
while server keeps the ledger tall,
claim a run, send back the news—
heartbeat, tape, and run-result clues!
No agent bloat on the local side,
just pure execution, tried and tried. 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.38% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title '[codex] add external worker execution' directly matches the main change: implementing external worker execution binding and related server/CLI functionality.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch external-worker-execution

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 and usage tips.

@delete-cloud
delete-cloud force-pushed the external-worker-execution branch from 269c0d3 to 96a1d27 Compare May 31, 2026 11:48

@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: 9

🤖 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 `@coding-agent/src/coding_agent/cli/remote_commands.py`:
- Around line 410-414: The --poll-interval option currently allows 0 or negative
values which will cause run_worker_loop() to busy-wait; add validation that
poll_interval > 0 before entering the loop. Fix by validating the option value
(either add a click callback on the existing --poll-interval click.option or
perform an explicit check in the command handler immediately before calling
run_worker_loop(poll_interval=...)) and raise click.BadParameter (or exit with a
clear error) if the value is <= 0. Apply the same validation to the other
--poll-interval definition mentioned (lines 433-441) so both command paths
reject non-positive intervals.

In `@coding-agent/src/coding_agent/remote/worker.py`:
- Around line 344-370: The await heartbeat_task in the finally currently
re-raises non-Cancelled exceptions and prevents the subsequent TurnEnd/complete
POST; change the finally to catch and swallow heartbeat errors so finalization
always runs: wrap the await heartbeat_task in a try/except Exception as e (still
allow asyncio.CancelledError to be ignored) and either append or fold e (e.g.,
str(e)) into the existing error variable before continuing, then always execute
the TurnEnd emit and the client.post to /worker/runs/{run_id}/complete; refer to
_heartbeat_until_complete, heartbeat_task, TurnEnd emission, and the
client.post("/worker/runs/{run_id}/complete") call to locate where to make this
change.
- Around line 123-125: The control-plane HTTP client currently uses timeout=None
in run_local_worker_once and run_worker_loop which lets non-SSE POSTs
(claim/heartbeat/events/complete) hang indefinitely; change those AsyncClient
calls to use a bounded httpx.Timeout(...) for normal control requests and only
disable timeouts for the SSE prompt-stream request. Also update
_execute_claimed_run so that in the finally block you wait for the
heartbeat_task with errors captured (e.g., asyncio.gather(heartbeat_task,
return_exceptions=True) or catching exceptions from await heartbeat_task)
instead of letting an exception from _heartbeat_until_complete/client.post abort
before posting to /worker/runs/{run_id}/complete, ensuring the finalization POST
always runs even if heartbeat fails.

In `@coding-agent/src/coding_agent/runtime_store.py`:
- Around line 223-241: Add a DB index to speed up the external-worker claim
query: create a partial/expression index on agent_runs covering the filtered
expression columns and ordering keys used by _CLAIM_EXTERNAL_WORKER_RUN_SQL —
e.g. an index on (started_at, run_id) with expression columns
(metadata->>'execution_binding_kind', metadata->>'executor_kind', session_id) in
the predicate and a WHERE clause matching the claimable subset (status IN
('requested','expired') AND metadata->>'execution_binding_kind' =
'external_worker' AND metadata->>'executor_kind' IS NOT NULL). Put this in a
migration (CREATE INDEX CONCURRENTLY ...) so the agent_runs SELECT ... FOR
UPDATE SKIP LOCKED query in runtime_store.py benefits from the index without
full table scans.

In `@coding-agent/src/coding_agent/server/http_server.py`:
- Around line 2630-2644: The current mapping sets completion_status to
CompletionStatus.ERROR for any non-"completed" status, making cancellations
indistinguishable from failures; update the logic that computes
completion_status (the variable set before calling _broadcast_event) to
explicitly map body.status == "cancelled" to CompletionStatus.BLOCKED.value,
keep body.status == "completed" mapping to CompletionStatus.COMPLETED.value, and
only use CompletionStatus.ERROR.value for other unexpected statuses so that
cancelled runs are broadcast as BLOCKED rather than ERROR.
- Around line 2611-2649: The try block currently wraps the durable state change
finalize_external_worker_run and the subsequent non-critical calls
get_session_async and _broadcast_event, so failures in session fetch or
broadcasting can raise after the run is already finalized; move
finalize_external_worker_run(run_id=..., ...) out of the broader try so it
executes and returns success independently, then perform
get_session_async(run.session_id) and _broadcast_event(...) in a separate
try/except that catches and logs exceptions (including run_id and session_id)
but does not re-raise them to clients; ensure any casting/serialization for
tape_entries/result remains unchanged and only the non-idempotent durable call
is guaranteed before returning success.

In `@coding-agent/src/coding_agent/server/session_manager.py`:
- Around line 1150-1169: The _load_and_authorize_external_worker_run helper only
validates worker_id and claim_token; add a guard that rejects expired or
inactive claims by checking the run.metadata lease fields (e.g.
"claim_expires_at" timestamp and/or a "worker_active" flag) before returning the
run; parse the timestamp (use datetime.utcnow() for comparison) and if the lease
has expired or worker_active is false/absent raise PermissionError("external
worker claim is expired or inactive") so stale workers cannot heartbeat, append
events, or finalize runs.
- Around line 2664-2695: The code moves an unclaimed ExternalWorker run into
"cancelling", which makes it unclaimable; instead, after loading the run via
load_runtime_run(session.current_turn_id) check the run's current status and if
it is still "requested" or "expired" update the run in _runtime_store to a
terminal "cancelled" (and add cancel_requested_at metadata) and set the session
(session.turn_status, session.turn_in_progress, last_activity) to idle; only set
the run and session to "cancelling" when the loaded run indicates it has been
claimed/started (i.e., not "requested"/"expired"). Ensure you update the same
call site that currently uses _runtime_store.update_agent_run and adjust the
returned CancelTurnResult status accordingly.

In `@coding-agent/tests/ui/test_http_server.py`:
- Around line 7034-7055: The claim_external_worker_run test double only claims
runs with status "requested", but production allows claiming recovered "expired"
runs; update claim_external_worker_run to treat runs with status in
{"requested","expired"} as claimable (i.e., change the status check in
claim_external_worker_run to allow both "requested" and "expired"), preserving
the existing metadata merge and replace call that sets status="claimed" and
updates self.runs[run.run_id].
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 744f0850-ccd4-4f16-90ed-5872fb985946

📥 Commits

Reviewing files that changed from the base of the PR and between d192c97 and 269c0d3.

📒 Files selected for processing (13)
  • coding-agent/.opencode/prompts/tasks/external-worker-execution-task-packet.md
  • coding-agent/docs/adr/0051-external-worker-execution-control-plane.md
  • coding-agent/src/coding_agent/cli/remote_commands.py
  • coding-agent/src/coding_agent/remote/worker.py
  • coding-agent/src/coding_agent/runtime_store.py
  • coding-agent/src/coding_agent/server/binding_resolver.py
  • coding-agent/src/coding_agent/server/execution_binding.py
  • coding-agent/src/coding_agent/server/http_server.py
  • coding-agent/src/coding_agent/server/schemas.py
  • coding-agent/src/coding_agent/server/session_manager.py
  • coding-agent/tests/cli/test_remote_client.py
  • coding-agent/tests/coding_agent/test_pg_runtime_store.py
  • coding-agent/tests/ui/test_http_server.py

@delete-cloud
delete-cloud force-pushed the external-worker-execution branch from 96a1d27 to 3ba47a1 Compare May 31, 2026 12:03
@delete-cloud
delete-cloud force-pushed the external-worker-execution branch from 3ba47a1 to 55e4a6b Compare May 31, 2026 12:07
@delete-cloud
delete-cloud merged commit be34e1b into main May 31, 2026
1 check passed
@delete-cloud
delete-cloud deleted the external-worker-execution branch May 31, 2026 12:08
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