#6298·CodeWhale

Fleet rework: stop defining read-only by command grammar — one grant model, working verify mode, classified tool families

Author: HmbownCreated Sep 17, 2026Updated Sep 17, 2026
Labelsdocumentation

Why (evidence)

  • The 2026-09-17 incident (#6296): a verifier child, refused a chained read-only git command, used an inherited computer-use tool to type into the host Terminal. It was trying to do the task it was given.
  • Our child model (crates/tui/src/worker_profile.rs, fleet/role.rs, execpolicy/command_safety.rs): role postures + PermissionSet ceilings + ShellPolicy + ToolScope::Inherit + deny-list unions + sentinels + a 21-prefix single-command read-only grammar (PARALLEL_READONLY_PREFIXES). A read-only child cannot run cargo, git fetch, or merge-tree — the exact commands a "verify" child is handed — while the catalog still accepts inherited MCP tools (unclassified ⇒ allowed), which is how computer-use got through.
  • Cost of that mismatch today: each 8–10-issue audit child and the verifier burned ~1M–3M input tokens flailing against refusals; one escalated to desktop control. Every refusal without a named alternative invites a workaround.

What the references do (read 2026-09-17, refs/)

  • codex: read-only is a real sandbox (Seatbelt/Landlock/bwrap); workspace-write = explicit writable roots with .git protected. Builds/tests are a sandbox denial resolved by policy-gated escalation with named alternatives (sandbox_permissions: require_escalated, a request_permissions tool). Roles are reduce-only ("never replace the parent's authority"). Per-spawn model + reasoning_effort, validated against the model's supported levels. No native computer-use tool; the namespace name is reserved against impersonation.
  • grokbuild: capability modes are kind-level allowlists with monotonic intersection (ReadOnly absorbs). Read-only excludes the Execute kind entirely — no tests, by construction. Typed denials that name alternatives (Unknown { available } / NotAllowed { allowed }). MCP tools are exempt from the capability filter (None => true) and children inherit the parent MCP pool — the same hole we hit, flagged in their own comment.
  • kimicode: four static preset profiles own their tool lists (explore keeps Bash; plan has none). "Read-only" is prompt-enforced + tool omission, NOT a command classifier; the approval chain decides what Bash may run. Per-spawn model only; thinking follows the caller ("primary") or the model default. Fail-open approval when no approval surface is mounted.
  • omp: per-agent frontmatter tool lists ARE the grant; "read-only" is a derived, presentation-only label (their code says so). Children run yolo with the parent's task call as the authorization boundary. The computer tool is off by default globally and stripped for restricted sessions — the cleanest desktop guard of the four. Per-agent thinkingLevel (scout = smol/medium); the resolved model+effort is surfaced on every run.

Convergent lesson: none of them pretends a command allowlist is "read-only". Read-only is either absent mutating tools (kimicode, omp), absent capability kind (grokbuild), or a real sandbox with named escalation (codex). Our grammar is both too narrow (verifier can't verify) and not a boundary (it is a classifier + charset gate).

Direction for Codewhale

  1. One grant object per childfiles: none|read|write, shell: none|inspect|verify|full, network: bool, desktop: false-unless-granted, tools: preset allowlist. Roles become presets over it; delete the parallel machinery (ceiling + sentinels + posture re-mapping onto a second surface) in favor of one projection: catalog visibility = grant, execution denial = the same grant.
  2. A verify shell mode that works: cargo test/check + git fetch / merge-tree under an explicit bounded write scope (target/, refs) instead of a command allowlist pretending to be read-only.
  3. Classified tool families, fail-closed: MCP/desktop tools are a labeled family; children never receive it unless the spawn grants it with a reason. Unclassified ⇒ not granted.
  4. Denials name the sanctioned alternative ("use Git action=fetch", "Run with workdir") and blocked probes escalate to the parent instead of inviting workarounds.
  5. Thinking: per-role defaults that are cheap where cheap is right (scout ≈ low/medium), per-spawn override, resolved model+tier surfaced in the roster and receipts.
  6. UI/UX: role picker and roster show the effective grant + model + thinking in plain words; spawn errors legible.

Related: #6296, #6015, #5529, #6194.