Workflows & Orchestration
Decompose complex work into recoverable steps and coordinate execution units through explicit contracts.
How do we decompose complex tasks and coordinate steps or multiple agents?Concept calibration
Sub-agent
- What it is
- A constrained execution unit delegated an explicit goal, inputs, permissions, and return contract.
- What it is not
- It is not mere parallelism; role splitting without contracts amplifies cost and conflict.
Planning
- What it is
- Create an inspectable and updateable execution structure for multi-step, costly, or recoverable tasks.
- What it is not
- It is not mandatory for every agent; simple tasks often do not need a separate planner.
Verification & Evaluation
- What it is
- Use tests, schemas, citations, state, and acceptance rules to verify outputs, then measure system behavior across datasets.
- What it is not
- It is not asking a model for another opinion about its own output; verification must resolve to externally inspectable evidence.
Multi-Agent Collaboration & Orchestration
Use message contracts, shared state, conflict handling, and cost evaluation to decide when roles should split.
Python 3.12 · pytest · Pydantic · SQLite · deterministic MockConcept calibration\n\nPut the concepts used in this module back inside their engineering boundaries before you work with the code.\n\n\n\n### Sub-agent sub-agent\n\n- What it is: A constrained execution unit delegated an explicit goal, inputs, permissions, budget, and return contract, then required to return a verifiable result.\n- What it is not: It is not mere parallelism or a full-context copy for every role; role splitting without boundaries and return contracts increases conflict and cost.\n- Relationship to adjacent concepts: Planning identifies delegable nodes, the harness limits permission and budget, versioned contracts exchange state, and a supervisor handles conflict and partial failure.\n- Where it lives in HeatStack Forge: Forge confines research, implementation, and review roles to different tools and directories; each returns structured evidence that the coordinator verifies before acceptance.\n- Typical misuse and correction: A common misuse starts several roles and expects coordination to emerge. Define task independence, message contracts, and conflict policy, then compare against a single-agent baseline.\n\n### Planning planning\n\n- What it is: An inspectable, updateable execution structure for multi-step, costly, or recoverable work, with explicit dependencies, budgets, state, and replanning conditions.\n- What it is not: It is not mandatory for every agent; simple low-risk tasks may be faster and more reliable with direct execution, so a separate planner must justify its value.\n- Relationship to adjacent concepts: An agent loop can choose the next action directly or execute plan nodes; verification decides completion, while state and checkpoints support local retry.\n- Where it lives in HeatStack Forge: Forge converts complex requests into a task graph with dependencies, budgets, acceptance rules, and recovery points; the executor takes runnable nodes and replans locally from evidence.\n- Typical misuse and correction: A common misuse generates a long static plan and follows it rigidly. Keep plans minimal and state verifiable, with local updates when inputs or tools change.\n\n### Verification & Evaluation evaluation\n\n- What it is: Verification checks one result with tests, schemas, citations, state, or acceptance rules; evaluation measures behavior across runs with datasets and metrics.\n- What it is not: It is not asking the model for another opinion about its own answer; pass or fail must resolve to externally inspectable evidence.\n- Relationship to adjacent concepts: The agent loop relies on verification to stop, RAG needs layered evaluation, planning needs node acceptance, and observability supplies run records.\n- Where it lives in HeatStack Forge: Forge preserves contract tests and evidence for each run, then compares quality, latency, cost, permission, and recovery across fixed scenarios.\n- Typical misuse and correction: A common misuse treats one aggregate score as the whole conclusion. Define layered metrics, failure samples, and version baselines, then review product risk.\n\n
Define bounded roles with explicit goals, inputs, permissions, and output schemas
Start with a concrete problem
Permission escalation during delegation
In the Module 11 Planner/Executor workflow, the executor can perform arbitrary actions without role-based permission restrictions.
Now we introduce multi-agent roles, but direct delegation may grant a sub-agent permissions beyond its role’s capabilities, leading to unauthorized operations.
For example, a read-only researcher role delegated write-code permission would break the system’s security boundary.
This stage requires defining AgentRole and delegate to ensure delegation can only narrow permissions, never expand them.
Make a prediction
In the delegate function, if the caller requests allowed_capabilities that include permissions the role does not have, what should happen?
- Create the delegation anyway, because the caller knows what they are doing
- Raise ValueError, because delegation cannot expand role permissions
- Silently ignore the extra permissions and keep only those the role has
- Log a warning but continue execution
Reasoning guide: The correct option is to raise ValueError. Delegation must be constrained by the role boundary; any permission escalation request should fail immediately rather than being silently ignored or trusted.
What it is
Role contract
A role contract is an immutable data structure that explicitly declares the role’s capability set and the set of output keys it can produce.
A delegation is a constrained instance of a role; it inherits the role’s identity but can only use a subset of the role’s capabilities and must produce keys the role can produce.
The delegate function is the enforcement point of the contract; it validates that the delegation request is within the role boundary before creating the delegation object.
What it is not
Not arbitrary authorization
A role contract is not a simple permission list; it must constrain both capabilities and outputs to prevent the role from being asked to do something it cannot do.
A delegation is not a copy of the role; it cannot have permissions the role lacks, nor can it promise to output keys the role cannot generate.
The delegate function is not a convenience constructor that trusts the caller; it is a security checkpoint that enforces boundaries.
How it relates to neighboring concepts
Relationship between role and delegation
AgentRole defines the static boundary of a role, while Delegation is a specific runtime task assignment that must satisfy the role’s boundary.
The delegate function connects the two: it receives a role and delegation parameters, checks that allowed_capabilities is a subset of role.capabilities, and that expected_output_keys is a subset of role.output_keys.
If the check fails, the function raises ValueError, preventing an invalid delegation from being created and thus ensuring system security.
Boundary decision for this stage: When a delegation request’s permissions or output keys exceed the role boundary, the function must raise an exception rather than silently adjust, because silent adjustment would mask caller errors and potentially introduce security vulnerabilities.
Exchange requests and results through correlatable, validated agent messages
Start with a concrete problem
Responses must come from the delegated agent
In Stage 01, we defined role contracts, but the requests and results exchanged between agents still lack a unified message structure.
If any agent can reply with a message that merely matches the correlation ID, a reviewer could impersonate a researcher and submit a result, causing the task to be incorrectly marked as complete.
This stage requires implementing create_message and correlate functions to ensure that the sender of a response matches the recipient of the original request, and that message kinds are restricted.
Make a prediction
When implementing the correlate function, which condition is necessary to prevent response spoofing?
- The response’s
correlation_idmust match the request’s - The response’s
sendermust equal the request’srecipient - The response’s
recipientmust equal the request’ssender - The response’s
kindmust beresultorerror
Reasoning guide: The correct answer is the second option. While all conditions are part of a valid correlation, the key to preventing spoofing is verifying the response sender identity: the response must come from the request’s recipient. If only the correlation ID and recipient are checked, an attacker can forge the sender.
What it is
What a message contract is
A message contract is a set of rules that ensures communication between agents is correlatable and verifiable.
Each message contains message_id, correlation_id, sender, recipient, kind, and payload, where correlation_id links a request and its response.
The correlate function verifies that a response is legitimate: the correlation ID matches, sender and recipient identities are reversed, and the message kind is result or error.
What it is not
What a message contract is not
A message contract is not a simple data container; it enforces identity and type constraints.
It is not a mechanism where any agent can reply arbitrarily; the response must come from the delegated agent.
It does not guarantee message content correctness, only that the message source and type conform to the protocol.
How it relates to neighboring concepts
Relationship to other concepts
The message contract builds on the role contracts from Stage 01: roles define who can do what, and the message contract defines how information is exchanged.
create_message validates message legality at creation time, while correlate validates response identity at correlation time; together they maintain communication integrity.
Restricting message kinds (request, result, error, cancel) prevents protocol confusion and ensures only expected message types are processed.
Boundary decision for this stage: In correlate, you must check response.sender == request.recipient; otherwise any agent can forge a response. Additionally, create_message must reject unknown kinds and self-addressed messages.
Apply deterministic safety-first rules to conflicting proposals and retain the decision rationale
Start with a concrete problem
When multiple agents propose mutually exclusive options, how can we choose safely?
In the previous stage, multiple agents exchanged proposals through shared state, but no decision was made on which one to adopt.
Now two agents have proposed mutually exclusive options: one wants to delete data (irreversible), and the other wants to archive data (reversible).
If we simply compare evidence counts, the deletion proposal might win because it has more evidence, but that introduces irreversible risk.
We need a deterministic rule: irreversible proposals must be explicitly approved to participate; otherwise, rank by risk level and evidence count.
Make a prediction
In conflict resolution, if there is one irreversible proposal and one reversible proposal, and the irreversible proposal is not approved, what should you do?
- Directly choose the proposal with more evidence
- Exclude the unapproved irreversible proposal, then choose the lowest-risk proposal
- Randomly choose a proposal
- Let the agents vote again
Reasoning guide: The correct answer is to exclude the unapproved irreversible proposal, then choose the lowest-risk proposal. Because irreversible operations cannot be undone once executed, they must be explicitly approved to participate; otherwise, deterministic ranking by risk level and evidence count applies.
What it is
Deterministic safety-first rule
Conflict resolution is a pure function that takes a list of proposals and a flag indicating whether irreversible proposals are approved, and returns a resolution object.
It first filters out unapproved irreversible proposals, then sorts the remaining proposals by risk level ascending, evidence count descending, and agent ID ascending, selecting the first as the winner.
If no proposals remain after filtering, it returns a resolution with reason “approval_required”, indicating that irreversible proposals need approval to proceed.
What it is not
Not a simple evidence count comparison
It is not merely comparing evidence counts, because a higher-risk proposal cannot automatically win even if it has more evidence.
It is also not random selection or another model vote, but deterministic ranking based on explicit rules.
It does not automatically approve irreversible proposals unless the caller explicitly passes approval_for_irreversible=True.
How it relates to neighboring concepts
Relationship with shared state and messages
The conflict resolution function receives a list of proposals from shared state, which are produced by different agents through message passing.
The resolution result can be written back to shared state for later stages, such as executing the winning proposal.
The risk order mapping RISK_ORDER defines the ordering of read_only, reversible, and irreversible, ensuring lower risk is preferred.
Boundary decision for this stage: When the proposal list is empty, return no_proposals; when the filtered list is empty, return approval_required; otherwise return the winner with reason lowest_risk_then_evidence.
Choose the least-privileged capable agent by permissions and load, refusing when safe delegation is impossible
Start with a concrete problem
How can we select an agent that can complete the task while having the least privilege?
In the current system, multiple agent roles have different permission sets: for example, reader has only read permission, while admin has read, write, and process permissions.
When a read-only task arrives, if we select solely by current load, an idle admin would be chosen, exposing unnecessary permissions.
We need to implement a routing function, choose_worker, that, among agents satisfying the required capabilities, prioritizes the one with the fewest permissions to reduce security risk.
Make a prediction
When multiple agents can complete a task, which agent should be selected first?
- The agent with the lowest load
- The agent with the fewest permissions
- The agent with the smallest role ID
- A randomly chosen agent
Reasoning guide: The correct answer is ‘The agent with the fewest permissions’. The principle of least privilege requires granting only the permissions necessary for the task, reducing potential security risks. Load and role ID are used only to break ties.
What it is
What least-privilege routing is
Least-privilege routing is a selection strategy: among all agents that can satisfy the task’s required capabilities, it prioritizes the one with the smallest permission set.
It sorts agents by the size of their capability set (permission breadth), ensuring the selected agent does not have extra permissions beyond what the task needs.
What it is not
What least-privilege routing is not
It is not simple load balancing: the agent with the lowest load may have excessive permissions, and even if idle, it should not be chosen for a low-privilege task.
It is not random selection or sorting by role ID: these methods may ignore permission differences and lead to unnecessary permission exposure.
How it relates to neighboring concepts
Relationship to other concepts
Least-privilege routing builds on role contracts (Stage 01): each agent role explicitly defines its capability set, and the routing function relies on these sets for filtering and sorting.
It complements conflict resolution (Stage 04): conflict resolution handles disagreements among multiple agents, while routing selects the appropriate executor before the task begins.
Boundary decision for this stage: Selection must first minimize permission breadth, then consider load and role ID to break ties.
Classify a collaboration as complete, degraded, or failed from required roles and minimum successes
Start with a concrete problem
When some agents fail, how do we decide whether the collaboration is complete, degraded, or failed?
In Stage 05, the Supervisor could route tasks to different agents, but there was no policy for evaluating the overall result when some agents fail.
Now you need to implement an assessment function assess_outcomes that takes each agent’s outcome, a set of required roles, and a minimum success count, then returns a decision object with status complete, degraded, or failed.
A common mistake is to check only whether the success count meets the threshold, ignoring that some roles are essential; even if the count is sufficient, missing a required role must cause failure.
This stage implements that policy and verifies it with tests: required role failure causes overall failure, optional failure causes degradation, and all successes are complete.
Make a prediction
If required_roles contains builder, but builder fails while two other roles succeed, and minimum_successes=2, what status should assess_outcomes return?
complete, because the success count meets the thresholddegraded, because one role failedfailed, because the required rolebuilderfailedfailed, because the success count is insufficient
Reasoning guide: The correct answer is failed, because failure of a required role must cause overall failure even if the success count meets the threshold. This reflects the policy that role semantics take priority over a numeric quorum.
What it is
Partial failure assessment policy
assess_outcomes is a pure function that computes the collaboration status from three inputs: the list of outcomes, the set of required roles, and the minimum success count.
It first collects the set of successful roles, then computes the missing required roles (required roles minus successful roles).
If the missing required roles set is non-empty, or the success count is less than the minimum, it returns failed status along with the tuple of missing required roles.
Otherwise, if any role failed (i.e., its status is not succeeded), it returns degraded status.
If all roles succeeded, it returns complete status.
What it is not
Not a simple numeric threshold
This policy does not merely compare the success count with minimum_successes; it must first check that all required roles succeeded.
It does not treat every role equally; required and optional roles have different impacts when they fail.
It does not ignore missing required roles just because the success count is sufficient; failure of a required role is fatal.
It does not return complete when any role failed; only all successes are complete.
How it relates to neighboring concepts
Relationship to Supervisor and messages
assess_outcomes is called by the Supervisor after collecting all agent outcomes to decide the next action.
It depends on the AgentOutcome dataclass, which has role_id and status fields.
It returns a CollaborationDecision dataclass with status and missing_required fields.
This policy complements the Stage 05 routing logic by giving the Supervisor failure handling capability.
Boundary decision for this stage: When a required role fails, return failed regardless of success count; when only optional roles fail and the success count meets the threshold, return degraded; when all roles succeed, return complete.
Compose least-privilege routing, message correlation, shared state, and partial-failure policy into an auditable collaboration result
Start with a concrete problem
Why is sequential task execution not enough?
In Stage 06, you implemented a partial-failure policy, but there is still no unified orchestrator to tie all mechanisms together.
The current codebase does not contain forge/multiagent/orchestrator.py, so the tests fail immediately when importing WorkItem and coordinate.
You need to create an orchestrator that selects the least-privileged agent for each work item, records required roles, updates shared state, and assesses overall status from outcomes.
If you simply call handler functions in order without tracking required roles, a required agent failure will incorrectly degrade the run instead of failing it.
Make a prediction
Before implementing coordinate, predict: if a required work item’s handler throws an exception, but the orchestrator does not record that role as required, what will the final run status be?
- The run status will be
failed, because the exception is caught and recorded as a failure. - The run status will be
degraded, because the failed role is not marked as required, so the assessment logic treats it as an optional failure. - The run status will be
complete, because the exception is ignored and other work items succeed. - The run will crash directly, because the exception is not caught.
Reasoning guide: The correct answer is degraded. assess_outcomes uses the required_roles set to decide whether a failure must fail the run; if the set is empty, any failure only leads to degradation.
What it is
The orchestrator is a stateful workflow engine
The orchestrator receives a list of work items, a list of available agents, and a mapping of handler functions, then processes each work item in order.
For each work item, it uses choose_worker to select the least-privileged agent based on required capabilities and records the assignment.
If the work item is marked as required, the orchestrator must add the selected agent’s role ID to the required_roles set.
After successful processing, the orchestrator writes the output to shared state and records a success outcome; on failure, it records a failure outcome.
Finally, the orchestrator calls assess_outcomes to evaluate the overall status and returns a CollaborationRun containing status, assignments, and state version.
What it is not
The orchestrator is not a simple sequential executor
The orchestrator cannot ignore the required flag on work items; a required role’s failure must fail the entire run, not degrade it.
The orchestrator cannot skip shared state updates; every successful work item output must be committed to state for later audit.
The orchestrator cannot decide agent selection logic on its own; it must rely on choose_worker to guarantee least-privilege routing.
The orchestrator cannot silently assign when no agent is available; if no agent satisfies the capabilities, it must raise ValueError.
How it relates to neighboring concepts
How the orchestrator composes existing components
choose_worker comes from supervisor.py; it selects the least-privileged agent based on capability sets and raises ValueError if none is found.
SharedState comes from shared_state.py; it maintains a versioned key-value store, incrementing the version on each update.
assess_outcomes comes from resilience.py; it decides the final status based on the outcome list and the required roles set.
AgentOutcome is the outcome data structure containing role ID and status (succeeded or failed).
The orchestrator connects these components into an auditable end-to-end run.
Boundary decision for this stage: The orchestrator must record the selected agent’s role ID for every required work item and pass that set to the final assessment; otherwise, a required agent’s failure will be incorrectly treated as an optional failure.
Complete the chapter
Add research, implementation, and review roles while proving when multiple agents are not worth using.
Local lab self-check
- Not started
- 2Reading
- 3Lab downloaded
- 4Test result read
- 5Local check passed
verification.json is parsed only in this browser and is never uploaded. This checks the lab contract; it is not server certification or third-party endorsement.
Concept calibration and one-week review
Three questions check the chapter's boundaries, delivery evidence, and recovery path. Answers stay in this browser.