#43

feat: add coddy worker driver for local execution

Openksergkwants to merge intofrom

What

Adds a Coddy worker backend (type: coddy) for local mode (coddy-agent).

Coddy's one-shot mode prints only the final answer to stdout (coddy -p "<prompt>"), which matches the dispatcher contract (results are read from stdout), so the integration is a thin driver and needs no plumbing changes.

Why

Local mode currently supports claude / codex / pi. Coddy is a terminal coding agent that many people already have installed and configured, and like the other local backends it needs no API keys in dispatch.yaml.

How

CoddyDriver.build_execute emits:

coddy -no-stdin -ephemeral -permission-mode bypass [-model <CODDY_MODEL>] -p <prompt>
  • -no-stdin - an inherited stdin must never block a one-shot run (nohup, systemd, CI runners that feed the job script on stdin);
  • -permission-mode bypass - local mode has no TTY that could approve tool calls;
  • -ephemeral - one throwaway session per task, nothing accumulates in ~/.coddy;
  • CODDY_MODEL (optional env) - a model id already configured in coddy's own config.yaml; no keys or base URLs are injected.

supports_conclude() returns false: coddy resumes by workspace ("the most recent session in this folder"), not by session id, so a conclude run could attach to another task's session in the same project directory. Tasks run as ephemeral one-shots and the optional conclude fallback stays off.

The driver is registered in LOCAL_DRIVERS only, because the binary is not in the worker image. A coddy worker under runtime.execution: container now fails fast with an explicit error instead of a key lookup error at exec time.

Files

  • cairn/src/cairn/dispatcher/workers/adapters/coddy.py (new driver)
  • .../workers/adapters/__init__.py, .../workers/registry.py (registration)
  • .../dispatcher/config.py (WorkerType, WORKER_ENV_KEYS, local-only guard)
  • cairn/tests/test_config_and_adapters.py (4 tests)
  • README.md, dispatch.local.example.yaml (docs)

Testing

  • uv run --project cairn --group dev pytest - 102 passed, including 4 new tests (argv shape, CODDY_MODEL override, no-conclude contract, local-only config guard)
  • live end-to-end on Linux with coddy 1.3.62 and llama.cpp (n_ctx=262144):
    • one explore turn: rendered prompts/default/explore.md, graph snapshot in a file, the model returned a single raw JSON payload accepted by parse_json_output + validate_explore_payload;
    • full project with bootstrap_enabled: false: reason solved it and the project completed (202 s);
  • cairn dispatch --config dispatch.yaml --startup-healthcheck-only detects the CLI on PATH: [+] coddy /home/.../coddy.

Notes

docs/specs/dispatcher-design.md lists only claudecode / codex / mock and has no pi section either, so I left it untouched to keep this diff focused. Happy to sync the spec in a follow-up if that is preferred.