Agent CLIs
Vincent orchestrates agent CLIs you install and authenticate yourself. It stores no credentials, embeds no model access, and speaks to no vendor API of its own: an agent step spawns the same binary you would have run by hand, in the task’s worktree, with its prompt on stdin.
Three adapters ship. What an adapter cannot do is documented and ignored at run time — never emulated. A field an adapter has no concept of is stated as ignored here and in the spec, so a workflow never appears to honor something it silently drops.
- At a glance
- Claude Code
- Codex
- Cursor
- How vincent finds a CLI
- Choosing models and effort
- Choosing between them
At a glance
| claude | codex | cursor | |
|---|---|---|---|
| Binary | claude |
codex |
cursor-agent |
agent: value |
claude |
codex |
cursor |
Mid-run questions (awaiting_input) |
✅ | — | — |
| Resumes its own session (chats) | ✅ --resume |
✅ exec resume |
✅ --resume |
| Reports a session it can no longer resume | ✅ session_lost |
✅ session_lost |
— (it adopts the id and answers) |
| Reports cost | ✅ | — | — |
model: |
✅ | ✅ (free text) | ✅ (~180 enumerated) |
effort: |
✅ | ✅ | — (it lives in the model id) |
restricted mode |
✅ | ✅ | ✅ on macOS/Linux, refused on Windows |
| Carries vincent’s MCP server for one run | ✅ --mcp-config |
✅ -c mcp_servers.… |
✅ .cursor/mcp.json in the worktree |
| Tested-build list | 2.1.224, 2.1.226 |
0.142.5, 0.147.0, 0.150.1 |
2026.08.04-aaa8809, 2026.08.11-e8db854 |
| Reports whether you are logged in | — | — | ✅ |
| Recognizes a usage limit / auth failure in a run | ✅ | — | — |
| Reports remaining quota without running | push only (its status line) | ✅ app-server --stdio |
— (no usage surface) |
vincent daemon status, the TUI’s daemon view, and GET /v1/agents all report
what vincent actually resolved on your machine — path, version, the model and
effort options it discovered, and the health verdicts below: whether the build
you have is one vincent has been tested against, and whether the adapter can
restrict anything on this operating system.
Claude Code
Binary: claude. Workflow value: agent: claude.
The most capable adapter, and the only one that can be interrupted mid-step.
- Runs
claude -p --output-format stream-json --verbose, cwd set to the worktree, prompt on stdin — never as an argv element, because Windows caps arguments at 8 KB and prompts embed task descriptions. full-autoadds--dangerously-skip-permissions;restrictedmaps to Claude’s allowlist flags with an edit/read/git/test tool set — plusmcp__vincent__*in full, sorestrictedbounds the filesystem and the shell, not what the step does to vincent. See Driving vincent from an agent.- Resumes its own session, with
--resume <session_id>. That is what makes a chat work: vincent stores thesession_idclaude stamps on its stream and hands it back on the next turn, so turn N sees turns 1..N-1 without anything being replayed into the prompt. Workflow steps never set it — every step still gets a fresh session. A session the CLI no longer knows fails that turn withsession_lostrather than quietly starting a new one. - Carries vincent’s own MCP server on
--mcp-configwith an inline config, alongside--strict-mcp-configso your own MCP servers never leak into a step. Per run: nothing global is written. model:andeffort:pass straight through as--model/--effort. Options are discovered by parsingclaude --helpand merged with a curated catalog, so a CLI upgrade that adds an effort level makes it selectable without a vincent release.- Reports the most about its own run. The output pane’s
#header line — the working directory and the tool set a run was given — and the closing line’s duration, turn count, cache-token split, per-model breakdown, refused tool calls and stop reason all come from claude’s stream and from no other adapter’s. A tool call a permission rule refused is marked apart from one that ran and failed. See the output pane. - Reports token usage and cost. The board’s cost column sums every attempt,
retries included. The other two adapters report no cost at all — which also
means
max_task_cost_usdcan only stop a task that ran on claude. - Recognizes a spent usage quota and a logged-out CLI in the output of a run
that failed. A quota stop becomes
usage_limit— no retry consumed, the task waits and re-runs itself — and a logged-out CLI becomesagent_unauthenticated, which blocks with the fix named. Codex and cursor do neither: their wordings have not been captured from a real run (doing so means burning a real quota window), and vincent will not guess at one, because a wrong guess parks a genuinely failed task in a wait it never leaves. On those two, both conditions still read asagent_errorornonzero_exit. See Troubleshooting. - Reports its remaining quota, but only by pushing. There is no usage
subcommand to poll; what Claude Code has is a status line, which it hands both
usage windows on every render.
vincent statuslineis what catches them, and the daemon view’siis what installs it — after showing the exact JSON it would write to~/.claude/settings.json. Until then claude reports nothing. See How much quota is left.
Mid-run questions
Claude Code is the one adapter with a control channel, so a step can pause,
ask, and resume in the same session. When the agent uses its AskUserQuestion
tool, vincent normalizes the request into a question (with option labels, and
multi-select honored); in restricted mode a denied tool surfaces as a
permission request instead.
The task moves to awaiting_input, keeps its concurrency slot, and pauses the
step’s timeout clock. Answer from the TUI popup or
POST /v1/tasks/{id}/answer; the run resumes where it stopped. Set
on_input: deny on a workflow that must stay unattended — see
Writing workflows → mid-run questions.
Input support is version-gated to the CLI family vincent has verified
against real captured runs. Outside that range the adapter reports
supports_input: false and runs exactly as it otherwise would — no input flags,
plain-text prompt. Nothing degrades silently.
A workflow that needs the conversation says on_input: require, and then a
claude outside the verified family is refused for that step rather than run
unattended — GET /v1/agents reports input_verdict: "unsupported" for it, and
a step that reaches the engine anyway fails with input_unsupported.
Codex
Binary: codex. Workflow value: agent: codex.
- Runs
codex exec --json, cwd set to the worktree, prompt piped on stdin. full-automaps to--dangerously-bypass-approvals-and-sandbox— the documented automation switch.restrictedmaps to--sandbox workspace-write, confining writes to the worktree.model:passes as-m;effort:as-c model_reasoning_effort=…. Efforts areminimal, low, medium, high, xhigh.- Carries vincent’s own MCP server the
same way:
-c mcp_servers.vincent.…dotted overrides, with the bearer token handed to the child in its environment rather than written down. Per run — yourconfig.tomlis never modified. - No model catalog. The CLI enumerates nothing, and codex model availability is account-dependent — the same id is accepted on one plan and rejected on another — so pickers offer free text and the CLI’s own default. A model you type is passed through with a warning, not rejected.
- No cost reporting, and
supports_input: false: a codex step never entersawaiting_input, andon_input: wait|denyhas no effect on it.on_input: requireis the one that does: a step declaring it cannot use codex at all, and a workflow pinningagent: codexon such a step fails validation. - Resumes its own session, so codex holds a
chat like Claude Code does. Resume is a
subcommand rather than a flag —
codex exec --json resume <thread_id>— and the prompt stays on stdin, becauseexec resumereads stdin when it is given no prompt argument. vincent stores thethread_idthe stream reports onthread.startedand hands it back on the next turn. The argv is pinned by a capture against codex-cli 0.150.1; nothing is emulated, and vincent never replays a conversation into the prompt. Workflow steps never set it: every step still gets a fresh session. Two consequences, both stated rather than worked around:- A resumed run is always full-auto.
codex exec resumehas no--sandbox, sorestrictedhas no spelling on it. Only a chat turn ever resumes, and a chat is always full-auto — there is no permission mode to ask for on one — so this is a combination you cannot reach rather than a restriction that gets dropped. - A thread codex no longer knows fails that turn with
session_lostrather than quietly starting a new one.
- A resumed run is always full-auto.
- The plan and command output are surfaced. Codex reports a running to-do
list, which the pane shows with a
☰gutter at thenormalandverboseoutput levels, ticking over as the agent works. What a command printed shows atverboseonly — it is the output body, and a step runninggo test ./...would otherwise flood the level most readers use; a body long enough to hit the cap ends in… output truncated. File changes are named by their paths and kinds, and MCP calls by server and tool. - Token usage is read whole. All five of the counters codex reports land in
the run’s accounting, so
verboseshows a codex turn’s cache read/write split and its reasoning spend rather than leaving them blank. Cost is still absent, because the CLI does not report one — see the bullet above. - Reports its remaining quota on request, and is the only adapter that
needs no setup to do it:
codex app-server --stdioanswersaccount/rateLimits/readwith itsprimaryandsecondarywindows, which vincent asks for on the ordinary catalog refresh. A missing binary, a logged-out account, a timeout or a malformed answer degrade silently and fail no probe. See How much quota is left. - Reasoning is surfaced. Codex emits whole reasoning blocks, which the TUI
shows at the
normalandverboseoutput levels (vcycles them) and the transcript records asagent.thinking. Whether any are emitted depends on the effort you asked for — a low-effort turn can spend reasoning tokens and produce no blocks at all.
Caveat on
restricted+ git. In a linked worktree the real git directory lives under the main repository, outside the sandbox, so agit commitfrom a restricted codex step may be denied. Vincent itself never needs a commit — the diff reads the working tree — but a workflow that commits from a restricted codex step should use acommandstep for the commit instead.
Cursor
Binary: cursor-agent — never cursor, which is the editor launcher
and would open a GUI. Workflow value: agent: cursor.
- Runs
cursor-agent -p --output-format stream-json --trust, cwd set to the worktree, prompt on stdin.full-autoadds--force;restrictedadds--sandbox enabledinstead. --trustis passed in both modes: a task runs in a git worktree the CLI has never seen, and a workspace-trust prompt in a headless run is a hang, not a question.- Vincent’s own worktree flags are never passed to it. Cursor has a worktree feature; worktrees belong to vincent, and two owners of one concept is a defect.
- Reports token usage but no cost, and
supports_input: false— so, like codex, cursor cannot back a step declaringon_input: require, and pinning it on one is a validation error. - Resumes its own session too, with
--resume <session_id>— the samesession_idits stream stamps on every line — so cursor can hold a chat. Unlike claude and codex it has no way to tell you a session is gone: handed a--resumeid it has never seen it starts a fresh chat under that id and answers normally, so a cursor chat whose session has aged out replies without remembering the conversation instead of failingsession_lost. - Errors do not arrive in the stream — an invalid model id exits 1 with a message on stderr and no result line — so the adapter reports “stream ended without a result event” plus the stderr tail, which is what makes an everyday typo diagnosable.
Four things about cursor are genuinely different, and all four are visible in a workflow:
1. Effort lives in the model id
Cursor has no effort flag. Reasoning depth is encoded in the model:
claude-sonnet-5-thinking-xhigh, gpt-5.4-mini-high. So effort: on a cursor
step is ignored, its effort catalog is empty, and
vincent workflow validate rejects a claude or codex effort value on a cursor
step — which is exactly the error a workflow author needs to see.
Run cursor-agent models to see what your account offers; vincent probes the
same list.
2. A cursor step overwrites your saved CLI model
Cursor persists whatever --model it is given into ~/.cursor/cli-config.json,
so leaving the model unset means “whatever the last invocation chose” — possibly
a previous vincent step. To keep runs reproducible the adapter always passes
--model, defaulting to auto.
The accepted cost: running a cursor step overwrites the model you last picked in
an interactive cursor-agent session. Determinism is worth more to an
orchestrator than preserving an interactive preference, and auto at least
lands on cursor’s own default rather than on wherever the last task left it.
3. restricted needs macOS or Linux
--sandbox enabled exits 1 on Windows with “Sandbox requires macOS or
Linux” before doing any work. Vincent therefore refuses to create a task
whose restricted step resolves to cursor on Windows: POST /v1/tasks answers
400 naming the step and the agent, and the TUI shows that message on the
new-task form. A task that reaches the engine anyway — a data directory carried
to Windows, or a workflow edited after the task was queued — fails to start with
block reason restricted_unsupported, under the normal retry policy.
Falling back to --force was rejected outright: it would run full-auto a step
that explicitly asked not to be, turning a safety choice into its opposite on
exactly one OS — the failure mode nobody would think to check for. See
Windows.
4. A cursor step writes .cursor/mcp.json into the worktree
Cursor has no per-run MCP flag at all — cursor-agent mcp reads only
.cursor/mcp.json in the workspace or ~/.cursor/mcp.json globally. So to give
a step vincent’s own tools the adapter
writes the workspace file into the task worktree before the run, passes
--approve-mcps so a headless run does not stop on a trust prompt for the
server vincent just configured, and removes the file (and an empty .cursor/)
after the step ends. Your global ~/.cursor/mcp.json is never touched.
The accepted cost, and the one you can see: while a cursor step is running, that
file is an untracked file inside a git worktree. It shows up in git status,
in the task’s diff, and in dirty detection for the duration of the run. It is
gone once the step ends, and if the daemon dies mid-step
recovery sweeps it on the next start. The
alternative — writing your global config instead — was rejected: a per-task
change to a file you own outside vincent is worse than a transient file inside a
worktree vincent already owns.
mcp.wire_steps: false skips all of it: no file is written and --approve-mcps
is not passed.
The model list is advisory in both directions
cursor-agent models lists roughly 180 ids, but the list is account-scoped and
still over-broad: an id it lists can be rejected at run time. So membership is a
hint, free text stays accepted, and the CLI is the final authority. The TUI’s
model picker is windowed and type-filterable for exactly this catalog.
How vincent finds a CLI
By default the daemon resolves each adapter’s binary from PATH. Override it
per adapter in config.yaml:
agents:
claude: { path: "" } # "" = resolve from PATH
codex: { path: "/usr/local/bin/codex" }
cursor: { path: "C:/Users/me/.local/bin/cursor-agent.exe" }
An explicit path is absolute and never consults PATH, which makes it the
standing fix for “my shell finds it, vincent does not”.
Detection is cached by binary identity — resolved path + mtime + version.
Help output is a pure function of the installed binary, so the cache cannot go
stale by construction: upgrading a CLI invalidates it and the next request
re-probes. GET /v1/agents?refresh=true (or R in the TUI’s new-task view)
forces one.
A failed probe expires; a clean one does not. Nothing about a binary changes when a probe times out, so caching that failure forever would serve one bad moment for the daemon’s whole lifetime.
Probe failure degrades rather than blocks: if a CLI is missing or its help
output cannot be parsed, vincent serves the curated catalog with probe_error
set, and free-text entry is unaffected.
“Found” is not “usable”
An installed but unauthenticated CLI probes as healthy and then fails every
single run. Where a CLI can answer cheaply, vincent asks: cursor-agent status
and codex login status populate logged_in, so both report a definite
true/false. claude exposes no non-interactive auth surface at all — no
login, auth or status command — and the only definite answer is a real
prompt round-trip, which would bill you for a health check. So logged_in is
null for claude, which the TUI renders as unknown rather than as fine.
Neither probe ever guesses. A non-zero exit is false, an explicit negative is
false, an explicit positive is true, and anything else — including a probe
that times out or cannot be spawned — is null. That last rule is why a slow
machine never gets told it is logged out: on Windows a killed probe exits 1,
and reading that as “not authenticated” would be a false accusation.
vincent doctor prints this row for every
adapter and re-probes each time, so logging in and running it again shows the
change immediately — unlike GET /v1/agents, which serves the binary-identity
cache.
If a service-installed daemon reports agents as missing while the same daemon
started by hand finds them, that is a PATH capture problem — see
Running at login.
logged_in gets its own five-minute freshness window inside that cache. Binary
identity is exact for --help output and only a floor for auth state — nothing
about the binary changes when you log in — so past five minutes vincent re-asks
that one question and leaves the option catalog alone. A re-ask that fails keeps
the previous answer rather than downgrading it, for the same reason the probe
never guesses.
Was this build ever tested?
Every adapter carries the list of CLI builds vincent’s parsers were captured against, and reports a verdict on the one you have installed:
| Verdict | Meaning | What it changes |
|---|---|---|
tested |
your build is one vincent has fixtures for | nothing |
untested |
anything else | nothing |
incompatible |
a build vincent knows breaks | nothing today |
untested is the normal, expected answer. Agent CLIs ship far more often than
vincent does, so a few weeks after any release most people are on a build newer
than the fixtures — and it is expected to work. The verdict is advisory
everywhere: no run is refused, no task is blocked, and vincent doctor does
not exit 1 over it. It exists so that when something does look wrong, you can
tell at a glance whether you are on ground vincent has walked.
Comparison is exact: cursor’s version is 2026.08.04-aaa8809, calver plus a
commit sha, which no version range can order — so rather than have one adapter
answer a different question from the other two, all three compare whole strings.
The incompatible list ships empty for all three, because no such build has
been observed.
vincent doctor and the TUI’s daemon view print the verdict beside each
adapter, along with the builds it was judged against;
GET /v1/agents carries it as version_verdict and
tested_versions.
Can this agent restrict anything here?
restricted_verdict answers whether an adapter can honour
permission_mode: restricted on this machine. Cursor answers unsupported
on Windows and supported everywhere else; claude and codex answer supported
everywhere.
This is the one health verdict that refuses something. A task whose restricted
step resolves to an adapter that cannot restrict here is rejected at creation
with a 400 naming the step and the agent, rather than being allowed to spend a
worktree and a retry to fail with restricted_unsupported. The answer does not
depend on the installed binary — it is a fact about the adapter and the
operating system — so it is correct even where the CLI is missing entirely.
The step failure still exists as a backstop, for a task whose daemon changed underneath it: a data directory carried to Windows, or a workflow edited after the task was queued.
How much quota is left, and who will say
Two of the three adapters can now answer “how much quota do I have?” without running a step, and they answer in opposite directions.
- codex answers on request.
codex app-server --stdiospeaks JSON-RPC over stdio and replies toaccount/rateLimits/readwith the sameprimaryandsecondarywindows the CLI prints when it walls a run. Vincent asks on the ordinary catalog refresh — at most once every five minutes, and unconditionally onvincent doctororGET /v1/agents?refresh=true. There is nothing to install and nothing to configure. - claude pushes. Claude Code has no
usageorlimitscommand to poll, but it hands its status line a JSON object carrying both windows on every render.vincent statuslinereads that object, posts it to the daemon and prints the status line you already had; the daemon view’siis what installs it, after showing the exact JSON it would write to~/.claude/settings.json. Nothing is reported while it is not installed. - cursor cannot.
cursor-agent about --format jsonreports a plan tier and no numbers, so cursor implements no quota capability at all, spawns nothing, and is observation-only exactly as before. Vincent does not emulate what an adapter cannot do — there is no “cannot report” stub and no fabricated percentage.
A reported reading is held in memory, not the database: it is as durable as
the daemon, and a restart drops it until the next probe or push. Nothing here
can fail a probe — a missing CLI, a logged-out account, a handshake that times
out or a malformed answer all fall back to the behaviour below, and
probe_error keeps meaning only “the option probe failed”.
Underneath both, vincent still remembers. When an agent stops on a spent
window, the daemon records that per adapter — when it was seen, when it resets,
and whether the CLI named that reset or vincent estimated it from
usage_limit_recheck_interval.
That observation outlives the task’s own wait, and is what an adapter with no
reported reading is rendered from. A reading wins where there is one, so:
- the board header badges the adapter —
claude ⏳14:20instead ofclaude ✓; - the daemon view spells it out beside path, version and login state. A reading
is written window by window with the time it was taken —
quota codex app-server · 5h 28% → 13:00 · 7d 53% → 11:00 · read 09:14— and an observation asusage limit → 14:20with→for a reset the CLI stated and≈for one vincent estimated. An adapter with neither saysquota unknown; - the new-task form warns under the agent row —
· usage limit until 14:20.
The warning is advisory. The form still submits, admission is unchanged, and a
task queued against a spent window simply parks on the ordinary
usage_limit wait. The next
successful step on that adapter retires the observation — and only the
observation, since a step completing proves the wall vincent watched has come
down and proves nothing about a percentage a vendor reported — so an estimate is
never left standing over a CLI that is visibly working.
GET /v1/agents and GET /v1/info carry all of it as
quota, whose source says which kind of
fact you are holding.
Choosing models and effort
Set agent, model and effort on a step, in workflow defaults, or per task
at creation. Resolution is first-hit-wins:
- the explicit step field
- the task-level override chosen at creation (
--agent/--model/--effort) - workflow
defaults - the adapter’s default (usually empty — the CLI decides)
Model and effort only inherit from a level whose agent matches. When a step
or a task override switches agent without setting them, they reset to the new
adapter’s default rather than leaking across — a claude alias like sonnet must
never reach codex. The TUI’s new-task form shows which level won for each field,
and POST /v1/resolve answers the same question for a script.
vincent workflow validate catches a value belonging to another adapter’s
catalog. It cannot catch a model your account lacks: the CLI is the final
authority there, and you find out at run time.
Choosing between them
- Anything where you may want to answer a question mid-run — claude. It is the only adapter that can be asked something mid-run; all three can be resumed, so all three can hold a chat.
- Cost tracking matters — claude. The other two report none, so the board’s cost column stays empty for them and a configured per-task spend cap never fires. Vincent will not estimate money from token counts.
- Cheap, strictly unattended passes — codex or cursor are fine; set
on_input: denyand neither will ever try to stop for you anyway. - You want a specific model cursor offers — cursor, remembering that the reasoning level is part of the model id and that the step will rewrite your saved CLI selection.
- Mixed workflows are normal. Set
agent:per step: implement with one, review with another. A second opinion from a different vendor on the same diff is one of the better uses of a multi-step workflow.
See also
- Writing workflows — where
agent:,model:andeffort:go. - Security model — what
full-autoandrestrictedactually mean. - Troubleshooting.
- Spec §9 — the normative adapter contract.