Skip to content
bflo.sh

Orchestration — how work moves through an agent team

Routing by ownership, the phase-start confirmation gate, deterministic workflows, and assigning model and effort per task.

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:

  1. 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).
  2. The lead presents that list to the user.
  3. 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 through contract-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:

  1. contract-owner updates contract/ (OpenAPI/AsyncAPI) and lints it (ddev contract-lint).
  2. contract-owner creates scoped tasks for the affected tiers, referencing the new contract version.
  3. 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:

  • TaskCreate one task per member-sized unit; dependencies encode tier order (contract task blocks implementation tasks).
  • Members message contract-owner for anything outside their ownership; contract-owner converts 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.