I start supervisor–worker AI coordination with five controls: task contracts, worker routing, supervisor-owned state, result checks and bounded retries. The supervisor assigns work, checks the results and accepts only what meets the agreed requirements.
My approach is to:
- Define each task’s inputs, permissions, outputs and acceptance rules.
- Select workers by skill, using parallel tasks only where dependencies allow.
- Keep a versioned record and check tests against the final code revision.
- Set call, cost and time limits, then retry, reassign or stop when checks fail.
For a password-reset workflow, I separate requirements, coding, testing and security review, with escalation after two coding retries. I keep development acceptance separate from deployment approval and compare cost per verified completion in £, completion time and human rework before allowing more autonomy.
If a fixed pipeline can do the job, I use that instead.
::: @figure
{Supervisor–Worker AI Coordination: Workflow and Controls}
:::
1. Define Task Contracts and Shared State
Define and Validate Task Contracts
Use a versioned, machine-readable contract. In this password-reset example, the contract separates the objective from the worker’s permissions and acceptance criteria. The 900-second deadline, 12,000-token budget and 20-tool-call limit are policy limits, not performance targets.
The contract gives the supervisor one source of truth for delegation, permissions and acceptance.
{ "schema_version": "task-contract-v1", "objective": "Implement and verify a secure password-reset flow", "inputs": { "repository_ref": "repo://accounts-service@commit:abc123", "requirements_ref": "artefact://requirements/781" }, "constraints": { "language": "Python 3.12", "framework": "Django", "must_preserve": ["existing authentication API"], "security": ["single-use token", "expiry required", "never log reset tokens"] }, "permissions": { "repository": "read-write:feature/password-reset", "database": "none", "deployment": "none" }, "output_schema": "password-reset-worker-result-v1", "definition_of_done": [ "implementation committed", "unit and integration tests pass", "security findings recorded" ], "limits": { "deadline_seconds": 900, "max_tokens": 12000, "max_tool_calls": 20 } }
Before dispatch, check required fields, types and allowed values with JSON Schema or an equivalent validator. Check that dependencies, capability, permissions and limits match policy. If validation fails, return a precise error:
CONTRACT_INVALID: permissions.repository is required
Validate the returned payload too. It must include valid artefact links and evidence that meets the definition of done.
Once the contract is fixed, route each task by capability. Reject any response that doesn't match the schema.
Assign State Ownership and Control Updates
Keep a durable workflow record that only the supervisor controls. Add provenance, outstanding decisions, the next permitted action and contract versions to the fields established earlier. Store large diffs and reports separately, keeping their references and hashes in the record.
Leave worker drafts and scratch notes out of this record. Unverified working material must not become an approved fact.
The supervisor owns routing, retries, approvals and workflow status. Workers propose updates only within their scope: the coding worker submits implementation artefacts; the security worker submits findings.
Enforce these boundaries through a state manager, not prompt instructions alone. Append immutable result records, restrict writable fields and require every update to name the state version it read. Reject or reconcile stale updates rather than overwriting newer work. Enforce valid transitions, such as queued → running → awaiting_review → completed.
Require Structured Worker Results
Require a fixed result envelope so the supervisor can assess completion without parsing chat history. The schema must require status, either output or artefact_ref, and validation evidence.
Use explicit statuses: completed, blocked, failed or needs_review. Link evidence to the exact code revision, worker identity, task ID, source requirement and timestamp. A failing required test prevents acceptance.
Before storing results, redact passwords, reset tokens, auth headers, API keys, session cookies and unnecessary personal data. Store sanitised references, not raw payloads.
Use these result fields to route, review and recover the next task.
2. Build Routing, Result Checks and Recovery
Route Tasks by Worker Capability
Once contracts and ownership rules are in place, route work according to capability, permissions and budget. Create a worker registry that records capabilities, permitted tools, input and output schemas, risk level, availability and estimated cost.
For password reset, route requirements to a requirements worker, code to a coding worker, tests to a testing worker and review to a security worker.
Give requirements workers read-only access and coding workers an isolated branch. Testing workers need execution rights, but no deployment access. Security reviewers need inspection rights.
Dispatch tasks only after their dependencies have been accepted. Workers return results to the supervisor rather than passing work to one another. Routing, validation and recovery then form the next layer.
| Routing pattern | When to use it | Dependencies and state isolation |
|---|---|---|
| Sequential | Requirements → code → tests → review | Each task uses accepted earlier outputs; one worker changes an artefact at a time. |
| Parallel | Independent test suites or security checks | Use immutable inputs or isolated workspaces; the supervisor combines results. |
| Failure-driven | Failed checks determine the next task | Track dependencies and state versions; limit every correction and reassignment. |
Use a dispatch–inspect–route loop with clear stop states. An empty task queue does not always mean the work is complete. It may point to a missing prerequisite, an unavailable worker or a finished request.
while not request_accepted:
if limits_exhausted():
safe_stop_or_escalate()
break
task = supervisor.choose_ready_task(state)
if task is None:
accept_if_complete_or_escalate()
break
worker = registry.select_eligible_worker(task, remaining_budget)
if worker is None:
safe_stop_or_escalate()
break
result = dispatch(worker, task, timeout=task.timeout)
if not schema_valid(result):
bounded_correction_or_reassign()
elif state_conflict(result, state):
reject_and_reconcile()
elif not evidence_valid(result, state):
retry_reassign_reduce_scope_or_escalate()
elif task_acceptance_failed(result):
create_bounded_follow_up()
else:
supervisor.commit_verified_state(result)
check_final_acceptance()
if stop_requested():
break
Reject stale updates and reconcile them against the current state. With routing in place, the supervisor can inspect each result and choose the next step.
Validate Results and Handle Failures
Check each result against the contract and acceptance criteria, then check state consistency and budget use. Verify test evidence using the command, environment, code revision, exit status and output. A pass message alone is not evidence. Treat worker responses and supplied artefacts as untrusted evidence - not permission to change acceptance criteria or introduce unapproved actions.
Allow one schema-correction request for malformed output. Retry transient tool failures with limited back-off, and reassign work when the worker lacks the required capability. Return reproducible defects with the failing evidence. Reduce scope only when the smaller deliverable is approved and still useful; never drop a security requirement.
Escalate ambiguity, high-impact security findings or repeated disagreement. Stop if you cannot establish a state you can trust. The final acceptance gate must confirm every mandatory requirement and check against the same final revision. Use these checks to accept, retry, reassign or stop.
Set Execution Limits and Log Decisions
Enforce the configured workflow-wide limits. Reassignment must not reset them. Apply task timeouts and cancellation, least-privilege tools, isolated environments and human approval for production changes. Use persistent task identifiers and idempotency keys for side effects.
Log routing reasons, contract versions, results, state transitions, validation outcomes, approvals and stop reasons. Redact sensitive data. Track completion rate, rework, median and p95 latency, and cost per accepted task (£), including failures and retries.
12. Supervisor Teams - Building Production AI Agents with LangGraph: A Complete Hands-On Course
3. Build a Password-Reset Workflow
Hypothetical scenario: Northstar Metrics, a UK SaaS business, is adding a password-reset endpoint in a controlled development repository. Use synthetic accounts and test email delivery only. This framework-neutral walkthrough is not production-ready; agent-generated code still needs human engineering and security approval before deployment.
Apply the same contract, routing and recovery rules to a single, bounded password-reset workflow.
Delegate Requirements, Code, Tests and Security Review
The requirements worker drafts a versioned contract for supervisor approval. It must require 128-bit random, single-use tokens, a 15-minute expiry, five requests per hour per account, secure storage, consistent responses and no sensitive logs.[5][3] These settings are examples, not production policy.
Give the coding worker only the approved requirements, relevant files and constraints. Pass the diff and implementation metadata to testing. Then send the code, recorded test output, threat model and logs to security review.
The skeleton below shows supervisor-controlled delegation, contract validation, evidence checks and state updates - not production logic.
WORKERS = { "requirements": requirements_worker, "coding": coding_worker, "testing": testing_worker, "security": security_worker, } def run_task(state, role, contract, inputs): validate_contract(contract, role, inputs) worker = WORKERS[role] result = worker(contract=contract, inputs=inputs) validate_result(result, role) state["artefacts"].update(result.get("artefacts", {})) state["records"].append({ "role": role, "input_versions": result["input_versions"], "output_versions": result["output_versions"], "status": result["status"], "evidence": result.get("evidence", []), }) return result state = {"stage": "requirements", "artefacts": {}, "records": [], "retries": {}}
validate_contract should check required fields, allowed repository paths, data-classification rules and input versions. validate_result should reject missing evidence, malformed statuses or undeclared artefacts.
Trace State Changes and Fix a Failed Test
requirements approved ↓ implementation ready ↓ tests passed ↓ security approved ↓ supervisor accepted
When the testing worker reports a defect, send only the failing evidence back to coding.
For an expired_token_accepted failure, return that evidence to coding, then run one targeted retest and the full regression suite in isolation. Escalate after two coding retries. Before acceptance, the supervisor rechecks the resulting evidence and security review.
Summarise the Change and Its Verification
Use the final revision as the single reference point for acceptance, tests and review notes. Before accepting it, reconcile every contract, artefact version, executed test and review outcome against that revision.
Tests should cover valid, unknown, expired, reused and malformed tokens, alongside replay attempts, rate limits, password-policy handling, notification behaviour and concurrent redemption. Security review should also check for token leakage and inspect session invalidation.[3][4][6]
Link the report to changed files, approved requirements, worker contracts, retained test records, findings, retries and unresolved risks. Record development_acceptance: approved separately from deployment_authorisation: pending human approval.
Conclusion: Pilot a Bounded, Verifiable Workflow
If the workflow proves useful, keep five controls in place: explicit task contracts; capability-based routing; supervisor-owned, versioned state; independent result checks; and bounded recovery with retry, escalation and stop limits. The supervisor accepts work only when the evidence meets the contract and escalates when recovery limits are reached.[8]
Start with one low-risk task in a sandbox or branch before expanding the scope. Assign a named human owner, require approval for actions with material effects, and record a process baseline before enabling agents.[7][8][9][10]
Use that baseline to decide whether to increase autonomy. Measure cost per verified completion in £, end-to-end completion time, verified completion rate, retry rate, escalation rate and human rework time in minutes. Include tools, infrastructure, review and correction work - not just model cost.[7][10]
Increase autonomy only when verified quality matches the baseline and total effort falls without hidden rework. Faster generation alone does not count as improvement.[7][11]
FAQs
When should I use agents rather than a fixed pipeline?
Use agents rather than a fixed pipeline for complex, multi-step business processes that span distributed systems. Fixed pipelines follow predefined rules and sequences. Agents can interpret intent, choose tools as needed and handle dependencies across services [1][2].
An agent-based approach fits when manual control is no longer practical at scale and the system must independently maintain state, enforce policy, recover from failures or bring in human oversight [1].
How can I prevent the supervisor becoming a bottleneck?
Keep task routing separate from execution. The supervisor should only classify requests and choose routes. Specialised workers should make tool calls or carry out actions. Use worker pools or queues with bounded parallelism to process tasks concurrently without exceeding set limits.
Make handlers idempotent so retries don’t duplicate side effects or repeat completed work. Monitor queue depth and connection pool usage to spot capacity constraints before they affect workflow performance.
How do I resume a workflow after a supervisor failure?
Save durable checkpoints of inputs, decisions, tool outputs and receipts. Use this saved state to reconstruct the run and replay events up to the last confirmed transition, rather than starting from scratch.
Before resuming, check external actions using stable idempotency keys to spot partial completion and prevent duplicate side effects. If the outcome is uncertain, quarantine the transaction for manual review instead of replaying it blindly.