Skip to content

The Bridge

barkley-clawd edited this page Aug 8, 2026 · 3 revisions

The Bridge

Caduceus runs your worker, but the worker is your code. The bridge is the thin script that connects the daemon to whatever AI harness you want — OpenCode, pi, codex, claude-code, your own custom thing. If you want to swap harnesses, the bridge is the only file you edit.

Setup seeds a reference implementation at ~/.hermes/caduceus/worker-bridge.py. It's yours — plugin updates never overwrite it. That's the whole point of the user-owned bridge pattern.

The reference bridge calls OpenCode's gentle-orchestrator agent. The daemon has no opinion about what your bridge invokes; it treats the bridge as a black box that reads some environment variables, does the work, and exits with a code.

What the daemon gives your worker

The daemon rebuilds the worker's environment from scratch. Every variable the bridge reads comes from this list:

Variable What it is
CADUCEUS_ISSUE_NUMBER Numeric issue ID.
CADUCEUS_ISSUE_TITLE Issue title.
CADUCEUS_ISSUE_BODY Issue body, raw Markdown.
CADUCEUS_ISSUE_REPO owner/repo slug.
CADUCEUS_ISSUE_LABELS_JSON Current label names as a JSON array.
CADUCEUS_WORKTREE_PATH The isolated directory your worker operates in.
CADUCEUS_RUN_ID ID for this run; names the transcript log.
CADUCEUS_BRANCH_NAME The daemon-owned branch. Read it, never create/rename branches.
CADUCEUS_CONTEXT_JSON Structured context for advanced multi-turn harnesses (timeline, trusted edits, allowed threads).

Plus whatever your worker_env_allowlist admits, on top of the sensible defaults (PATH, HOME, USER, SHELL, LANG, LC_ALL, TERM, TMPDIR, OPENAI_*, ANTHROPIC_*, OPENROUTER_*, OPENCODE_*).

The daemon never passes a GitHub credential to the worker. Not GITHUB_TOKEN, not GH_TOKEN, not anything token-shaped. Even if you add the name to the allowlist, it is denied. This is enforced in the daemon, not in the bridge, and it is not configurable. Your worker can read your files (same user), but it cannot push to GitHub or comment under the daemon's identity. That's the security model, and it's load-bearing.

What your worker must return

On success (exit 0), your worker leaves a worker-result.json at the worktree root:

{
  "status": "success",
  "summary": "What the worker did, as Markdown. Becomes the PR description.",
  "commit_message": "fix(component): description",
  "pull_request_title": "fix(component): description"
}

Rules that will bite you if ignored:

  • status must be exactly "success". Anything else is a failure.
  • summary is required, non-empty, and becomes the PR body (code tickets) or the findings comment (investigation tickets).
  • commit_message and pull_request_title are required even for investigation tickets, where they're ignored. One harness, one result shape, no forking.
  • Unknown top-level fields are rejected. The daemon doesn't silently ignore slop.
  • The whole file is capped at 1 MiB.
  • An optional artifacts object (keys + JSON values) renders into the PR body, escaped and size-limited.

Exit codes

  • 0 — success. The daemon reads worker-result.json and finalises.
  • anything else — failure or abstain. The transcript is captured, the claim released, and the retry budget applies. The daemon doesn't care which non-zero code you picked; pick meaningful ones for your own debugging.

What the bridge must not do

The daemon trusts you here — the bridge is your code, so the contract is enforced by discipline, not runtime checks:

  • Don't write into <state_dir>. That directory is the daemon's. Your worker writes only inside its worktree.
  • Don't create or rename git branches. The daemon owns the branch name. Create a different one and the finalize step won't see your work; rename the daemon's and the next tick can't resume.
  • Don't call the GitHub API. You have no token — deliberately. The local clone (fetched by the daemon) is your read source; the context JSON is your structured source.
  • Don't install signal handlers. The supervisor delivers SIGINT/SIGTERM to the whole worker session; your handlers confuse the kill propagation and a timed-out worker may not die.
  • Don't background yourself. The supervisor is not a process reaper. A daemonised child of the bridge is the supervisor's problem child. This is the most important rule of bridge-writing.
  • Don't retry the harness internally. Retries are the daemon's job. One invocation, one exit.

Swapping harnesses

Edit your user-owned worker-bridge.py. The invoke_harness(worktree, prompt_file, run_id, labels, branch_name) function is the single user-editable hook — replace its body and the rest of the bridge is harness-agnostic plumbing you never need to touch.

Caduceus docs

Clone this wiki locally