A typed role fixes LLM, isolation, and gates per dispatch.

Role-typed dispatch

Intent — Dispatch every agent under a typed role (sonnet-active / opus / lint-runner / commit-slave) that determines its LLM, isolation mode, permissions, and which gates apply, so those choices are policy-by-type, not a per-dispatch judgment call.

SummaryA typed role fixes LLM, isolation, and gates per dispatch.
TargetAgent · Context & dispatch substrate
Formquality-gate
EnforcementHard (deterministic) · blocking at each gated op — enforcers refuse calls whose role doesn't match (e.g. lint-all refuses sonnet-active) · human bypass ADA_TOOL_BYPASS_ORCHESTRATOR_CHECK=1 (audit-logged)

Motivation — the failure it kills

Every dispatch bundles several correlated decisions: which LLM (Opus for architecture, Sonnet for mechanical work), whether the agent runs in an isolated worktree or on main, and which compute gates it must respect. Made ad hoc, these decisions drift apart from the work: a Sonnet gets pointed at an architecture change whose incorrect output would cause an incident; a commit agent runs in a worktree when it needs to be on main for the hook to fire; a resource-hungry agent runs the aggregate lint it should never trigger. The failure recurs on every dispatch and is invisible until the wrong-tier output ships or the gate is bypassed.

Why it's not just "pick the right LLM each time"

"Pick the right LLM" is a per-dispatch judgment, and per-dispatch judgment is exactly what drifts under load and gets skipped under time pressure. A role is a typed enum that binds the whole bundle (LLM, isolation, permission mode, and the set of gates) once, and is then enforced: the lint-all role-enforcement gate refuses to run under sonnet-active; commit-slave is defined to operate on main (no worktree) so the commit hook fires; enforcers key on ADA_TOOL_AGENT_ROLE. Four independent judgments a dispatcher re-makes each time drift apart under load. One named type carries the LLM, isolation, permissions, and gate set together, so they cannot silently diverge.

Mechanism

The role is a small closed enum. Each role maps to a policy tuple:

RoleLLMIsolationNotable policy
sonnet-activeSonnetworktreerefused by the aggregate-lint role gate
opusOpusworktreearchitecture / RCA / multi-file
lint-runnerworktreemay run aggregate lint
commit-slaveSonnetnone (on main)commits via an agent commit helper so the hook fires

The dispatch tool's --role <r> selects the tuple; downstream enforcers refuse calls that don't match the declared role. Because the role is declared once and read everywhere, the correlated decisions can't silently diverge.

Prerequisites

Consequences & costs

Known uses

Related mechanisms