You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
shell is classified ToolRisk::Risky, so when a model turn emits several shell calls they execute sequentially. Only Safe tools are fanned out concurrently (futures::future::join_all); Risky tools run one-at-a-time through the approval gate. As a result, batched/parallel read-only shell calls (e.g. inspecting several files at once) can never run concurrently — even in unsafe_auto. This proposes letting read-onlyshell invocations be treated as Safe so they execute concurrently, mirroring the read-only inspection tools that already fan out.
Where this lives
Per-turn partition + the two execution paths — crates/adapter-zarvis/src/agent.rs:
Safe bucket → join_all (concurrent), ~L797–819
Risky bucket → sequential for loop via run_one_tool (approval gate), ~L841–870
unsafe_auto removes the approval prompt but does not change the Safe/Risky classification, so shell still serializes there too.
Motivation
Benchmarking zarvis vs the Codex CLI (same model + backend, only the harness differs), codex parallelizes ~27% of its turns — multiple exec_command reads at once — while zarvis stays ~1 tool/turn. There are two independent gaps:
the model rarely emits parallel calls under zarvis (separate, prompt/tool-surface issue), and
even when it does, zarvis serializes them because shell is Risky.
This issue is about (2), the execution side. Prior art: openai/codex PR #10505 ("Enable parallel shell tools") marks its shell tools supports_parallel_tool_calls = true so its executor runs them concurrently.
Proposal (options, not mutually exclusive)
Read-only command detection (recommended): treat a shell call as Safe only when its command is provably side-effect-free — an allowlist of cat/sed -n/rg/grep/ls/find/head/tail/wc/git log|diff|show, with no redirects, no $(...)/backticks, no ;/&&/| into a mutator. Default to Risky on any doubt.
Explicit read_only: true arg the model sets; only those fan out. Simpler than parsing, but trusts the model.
shell runs arbitrary commands; concurrent mutating shell can race (a build vs rm, two writers to one file) and can't be gated one-by-one for approval. Any downgrade must be conservative — only provably read-only commands, default Risky.
Naive parsing is fooled by command substitution, redirects, and chaining; the allowlist must reject those outright.
Need a concurrency cap and sane interleaving of concurrent output.
A blanket downgrade of shell to Safe is a non-starter — it would bypass approval for destructive commands in manual/auto_review.
Acceptance
Independent read-only shell calls in one turn execute concurrently (duration test in the spirit of codex's tool_parallelism suite).
Any non-read-only or ambiguous command stays Risky and serial.
No change to approval semantics for mutating commands.
Note
This is the execution-side enabler only; it pays off in combination with the model actually emitting parallel shell calls, which under zarvis it currently mostly doesn't. Tracked/most valuable alongside that separate investigation.
Summary
shellis classifiedToolRisk::Risky, so when a model turn emits severalshellcalls they execute sequentially. OnlySafetools are fanned out concurrently (futures::future::join_all);Riskytools run one-at-a-time through the approval gate. As a result, batched/parallel read-only shell calls (e.g. inspecting several files at once) can never run concurrently — even inunsafe_auto. This proposes letting read-onlyshellinvocations be treated asSafeso they execute concurrently, mirroring the read-only inspection tools that already fan out.Where this lives
crates/adapter-zarvis/src/agent.rs:join_all(concurrent), ~L797–819forloop viarun_one_tool(approval gate), ~L841–870shellrisk =Risky—crates/adapter-zarvis/src/tools/shell.rseffective_risk's auto-approve downgrade only matchesedit_file(path-scoped), nevershell—crates/adapter-zarvis/src/tools/mod.rs(auto_approve_covers)unsafe_autoremoves the approval prompt but does not change the Safe/Risky classification, soshellstill serializes there too.Motivation
Benchmarking zarvis vs the Codex CLI (same model + backend, only the harness differs), codex parallelizes ~27% of its turns — multiple
exec_commandreads at once — while zarvis stays ~1 tool/turn. There are two independent gaps:shellisRisky.This issue is about (2), the execution side. Prior art: openai/codex PR #10505 ("Enable parallel shell tools") marks its shell tools
supports_parallel_tool_calls = trueso its executor runs them concurrently.Proposal (options, not mutually exclusive)
shellcall asSafeonly when its command is provably side-effect-free — an allowlist ofcat/sed -n/rg/grep/ls/find/head/tail/wc/git log|diff|show, with no redirects, no$(...)/backticks, no;/&&/|into a mutator. Default toRiskyon any doubt.read_only: truearg the model sets; only those fan out. Simpler than parsing, but trusts the model.read_file/list_dirfor the codex-style minimal surface, so this partly reverses that.Risks / why
shellisRiskytodayshellruns arbitrary commands; concurrent mutating shell can race (a build vsrm, two writers to one file) and can't be gated one-by-one for approval. Any downgrade must be conservative — only provably read-only commands, defaultRisky.shelltoSafeis a non-starter — it would bypass approval for destructive commands inmanual/auto_review.Acceptance
shellcalls in one turn execute concurrently (duration test in the spirit of codex'stool_parallelismsuite).Riskyand serial.Note
This is the execution-side enabler only; it pays off in combination with the model actually emitting parallel
shellcalls, which under zarvis it currently mostly doesn't. Tracked/most valuable alongside that separate investigation.🤖 Generated with Claude Code