pub struct CommandBackend { /* private fields */ }Expand description
A subprocess backend: prompt on stdin, completion on stdout.
Implementations§
Source§impl CommandBackend
impl CommandBackend
Sourcepub fn new(argv: Vec<String>, timeout: Duration) -> Self
pub fn new(argv: Vec<String>, timeout: Duration) -> Self
A backend named by its own command line.
The named constructors below are the production path; this is for a backend with nothing better to call itself, which in practice means a test double.
Sourcepub fn with_working_dir(self, dir: &Path) -> Self
pub fn with_working_dir(self, dir: &Path) -> Self
Run the child in dir.
The repository root, for any backend whose prompt names repo-relative paths — which the default one does. See the field for what goes wrong without it, and why it goes wrong silently.
Sourcepub fn with_cancel(self, flag: Arc<AtomicBool>) -> Self
pub fn with_cancel(self, flag: Arc<AtomicBool>) -> Self
Kill the child as soon as flag is set.
pub fn with_timeout(self, timeout: Duration) -> Self
Sourcepub fn claude_cli(fetch: &str) -> Self
pub fn claude_cli(fetch: &str) -> Self
The default: headless, text output, and read-only tools (ADR 0022).
ADR 0010 denied tools outright, because the evaluated grouping tool kept
exiting 1 on stop_reason: "tool_use". Denying them cured it by sending
no tool definitions at all, so the model could not ask. An allowlist is
the other cure: it can ask, and the answer is yes.
fetch is the executable the prompt tells the model to run — normally
this process. The allowlist is derived from it, so the two cannot
disagree about what the model is allowed to invoke.
Nothing here can write. The fetch command reads the document the engine
just wrote; the rest read the repository. git log and git show are
what reach the reason a change was made, which no prompt can carry.
git diff is advertised; the rest are not. The prompt names the
fetch command and git diff, and nothing else.
That is a change of rule, and it is worth saying why. git diff is
advertised because it is now the only way to see what a hunk says: the
fetch command’s diff query is gone, having duplicated class except
for the text. A tool the model must use and is not told about is a tool
it will not use.
It costs an invitation to read the whole repository, and the prompt is what pays for that: it says to read what decides a label and then stop.
It no longer costs a route around the generated content this stage folds
away, though it did when it was written. generated is part of the
shape-class key now (ADR 0004), so no class the model is given contains
a generated file and there is nothing folded left for it to ask
git diff about by accident. The prompt still says not to go looking.
Read, Grep, Glob, git log and git show stay unadvertised for
the original reason: a model that needs the code around a hunk can go
and read it, but it is not sent looking. If you add a tool here, do not
add a line about it to the prompt.
The allowlist is this function’s business, not the user’s, and there is
no config that replaces it. [grouping].agent picks between agents by
name; it used to take a free argv, which handed a stranger’s process the
prompt and none of the allowlist, fetch command or read path the prompt
is written for.
fetch is where a binary lives, so it is the one part of this argv that
says nothing about what the model will do. The cache identity stands a
placeholder in its place: change the allowlist and every cached grouping
is rightly invalidated, move the binary and none of them are.
--permission-mode default is what makes the allowlist mean anything,
and it was missing for two releases (ADR 0033). --allowed-tools ADDS
permissions; it does not cap them. A user whose own settings set
defaultMode to auto, acceptEdits or bypassPermissions was
handing this call an agent that could write, commit and push, and
nothing anywhere said so. default means ask, and a headless call has
nobody to ask, so the answer is no.
It was found by dfr agents --probe, on the first run, against the
agent that had shipped as the only option. That is the whole argument
for the probe existing.
Sourcepub fn codex_cli() -> Self
pub fn codex_cli() -> Self
Headless codex exec, read-only by OS sandbox (ADR 0033).
Codex has no tool allowlist and needs none: --sandbox read-only is
enforced by the kernel — Seatbelt on macOS, bubblewrap on Linux — so the
model may run any command it likes and the writes are refused beneath
it. That is a different boundary from Claude Code’s and an equally real
one, which is why fetch does not appear in this argv at all. The
prompt still names the fetch command; nothing has to permit it.
-c approval_policy="never" is the headless half. Without it a command
the sandbox refuses escalates to a human who is not there, and the call
sits until the deadline kills it. With it the refusal returns to the
model as a tool failure, which is what we want it to see.
It is a config override rather than the --ask-for-approval flag the
docs name, because that flag does not exist on codex exec — it is
on the interactive top-level command only, and codex exec rejects it
outright. Checked against 0.154.0, where passing it is
error: unexpected argument, which is a failure to spawn rather than a
bad grouping.
codex exec already defaults to never asking, so this says out loud
what is currently true anyway. That is the point: a boundary resting on
another program’s default is one release away from being no boundary,
and --ignore-user-config means nothing on disk can move it back.
--color never keeps stdout clean. The response parser takes the text
between the first { and the last }, and an escape sequence inside
that span is a parse error with a sample nobody can read.
The trailing - makes stdin the whole prompt. Codex will otherwise
treat stdin as context for an argv instruction, and there is no argv
instruction here.
Never pass --full-auto, --yolo or
--dangerously-bypass-approvals-and-sandbox: each removes the boundary.
--ignore-user-config and --ignore-rules are the same lesson Claude
Code taught (ADR 0033): the sandbox a flag asks for is not the sandbox
that runs if the user’s own config.toml or execpolicy rules say
otherwise. An argv that can be widened by a file this crate never reads
is not a boundary, it is a request.
Sourcepub fn droid_cli() -> Self
pub fn droid_cli() -> Self
Headless droid exec, read-only by default (ADR 0033).
Droid is the one agent whose boundary is what this function does NOT
pass. Its documented default is read-only file inspection plus git read
operations, with file edits, package installs and git writes blocked,
and a blocked action fails rather than asking — so a bare droid exec
neither writes nor stalls.
Never pass --auto at any level, and never
--skip-permissions-unsafe. Each is the whole boundary, given away.
-o text prints the final message only. - makes stdin the prompt.
Sourcepub fn copilot_cli(fetch: &str) -> Self
pub fn copilot_cli(fetch: &str) -> Self
Headless copilot, read-only by allowlist and an explicit deny
(ADR 0033).
The closest of the five to Claude Code: an allowlist derived from
fetch, so the prompt can never name a command the model may not run.
There is deliberately no -p. Copilot reads the prompt from stdin,
and its own documentation says piped input is ignored when -p is
given. Passing both would send an empty prompt and waste a call.
-s suppresses the session decoration around the reply, for the same
reason Codex gets --color never. --no-ask-user stops the agent
pausing for a human who is not there.
--deny-tool write is belt and braces: write is already absent from
the allowlist, and a deny takes precedence over any allow, so the two
cannot be talked out of agreeing.
Never pass --allow-all-tools or --allow-all-paths.
Sourcepub fn pi_cli() -> Self
pub fn pi_cli() -> Self
Headless pi. Read-only is NOT enforced here (ADR 0033).
Every other constructor in this file hands the model a boundary. This one cannot, and the reason is Pi’s design rather than an oversight in this argv.
Pi ships no sandbox, no per-command allowlist and no approval prompts.
Its -t flag toggles whole tools, and bash is one tool: the model
needs it to run the fetch command and git diff, and the same tool lets
it write a file, commit or push. Nothing but the prompt asks it not to.
Dropping bash would restore the boundary and take the change with it.
The model would be back to grouping from class ids alone, which is the
truncated payload ADR 0022 was written to end — a worse grouping, every
time, in exchange for a risk the prompt never asks anyone to take.
So the author chose this knowingly, and the duty that comes with it is
disclosure: Agent::read_only answers NotEnforced for Pi, and
every place that offers the name says so.
The rest of the argv is hermetic sealing, and it is not decoration.
-nc drops AGENTS.md and CLAUDE.md, -na drops the repository’s
own .pi/ config, and --no-extensions --no-skills drop the user’s.
Each is a file outside the cache key that could otherwise change a
grouping, which is the hole ADR 0022 names and cannot close.
--no-session stops Pi writing a session file for a call nobody
resumes.
Sourcepub fn program(&self) -> &str
pub fn program(&self) -> &str
The program this backend spawns, for a caller checking PATH.
dfr agents says whether each agent is installed, and the answer has to
come from the argv that will actually run rather than from a second list
of executable names that could disagree with it.
Sourcepub fn command(&self) -> &str
pub fn command(&self) -> &str
The whole command line, for a caller showing what will run.
Not LlmBackend::name, which is a product name, and not
LlmBackend::identity, which stands a placeholder where the binary
path is. This is the argv itself, and the two callers that want it are a
spawn failure and dfr agents.