14 min read

How We Built Charlie, Part 8: Delegation Without Swarms

How explicit delegation keeps parallel work scoped, reviewable, and accountable to one parent Task.

White geometric house with a large central arch, deep blue walls, flowering trees, and stone landscaping

How We Built Charlie continues from Chapter 7: delegation turns a narrow assignment into a child Task with an explicit return path.

A flaky integration test has started blocking pull requests. Fixing it sounds small, but the request actually needs three kinds of work: trace the test’s history across the repository, make a narrow change that does not weaken the assertion, and run enough validation to distinguish a fix from a lucky pass.

Charlie can divide that work. A read-oriented worker can inspect commits and nearby tests. Another worker can review a proposed diff for accidental scope expansion. The parent still owns the customer-facing plan, decides what changes, and reports the result. Each worker returns a bounded handoff rather than becoming another independent owner of the issue.

That is the central idea behind delegation in Charlie. We use a visible task tree with explicit instructions, fresh worker context, scoped capabilities, and a terminal result that returns to the caller. We do not assume that several agents sharing an implicit conversation will somehow converge on the right patch.

The distinction matters because delegation can increase both throughput and confusion. A system that makes child work cheap but leaves ownership vague can produce duplicate edits, contradictory conclusions, or a polished answer assembled from evidence nobody can trace. Our design keeps the parent responsible for synthesis while giving workers enough independence to investigate a narrow question well.

Diagram showing one parent Task delegating bounded child Tasks for exploration and review, with both handoffs returning to the parent for synthesis

The parent owns the objective and external narrative. Child Tasks own bounded questions and return inspectable results.

Entry and worker roles have different jobs

An entry agent starts on the root Task. It is responsible for the end-to-end request: understanding the objective, planning the work, deciding when delegation is useful, integrating child results, and making sure the human-facing outcome is delivered through the appropriate surface.

A worker agent starts on a child Task. Its job is narrower. It receives a delegated objective and returns a result to its caller. A worker may have tools and may even delegate further when configured to do so, but it does not inherit authority over the parent request merely because it contributes useful work.

This produces a practical division of responsibility:

RoleOwnsDoes not own
EntryRoot objective, plan, synthesis, external updates, final outcomeEvery detailed inquiry or review
WorkerThe child goal described in its handoffParent context, plan, public narrative, or siblings

For the flaky-test request, the entry agent might state the plan in the issue, delegate repository archaeology to an exploration worker, inspect the returned evidence, implement the change, and then delegate a focused review of the diff. The exploration worker should not announce a fix. The review worker should not widen the patch because it noticed an unrelated cleanup. The parent has enough context to decide whether either finding changes the plan.

This role split is enforced in the product surface as well as described in prompts. Worker configurations expose different capabilities and tool sets. A read-oriented worker can inspect repositories and platform context without being the right place for arbitrary writes. A review worker can be given the access needed to submit a review while remaining scoped to review behavior. An execution worker can make changes when the parent deliberately delegates implementation. Communication work is separated so that posting to GitHub, Linear, or Slack remains an explicit effect rather than an incidental side effect of research.

Capabilities are not a substitute for instructions. They form an upper bound. A worker with write access still needs a narrow task contract that says what it may change, which branch or artifact it should use, what verification is expected, and what it must return.

Delegation creates a child Task

The runtime does not treat a worker call as an invisible nested completion. Scheduling a worker creates a normal Task with lineage.

The child has its own id. Its parentId points to the Task that delegated it, and its rootId stays connected to the root of the task tree. A direct child of a root therefore has a distinct task identity, a parent link to the root, and the same root identity as the parent. Deeper delegation adds another parent edge while preserving the original root.

type DelegatedTaskIdentity = {
  id: TaskId;
  parentId: TaskId;
  rootId: TaskId;
};

This small set of fields supports several important behaviors. The scheduler can represent the work as a tree. Task-list queries can filter by parent or root. A stop decision can consider ancestor state at defined checks. Product and debugging views can show that one result came from a delegated investigation rather than the root run itself.

The telemetry path records the relationship at delegation time as well. The worker-task-delegated event connects the caller’s run and turn to the delegated task, the selected worker label, and the tool call that initiated it. A task-run timeline can then display the delegation alongside other run events instead of hiding it inside a single model response.

That visibility changes how failures are diagnosed. If a parent produced an incorrect synthesis, we can ask whether the worker received an incomplete contract, returned weak evidence, failed, or was interpreted badly. A flat transcript that only shows the final paragraph makes those cases difficult to separate.

Fresh context is a correctness boundary

A worker starts from a fresh conversation assembled for that worker. The delegated task text is included. The worker’s own system instructions, configured context providers, tools, and capability description are included. The caller’s conversation history, intermediate plan, internal notes, and prior tool results are not copied automatically.

This is intentional. Hidden context inheritance feels convenient until the child relies on a detail that was never part of its stated assignment. It also makes delegation hard to reproduce: the real input becomes “everything the parent happened to know at that moment,” including assumptions the parent may not realize it is passing.

Fresh context forces the caller to decide what the worker needs. The contract may include a repository, file paths, issue or pull request identifiers, a branch name, relevant constraints, and the expected output shape. If a previous finding matters, the parent includes it directly or provides a durable reference.

Boundary diagram showing parent-only conversation and plan on one side, an explicit delegation contract in the middle, and a fresh worker prompt with scoped context and tools on the other

Only the explicit child task and configured worker context cross the boundary. Parent history does not arrive by implication.

Fresh context does not mean context-free execution. Worker context providers can still supply repository guidance, current task metadata, running devbox information, integration help, or selected skills. The distinction is provenance. Configured context is gathered for the worker, while parent reasoning crosses only when the parent puts it in the delegation.

There is a product benefit here: users can inspect the handoff and ask whether it was sufficient. “Review this patch” is weak. “Review commit abc... for assertion weakening and race-prone timing; do not modify code; return findings with file and line references” is testable.

A delegation contract should stand on its own

The task string is the main interface between caller and worker. We treat it like an engineering handoff rather than a casual prompt.

A useful contract usually includes:

FieldPurpose
GoalThe bounded outcome the worker should produce
ContextRepository, artifact, branch, paths, and relevant prior facts
ConstraintsAllowed changes, prohibited scope, safety or editorial rules
IdentifiersStable issue, task, commit, comment, thread, or devbox references
VerificationChecks the worker should run or evidence it should inspect
Return shapeMarkdown, structured JSON, findings, effects, assumptions, and risks
Allowed assumptionsDecisions the worker may make without asking the parent for clarification

For example:

Goal: find why checkout-flaky.spec.ts started failing.
Context: repo acme/storefront, branch fix/checkout-flake,
paths tests/checkout/** and src/payments/**.
Constraints: read only; do not edit files or post comments.
Return: likely cause, supporting commits and lines, two fix options,
and the smallest validation command for each option.

The contract does not need to be long. It needs to remove the ambiguity that would otherwise be supplied by hidden shared context. It should also tell the worker what not to do when the tempting next step exceeds its role.

Contract diagram showing a parent request narrowed into goal, context, constraints, identifiers, verification, and return requirements before scheduling a worker

A worker can act independently when the handoff carries the facts and boundaries required for that independence.

Repository and devbox inheritance are separate choices

Delegation accepts a required repo field whose value is either an explicit owner/repo or null. An explicit value overrides the parent repository. null asks the runtime to inherit the parent’s repository when one exists.

That choice is more important than it first appears. A worker investigating a change in the same repository should normally remain in that repo context. A worker checking an SDK or documentation repository may need an explicit override. Requiring the field makes the caller state which behavior it expects rather than relying on a silent default.

Devbox reuse is narrower. When the parent and child use the same repository and the parent has a devbox, the scheduler may seed that devbox ID into the child for context and efficiency. If the child targets another repository, the parent devbox is not forwarded. If repository identity is missing, devbox inheritance is also withheld.

The Task type describes this as an optimization, not a semantic guarantee. A devbox ID can help a child discover or reuse an execution environment, but delegated correctness must not depend on undocumented in-memory state. The task contract should still name the branch, revision, working tree expectations, or files that matter.

Daemon context follows its own rules. A normal child Task inherits task lineage, not arbitrary daemon authority. Daemon-defined work carries explicit daemon configuration and coordination metadata through scheduler paths designed for it. Treating repo, devbox, and daemon identity as three separate concerns avoids a common error: assuming that “same parent” means “same entire runtime environment.”

Waiting is bounded and produces a tool result

After scheduling a child, the delegation tool waits for that Task to reach a terminal status. The terminal statuses are the same lifecycle outcomes used elsewhere: succeeded, failed, canceled, or timed_out.

If the child succeeds, its terminal text is returned to the parent’s run as the text content of a tool result. The parent model can then read it alongside other tool results and continue. If the child reaches a non-success terminal state, the tool result describes that outcome rather than pretending that no answer arrived.

parent model call
  -> delegate tool call
  -> child Task runs to terminal status
  -> child result becomes parent tool result
  -> parent model call resumes with the handoff

The wait itself is bounded. If the delegation call stops waiting while the child is still running, the runtime can return a still-running state and the parent can use the task ID to wait again. It should not create another child merely because the first one has not finished. The separate wait_for_task operation exists for exactly that continuation.

This protects a subtle idempotency boundary. Worker scheduling uses a deterministic key derived from the caller’s run, tool call, selected worker, and delegated agent identity. Repeating the same delegation operation can converge on the same intended child scheduling effect. Calling delegation again with a new tool call because the parent grew impatient is a different action and may create duplicate work.

Bounded waiting also keeps the parent in charge of policy. A failed exploration may be retried with a better contract. A timed-out review may be replaced with direct inspection. A canceled child may reflect an ancestor stop. None of those outcomes automatically determines the root Task’s final status; the parent decides what the outcome means for the owned objective.

Explore before execute is a product pattern

Many engineering requests benefit from a deliberate research step before code changes begin. The parent can delegate repository exploration to a read-oriented worker, receive a compact evidence bundle, and use it to choose the implementation path.

For the flaky test, the exploration worker might report:

  • the first commit where timing changed;
  • the helper shared by the failing test and two stable tests;
  • the existing repository guidance for retries;
  • a recommendation to wait on an observable state rather than increase a timeout.

The parent can reject part of that recommendation, ask a follow-up child to inspect the helper’s callers, or proceed with a narrow patch. The worker speeds up archaeology without turning repository browsing into an unreviewed code change.

The same pattern applies to unfamiliar services, failing CI, issue triage, or documentation work. Exploration is useful when the uncertainty lies in what should change. Execution is useful once the intended change and its boundaries are clear.

Read-only review keeps another perspective narrow

A review worker provides a second model perspective without inviting overlapping edits. The parent gives it a commit or diff and asks for findings against explicit criteria. The worker can inspect the repository and provider context, but the contract says not to modify the branch.

This avoids a common multi-agent failure mode: two agents edit the same code from different assumptions and leave the parent to reconcile both working trees. Charlie can support parallel work when branches and state are isolated, but delegation does not imply unrestricted parallel editing. Shared-state changes need deliberate isolation, sequencing, or ownership.

A focused review handoff is also easier to evaluate. The parent can check whether each finding points to a real line, whether it blocks the requested objective, and whether the suggested test covers the risk. “Looks good” without evidence is weak whether it came from a human or a worker.

Parent synthesis is where the request becomes coherent

Workers return partial results by design. The parent combines those results with the original request, later follow-ups, its own tool evidence, and the current state of external artifacts.

That synthesis includes judgment calls that should remain visible:

  • whether two worker findings actually agree;
  • whether a suggested fix respects the user’s scope;
  • whether a failed child blocks completion;
  • whether validation is sufficient for the claim being made;
  • whether a customer-facing plan or update needs to change.

The task tree helps here because lineage and timelines preserve where the pieces came from. A parent can say that repository archaeology found a timing regression, review found no assertion weakening, and the final validation passed on a specific revision. It does not need to present the workers as independent teammates talking to the customer. Their contribution is reflected in the evidence and the parent-owned result.

Scoped task trees are easier to trust

Delegation works when it makes ownership more precise. The root Task remains the address for the request. Child Tasks receive bounded objectives. Fresh context prevents accidental reliance on an invisible parent conversation. Capability sets limit the available action surface. Repository and devbox inheritance follow explicit rules. Terminal child results return as tool results for parent synthesis.

This design does not guarantee that workers are correct, that parallel edits merge cleanly, or that every child finishes successfully. It gives those outcomes stable identities and reviewable boundaries. When delegation goes wrong, the system can show which Task ran, what it was asked to do, what tools it could use, what it returned, and how the parent incorporated it.

For engineering work, that is more useful than a crowd of agents producing activity. The goal is a completed request with clear ownership and evidence. Delegation should make that easier to achieve and easier to inspect.

Continue the series

Previous: How We Built Charlie, Part 7: Task Graph vs Transcript Ledger. Next: How We Built Charlie, Part 9: Follow-Ups While Work Is Running. Browse the full How We Built Charlie series.