Delegate bounded work from Claude Code to a different coding-agent CLI, and get a durable, inspectable result back.
Status: core commands, all three adapters, and the current ticket set are complete. The ticket records are
archived in docs/tickets/.
You are working in Claude Code and you want a genuinely independent second opinion, or a scoped code review whose
diff never enters your own context window, or a long task running in the background, or a code change made
somewhere your working tree cannot be touched. dcli gives you one job model for all of that, across
three backends.
| Command | Backend | Driven by |
|---|---|---|
dcli-opencode |
opencode 1.18.7 | one opencode serve process per job, over HTTP |
dcli-codex |
Codex CLI 0.145.0 | codex exec --json, prompt on stdin |
dcli-claude |
Claude Code 2.1.220 | claude -p --output-format stream-json |
dcli --backend <b> |
any | umbrella, for scripting |
Each backend also installs its own skill, generated from one source — so an agent reading a skill sees exactly one coherent surface and never assumes a sibling backend's flag exists.
Two sequenced steps, from a clone of this repo:
# 1. Put dcli, dcli-codex, dcli-opencode, dcli-claude on PATH
npm install -g . --install-links
# 2. Install the agent integration (skills, commands, rules)
pwsh -NoProfile -File install.ps1Step 2 asks which targets to install, with both selected by default:
| Target | Directory | Receives | Read by |
|---|---|---|---|
claude |
~\.claude |
skills, commands, rules, worker prompts | Claude Code |
agents |
~\.agents |
skills only | any CLI that reads the shared skills root, including the Codex CLI |
commands\ and rules\ are Claude Code layouts and go only to the claude target: the Codex CLI's
custom-prompt directory is flat, with no dcli-<backend>\ namespace, and its ~\.codex\rules holds
execpolicy rules rather than agent instructions. Installing either there would place files no host reads.
To choose non-interactively, name the targets and override either directory:
pwsh -NoProfile -File install.ps1 -Targets claude,agents -Force
pwsh -NoProfile -File install.ps1 -Targets agents -AgentsDir D:\alt\.agents -ForceReinstalling is a swap, not a merge, so a file dropped in a newer version does not survive as a ghost. The installer refuses to overwrite a file it did not write, and refuses any directory that collides with the job-state root.
Open a new shell afterward — step 1 changes PATH, and an already-open shell won't pick that up. Verify with:
dcli-opencode --help
dcli-codex --help
dcli-claude --helpThe --install-links option makes this local-path install copy the package into npm's global directory instead of
creating a junction back to the repository. The installed shims therefore keep working if this checkout is moved or
deleted. Use npm link only for deliberate live development against this checkout.
# the canonical shape: one synchronous run, prompt from a file
# run ignores --timeout-sec (that flag belongs to wait). The second bound is the
# timeout of whatever tool invokes this line; a bare shell does not impose one,
# so if you cannot set it, use submit + wait --timeout-sec <n> instead.
dcli-codex run --repo D:\path\to\repo --prompt-file .\prompt.md `
--hard-timeout-sec 900 --label diagnose-cache
# a second opinion, synchronously
"Compare these two designs." | dcli-opencode run --hard-timeout-sec 900
# a scoped review; the wrapper generates and embeds the diff itself
dcli-opencode review --range main..HEAD --path src/ --intent "Add cache invalidation" --hard-timeout-sec 900
# something long, in the background — wait --all --group only finds jobs submitted with that group
"Run the full test suite." | dcli-claude submit --access workspace --group nightly --hard-timeout-sec 3600
dcli-claude wait --all --group nightly --timeout-sec 3600 --json
# a code change, isolated — you review it before it lands
dcli-codex run --mode implement --access workspace --hard-timeout-sec 1800 "Add retry logic to the fetch helper"
dcli-codex diff <job-id> --stat
dcli-codex diff <job-id>
dcli-codex apply --reset-author --message "feat: add retry logic" <job-id>
# the same change, in the background — submit honours --mode implement
"Add retry logic to the fetch helper" | dcli-codex submit --mode implement --access workspace --group nightly --hard-timeout-sec 1800
dcli-codex wait --all --group nightly --timeout-sec 1800 --json
dcli-codex diff <job-id> --stat
dcli-codex diff <job-id>
dcli-codex apply --reset-author --message "feat: add retry logic" <job-id>
# resume — continue a backend conversation (piped)
"Now critique your own plan." | dcli-opencode resume <job-id> --kind continue_backend_session --hard-timeout-sec 600
# resume — continue with a prompt file (always works)
dcli-opencode resume <job-id> --kind continue_backend_session --prompt-file followup.txt --hard-timeout-sec 600
# resume — retry after a transient failure
dcli-opencode resume <job-id> --kind retry_attempt --hard-timeout-sec 600 "Re-run the same analysis"
# resume — fork from a completed result
dcli-opencode resume <job-id> --kind fork_from_artifacts --hard-timeout-sec 600 "Build on what you found"
# submit a detached child forked from a parent's artifacts
dcli-opencode submit --resume <job-id> --mode implement --hard-timeout-sec 600 "Build on what you found"
# preview and then remove aged jobs and their worktree artifacts (days or hours)
dcli-codex cleanup --older-than 1d --dry-run
dcli-codex cleanup --older-than 1d
# diagnose the backend with a real bounded request (120 seconds by default)
dcli-opencode doctor --jsonBoth a backgrounded submit and a foreground run/resume respond to dcli cancel <job-id>:
the running attempt watches the same cancel.request file cancel writes, so the job reaches
cancelled instead of done or timed_out.
submit --resume <job-id> creates a detached child job with the fixed strategy
fork_from_artifacts: in implement mode its worktree is seeded from the parent's result commit, and it
inherits the parent's group, label and access unless overridden. It does not continue the backend
session. For conversational continuation use resume <job-id> --kind continue_backend_session.
--kind does not apply to submit.
Every recipe carries an execution budget and a wait budget. That is not decoration — an unbounded wait once cost a
user eight hours. The job hard-timeout default is 1800 seconds; the caller-side wait default is 300 seconds.
They are independent: exit 20 means only that this caller stopped waiting, while the job may still be running.
Use wait --json and inspect wait_timed_out / wait_timeout_sec when automation must distinguish the two.
The calling shell or tool needs its own finite timeout too, longer than --hard-timeout-sec.
Job ids look like 20260804T123456Z-a1b2c3d4. Anything else is rejected as a usage error rather than
looked up — an id from another tool's runtime is not a dcli job, and dcli list is the list of ids
that are. Records are per repository, so read a job with the same --repo you submitted it with.
- Every wait is bounded. Every wait, read, lock, HTTP call, and drain has a finite default. A job blocked on a permission decision is reported as blocked, with the permission named — not as a timeout.
- Nothing is applied automatically. Delegated changes land in an isolated git worktree. You inspect the diff
and decide. There is no automatic path to
apply, at any policy checkpoint. - Apply reports repository safety explicitly. Exit
25means the failed apply was verified restored; exit27means restoration could not be verified, so stop and inspect the repository before retrying. - Cleanup owns the whole artifact. For eligible terminal implement jobs,
cleanupremoves the job record, worktree directory, and its git registration together. It also reports and removes orphan worktrees under the dcli state root. Usecleanup --dry-runfirst; it lists each worktree and its byte count, while busy artifacts are named and skipped. - Differences are stated, not hidden. An option a backend cannot serve fails immediately, naming the alternative, before any job is created. Options whose meaning differs get backend-qualified names rather than one flag with three meanings.
- A parse failure is never a pass. An unreadable review appendix reports
malformed— never "no findings". - Reduced coverage is always announced. Truncated diffs and excluded untracked files are reported, not silent.
doctorruns a live smoke by default. It starts the selected backend, sends a trivial read-only request, and reportsok,coverage, andlive_smoke_timeout_secin its JSON envelope. Use--live-smoke-timeout-sec 0only for an explicit static-only check; the output reports that reduced coverage.
Running jobs do not survive a wrapper crash. If the controlling process dies, the attempt is marked
interrupted; resume then starts a new attempt from durable inputs, and never reattaches to a running backend.
The alternative would require a reconnectable process supervisor, and that complexity was judged worse than the
honest limitation.
A budget bounds the wrapper's wait, not the backend's process tree. --hard-timeout-sec and --timeout-sec
both bound what dcli does: the attempt is ended, the record is written, and control returns to you. On Unix
the backend runs in its own process group and a hard timeout or an escalated cancel terminates the whole group
(SIGTERM → grace → SIGKILL), so the backend and everything it spawned cease running. On Windows dcli
terminates the backend's descendants too — it enumerates them, kills them with taskkill /T /F, and then
verifies against the exact set it enumerated — but this is a declared degraded capability: a descendant
spawned between the enumeration and the kill escapes, and a process that a tool cannot end is reported, not
hidden. The record says so: the job carries containment: { kind: "taskkill-tree", degraded: true }, a hard
timeout records any survivors on its timed_out detail, and a cancel that leaves survivors exits 21 and
names them rather than reporting a clean cancellation. If a job matters enough that a survivor would be a
problem, check for one before re-running it.
README.md is user-facing. If you are developing, start with AGENTS.md — it is the standing rules,
and every line of it was paid for by a real bug.
| For | Read |
|---|---|
| Current and past work items | docs/tickets/ |
| Product intent and user stories | docs/product-spec.md |
| Why the architecture is this way | docs/architecture-decisions.md |
| What was challenged, and what changed | docs/architecture-review-record.md |
| Binding contracts | docs/design-spec.md |
| Engineering notes: lessons, backend traps, testing, spawning | docs/engineering/ |
| What a backend CLI actually accepts | docs/reference/ |
| Verified facts about opencode, and what is not verified | docs/reference/opencode-study.md |
node tests/run-tests.js # quick suite; skips slow tests, lists them
node tests/run-tests.js --suite full # full suite; runs everythingTests are plain Node assertion scripts checked by exit code. No test framework.
This design descends from ccodex, a complete production PowerShell tool that wrapped the Codex CLI. Its two
months of hardening — and its bugs — are why AGENTS.md exists. The predecessor keeps running unchanged; no job
state is migrated.