skip to content

work/caduceus

in development · 2026

caduceus

label an issue. wake an agent. get a pull request.

a self-hosted rust daemon + hermes plugin. polls github for labeled issues, hands each one to a configurable ai harness in an isolated worktree, and finalizes the result as a branch + push + pr.

rusttokiogithub rest + webhookshermes pluginisolated git worktreesposix flock

the story

caduceus is what happens when you stop pretending your ai agent is a chat partner and start treating it like a cron job that writes code.

the daemon owns everything deterministic: polling, claims, worktrees, timeouts, git, github, retries. the worker is a black box that reads env vars and exits with a code. they never share a library.

that contract is the whole point. you can swap the worker for opencode, codex, claude code, or your own custom script without touching the daemon. the daemon never imports a harness library; the harness never imports a github library.

what it does

  • harness-agnostic — the bridge is a small python script you can edit. the daemon doesn’t care.
  • worker sandbox is air-gapped: sanitized env, no github credentials ever reach the agent.
  • deterministic infrastructure (etag-aware polling, posix flock queue, hard worker timeouts) keeps the plumbing boring on purpose.
  • zero inbound networking. refuses to listen on any port. refuses to publish a comment with a hardcoded list of internal tool names.

decisions i’m proud of

two processes, one contract

an llm call is non-deterministic. a state machine is not. the second you let either side reach into the other, you’ve bought a future where a stalled token stream holds your queue hostage. we didn’t buy it.

the worker is a subprocess with env vars

caduceus doesn’t ship an "agent sdk". it ships `worker-bridge.py`. you can read it in a minute. you can replace it in an afternoon. the daemon is unopinionated about which model you call — because the daemon will outlive whichever model is hot this month.

public-voice rule is enforced, not documented

the daemon refuses to post a comment that names internal tools. not because the rule is important in itself — because the rule is a forcing function to keep the agent’s voice something a stranger could read without a decoder ring.