Orchestrata Ultimate › Gates › dispatch-brief
Gate: dispatch-brief
At a glance
| Purpose | No worker starts from an under-specified brief: every Agent spawn carries GOAL/FILES/CONSTRAINTS/DONE WHEN/VERIFY, coding and testing briefs carry the AST of the module they change, and builder/wirer/refactorer dispatches carry design authority. |
| Where it runs | PreToolUse hooks of the orchestrata plugin (plugins/orchestrata/hooks/hooks.json): task_context_hook.py on Agent|Edit|MultiEdit|Write|NotebookEdit; _dispatch.sh require-design-before-build.sh on Agent; _dispatch.sh workflow-default-sonnet.sh on Workflow. |
| Trigger | Every Agent tool call (forks included) and every Workflow script. |
| Exact command | No manual command. The hooks receive the tool payload on stdin. |
| Blocks | task_context_hook.py answers permissionDecision: deny with a reason; require-design-before-build.sh and workflow-default-sonnet.sh exit 2 with remediation on stderr. All three fail open on malformed input. |
| Ruling / ADR | RUL-022 and ADR-0048 (a ratified ADR is design authority); RUL-026; hook placement RUL-035 / ADR-0070. The five labels have no RUL page; their origin is the hook's own comment (below). |
The flow
What task_context_hook.py checks
Read with the AST toolkit (claude/hooks/task_context_hook.py; the plugin copy is
byte-identical, and the deployed ~/.claude/hooks copy is identical after line 1,
which deploy/install-claude-config.sh rewrites to the deployed interpreter).
- The five labels.
REQUIRED_FIELDS = ("GOAL:", "FILES:", "CONSTRAINTS:", "DONE WHEN:", "VERIFY:"). The test isf not in prompt.upper(): each label must appear as that exact substring, case-insensitive. A qualifier between the word and the colon (FILES (read-only):) is a missing field. The comment above the tuple gives the reason: "briefs with 'minimal info' rebuilt the wrong thing, unstated done-criteria produced 'mostly done', and an unnamed verification command produced claims nobody had run." - Categories.
categorize()uses the one shared pattern table imported fromworkflow_context_inject.DOMAINS("A second copy would drift"). - Module context.
module_context_fortakes.pypaths from theFILES:field first (prose before FILES: once burned the six-path budget on files merely mentioned, 2026-08-19), resolves them againstCLAUDE_PROJECT_DIR, the git root above cwd, then cwd, and runstools/module_context.py: an existing file gets "AST, class diagram, Prolog dependency contracts, blast radius"; a new file gets the public API of the package it joins; a package resolving to$HOMEis ignored. The output is the=== MODULE CONTEXT …block seen at the end of every worker brief. - Mandatory AST. For categories in
AST_REQUIRED_CATEGORIES = {"development", "testing"}a brief with no resolvable.pypath is denied. - No redundancy. Each injected block is hashed into a per-session ledger under
$XDG_RUNTIME_DIR/claude-inject-ledger, so the same module context never enters one session twice. - On success it returns
permissionDecision: allowwithupdatedInput(the rewritten prompt). "Never blocks: any failure returns the tool call unmodified" (module docstring) — the denials above are deliberate verdicts, not failures. - The same hook also runs on Edit/Write/NotebookEdit (
_inject_for_edit) with a 10 s cap (EDIT_TIMEOUT) instead of 30 s (MODULE_TIMEOUT); that path injects context and is not a brief gate.
What require-design-before-build.sh checks
Its decision table (header, mirrors plans/contracts/rul-022-design-gate.contract.json):
- tool is not
Agent→ allow. subagent_typenot in{builder, wirer, refactorer}→ allow. The comparison is an exact set membership, so a namespaced type such asorchestrata:builderis not in the set (read from the script, 2026-10-23; whether such dispatches occur was not measured).- a line matching
^DESIGN_WAIVER:[ \t]*(.*)$with a non-empty reason → allow, reason echoed to stderr. A bareDESIGN_WAIVER:does not count, and a waiver embedded mid-sentence does not match (memoryagent-brief-hook-fields, 2026-10-06). - an existing
plans/contracts/…path in the prompt → allow. --requirements,--spec-fileand--artifact-fileall named andscripts/factory_decision.py decideexits 0 → allow.- otherwise →
BLOCKED (RUL-022): implementation dispatch with no ratified design, exit 2, with the remediation (dispatchanalystfor a contract, rungen-skeleton, re-dispatch naming it, or addDESIGN_WAIVER: <reason>).
It runs through _dispatch.sh, which scopes plugin hooks to the orchestrata tree.
PreToolUse(Agent) payloads carry no path, so the dispatcher uses a per-session memo
of the repo the session last operated on; in a session that has not yet touched
the repo through a path-bearing call, the guard does not fire (_dispatch.sh
header: measured 2026-08-23, 14 of 25 project hooks were dark in $HOME sessions
before the memo). task_context_hook.py is registered directly and fires in every
session.
workflow-default-sonnet.sh (Workflow tool)
Blocks (exit 2) a Workflow script with an agent() call that names no model,
unless it is a resumeFromRunId replay. Measured cause (header): on 2026-08-05 two
scripts with no model ran 58 agents, all on Opus, 10.4M subagent tokens, for
Sonnet-shaped work. "The rule is 'state the tier', not 'always Sonnet'."
Evidence of the gate biting
| When | What was refused | Source |
|---|---|---|
| 2026-10-06 | Two builder dispatches: missing labels, and a waiver inside the GOAL line | memory agent-brief-hook-fields |
| 2026-10-22 (D16) | A rehearsal dispatch, twice: FILES named no .py path, then the label read FILES (read-only context; ...): |
IMPROVEMENT-LOG D16 |
| 2026-10-22 (D19) | A test-writer dispatch for FILES (yours alone; ...):, an hour after the template rule against it |
IMPROVEMENT-LOG D19; mechanism queue item "brief_lint.py label check" |
| 2026-10-22 | Three bounces for label variants such as VERIFY (budget…): |
memory better-briefs |
| 2026-10-23 ~04:50 UTC | Three fork dispatches by the documentation session: "Agent brief is missing required field(s): GOAL:, FILES:, CONSTRAINTS:, DONE WHEN:, VERIFY:" — the hook applies to forks too | this session |
Typical failures and how to fix them
| Message | Fix |
|---|---|
Agent brief is missing required field(s): … |
Put each label literally, e.g. FILES: with nothing between the word and the colon. Copy the skeleton from the brief template (briefs). |
… the brief must carry the AST of the module being changed — and FILES names no .py path at all |
Name at least one .py path in FILES:, even for read-only work (mark it read-only after the path). |
module_context.py is MISSING at … |
The plugin's delivery layout is broken; regenerate and redeploy the plugin (the message says so), or embed a === MODULE CONTEXT block. |
BLOCKED (RUL-022) |
First line DESIGN_WAIVER: <ADR/RUL/ruling and date>, or name a ratified plans/contracts/ file. |
Workflow blocked for a missing model |
Pass model: on every agent() call. |