Skip to content

Conductor tool surface: enforce the R6 scoping table (path-scoped write, auto-approved reads, Bash denied in repos) #276

Description

@saucam

Implements decision R6 from conductor-design.md §3 (landed in #275). This is the first slice of the reframe and the smallest.

Why

The conductor's tool surface today is an accident, not a decision. #createConductor (session-manager.ts:2733) builds a plain provider session with no capability role and no disallowedTools; allowedTools is a pre-approval list, not a restriction. So the conductor holds the backend's entire default toolset — Write, Edit, Bash, WebFetch — behind nothing but the canUseTool prompt, while its system prompt claims "You have NO tools to edit files or run commands yourself."

R6 retires the read-only-by-construction framing (it was never implemented: WRITE_TOOLS excludes Bash by design, and fleet_spawn(shape:"ship") obtains write on owner approval anyway). What replaces it is scope, not capability — which is both enforceable and strictly narrower than today's accident.

Scope

Where Posture
~/.codeoid-conductor (its own workdir — already a dedicated empty non-repo, non-$HOME dir) write auto-approved
Outside a git repo write approval-gated (today's default, made intentional)
Inside a git repo write never auto-approved; hard-denied where a live session holds that repo's worktree
Bash inside a repo denied — it defeats every path scope above
  • Path-scoped write enforcement in the session's canUseTool gate
  • Fleet read verbs (FLEET_TOOL_NAMES) into the auto-approved set — today they only auto-approve under claude via allowedTools, so every other provider prompts on fleet_list (see Support non-Claude orchestrators for collaborative sessions (mountable fleet MCP) #245)
  • WebSearch / WebFetch auto-approved for the conductor. An always-on assistant that prompts on every fetch is not an assistant, and NETWORK_TOOLS already classifies both as read-only fetches
  • Bash denied inside a repo for the conductor role
  • Worktree-conflict denial: refuse a write into a repo a live session owns
  • Replace the system prompt's "You have NO tools" line with the cost-based rule — delegate when the overhead (session + identity mint + model call + digest round-trip) exceeds the context cost of acting inline. A three-line note is inline; a refactor is not

Where the enforcement goes (resolved, design §12 Q8/Q9)

In the gate, not in tool-safety.ts. The two questions are different kinds:

  • tool-safety.ts stays a pure classifieris this verb read-only? — answerable from a tool name alone, no dependencies, no context. That purity is load-bearing for its tests and is why it can be trusted as a security boundary.
  • The gate makes the contextual decision — may this session write this path right now? — which needs the workdir, the repo boundary, and the live worktree map.

A path field on the capability role was the alternative and it loses: it drags fleet state into the classifier.

Note: this is differentiated, not catch-up

The worktree-conflict check is deterministic because the daemon already knows which session owns which worktree (fleet_list returns it). A kernel sandbox has no concept of a session, so "don't write where another agent is working" is an invariant only a session-owning control plane can enforce. It is not sandbox parity work — peers with better sandboxes structurally cannot do it.

Verification

  • Conductor can write notes in its own workdir with no prompt
  • Conductor write into a repo with a live session's worktree is denied with a clear reason naming the owning session
  • Bash in a repo denied; Bash in its own workdir behaves per mode
  • fleet_list and WebFetch run silently under a non-claude provider
  • tool-safety.ts keeps its no-import property (there is already a test walking that graph for local-auth.ts — mirror it)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions