Adapted from the repo's docs/orchestration.md — Part of Internals: the platform's design set, published as content on the platform it describes.
How work moves through the team. The interactive Claude Code session is the
lead/orchestrator; roster members (.claude/agents/) do the building.
Owner: contract-owner.
Operating model
user request
│
▼
lead (interactive session)
│ decompose → route by ownership
├────────────► single-member task → Agent tool (subagent_type: <member>)
├────────────► multi-member, dynamic → Agent Teams: named teammates + SendMessage
│ + TaskCreate/TaskList for tracking
└────────────► multi-member, deterministic shape (fan-out / pipeline / gate)
→ Workflow tool (script below)
▼
lead integrates, verifies against contract, reports to user
Route most build work through the roster. The lead implements directly only for trivial single-file edits, pure reading/reporting, or answering questions.
Phase-start confirmation gate
Applies to every phase in the delivery roadmap
(master-blueprint.md §11).
A phase does not open on the lead's initiative — it opens on the user's:
- The phase's owning member(s) draft the task list for that phase — planning only:
no implementation, no file edits. Each task carries its owning member (
agentType) and a proposed model + effort (per Model & effort policy below). - The lead presents that list to the user.
- No execution begins until the user explicitly confirms the list. Silence is not confirmation; the lead waits.
This gate sits above task routing — it decides that a phase starts, not how its tasks are
dispatched (that is the operating model and routing table above). It does not narrow
intra-task autonomy: once the user confirms a phase, members are fully autonomous within
their individual tasks (working-agreement.md). The gate governs
starting a phase; the working agreement governs working within a confirmed one.
Executing a confirmed phase — run it as a Workflow. On confirmation the lead executes the
phase with the Workflow tool, not by spawning members one-by-one: a deterministic script
maps each confirmed task to an agent(prompt, { agentType, model, effort, … }) call —
honoring the model + effort proposed in the list — wired into pipeline() / parallel()
structure that follows the task DAG (contract/tier order as gates, independent tasks in
parallel; the Multi-member, deterministic — Workflow tool skeleton above is the template).
This is what makes the model & effort policy real at phase scale:
one-by-one lead spawns all inherit the lead's tier, whereas a Workflow lets mechanical tasks
run cheaper and verification/arbitration run at higher effort, task by task. Direct
Agent-tool spawns stay the right tool for trivial one-offs, follow-ups within a turn, and
continuing a named member — not for executing a whole confirmed phase.
Effective from Phase 2 (Backend) onward — Phases 0–1 predate the rule. The original roadmap (Phases 0–4) is delivered; the gate applies unchanged to any future roadmap extension.
Tiers and direction
frontend/ ──► contract/ ◄── backend/ (both couple ONLY through contract/)
│
Http → Domain → Data (inside backend/)
│
infra/ (supports everything, depends on nothing)
- Requirements and change requests flow along the arrows only. A member never edits a foreign tier's files and never demands changes in one directly.
- When a member hits a constraint outside its walls, it raises the constraint as a task
to
contract-owner, who arbitrates. All cross-tier coordination goes throughcontract-owner— direct cross-file edits across ownership boundaries are a rules-of-engagement violation.
Contract-first sequence
Any work that touches an interface between members follows this order, with no overlap:
contract-ownerupdatescontract/(OpenAPI/AsyncAPI) and lints it (ddev contract-lint).contract-ownercreates scoped tasks for the affected tiers, referencing the new contract version.- Implementing tiers build to the published version. Discovered contract defects go back as tasks — implementations never diverge from the spec to "make it work".
Routing table
| Work smells like… | Route to |
|---|---|
| New/changed endpoint shape, event shape, error format, versioning | contract-owner (always first) |
| Controllers, routes, validation, serialization, HTTP tests | backend-api |
| Business rules, use cases, invariants, domain events, ports | domain-engineer |
| Migrations, models, port implementations, Redis/queues, query perf | data-engineer |
| Pages, components, data fetching, UI state, frontend tooling | frontend-engineer |
| DDEV, services, CI, bootstrap, root tooling | platform-engineer |
| Spans two+ of the above | decompose; interface part goes to contract-owner first |
Mechanics
Single-member tasks — Agent tool
Spawn the owning member with subagent_type set to the roster name. Give the agent a
stable name so follow-ups can continue the same context via SendMessage instead of
respawning. Members run in the background by default; the lead is notified on completion
and relays results.
Multi-member, exploratory — Agent Teams
For work whose shape emerges as it goes (a feature negotiated between producer and consumer), spawn named teammates and coordinate with tasks/messages:
TaskCreateone task per member-sized unit; dependencies encode tier order (contract task blocks implementation tasks).- Members message
contract-ownerfor anything outside their ownership;contract-ownerconverts requests into contract changes or routed tasks. No member edits another member's files — if a diff would touch two ownership areas, it is two tasks. - Parallel members that might touch adjacent files run with worktree isolation.
Multi-member, deterministic — Workflow tool
When the shape is known up front (contract gate → parallel implementation → verify), encode it as a workflow so ordering is guaranteed. Reference script:
export const meta = {
name: 'contract-first-feature',
description: 'Publish contract change, then implement backend + frontend in parallel, then verify',
phases: [
{ title: 'Contract' },
{ title: 'Implement' },
{ title: 'Verify' },
],
}
// args: { feature: string, notes?: string }
phase('Contract')
const contract = await agent(
`Feature: ${args.feature}. ${args.notes ?? ''}
Update contract/ (OpenAPI/AsyncAPI) for this feature, bump version, lint with Spectral.
Return the published version and a summary of the interface.`,
{ agentType: 'contract-owner', label: 'contract:publish',
schema: { type: 'object', required: ['version', 'summary'],
properties: { version: { type: 'string' }, summary: { type: 'string' } } } }
)
phase('Implement')
const [backend, frontend] = await parallel([
() => agent(
`Implement contract v${contract.version} for: ${args.feature}.
Interface summary: ${contract.summary}.
Transport layer only; file tasks for domain/data needs; run HTTP tests.`,
{ agentType: 'backend-api', label: 'impl:backend', phase: 'Implement', isolation: 'worktree' }),
() => agent(
`Consume contract v${contract.version} for: ${args.feature}.
Interface summary: ${contract.summary}.
Regenerate types from contract/, implement UI + data layer, run lint/build.`,
{ agentType: 'frontend-engineer', label: 'impl:frontend', phase: 'Implement', isolation: 'worktree' }),
])
phase('Verify')
const verdict = await agent(
`Verify tier direction and contract conformance for: ${args.feature} (contract v${contract.version}).
Backend report: ${backend}
Frontend report: ${frontend}
Check: no forbidden imports, responses match OpenAPI, error model is RFC 9457. Report violations.`,
{ label: 'verify:conformance' }
)
return { contract, backend, frontend, verdict }
Adapt the same skeleton for domain/data pipelines (contract → domain ports → data
implementation → API wiring) by chaining agent() calls with agentType per tier owner.
Model & effort policy
Role definitions in .claude/agents/ deliberately pin no model or reasoning effort.
A pinned model in a reusable template rots as model families evolve, and the right tier
depends on the task, not the role. Resolution falls through: dispatch-time override →
agent frontmatter → inherit from the lead session (.claude/settings.json).
Policy: the lead assigns model and effort per dispatched task, not per role.
- Default: inherit. Correct for most build work — omit overrides unless there is a reason.
- Mechanical, well-specified work (scaffolding, codegen regeneration, lockstep renames,
doc formatting) → smaller model and/or
effort: low. - Contract design, breaking-change review, arbitration, debugging → inherit model; raise effort if the session default is low.
- Verification / adversarial-review stages in workflows → inherit model; the highest effort tier the stage merits.
Mechanics: Agent tool → model parameter; Workflow agent() → model / effort
options. If a role turns out to consistently warrant a non-default assignment, pin it
in that member's frontmatter and record the reason in docs/team.md — that is the
signal to move the decision from dispatch time to definition time.
At phase scale this policy is applied through the confirmed phase task list: each task's
proposed model + effort is carried straight into the Workflow agent() calls that execute the
phase (see Phase-start confirmation gate). That Workflow run,
not one-by-one Agent spawns, is the primary place these per-task assignments take effect.
Escalation & arbitration
| Situation | Route |
|---|---|
| Contract defect found during implementation | Task to contract-owner |
| Cross-tier requirement | Task via contract-owner to the owning tier |
| Constraint that blocks a higher tier | Task to contract-owner with the constraint |
| Interface dispute | contract-owner arbitrates; significant rulings become ADRs |
| Scope change / destructive action needed | Stop; surface to the user |
Version-control discipline (applies to lead and every member)
- Never
git push. - Commit only when the user explicitly asks. Default is: leave the working tree dirty and report what changed.
- Workflows and teammates inherit this rule; worktree isolation does not change it.
- The full agreement lives in
docs/working-agreement.md.