Nested turns
Follow child work back to its caller while the root retains ownership.
T1 · assistant parent: null
└─ T2 · researcher parent: T1
└─ T3 · analyst parent: T2T1 closes its own turn
→ final response + root outcomeDelegation opens a child turn and waits for its outcome. The child has its own model loop, transcript, budget, and record. When it finishes, its result returns to the parent that called it. A completed child does not complete the parent's request.
Start a child through a tool connection
import { asTool } from "@keel-dev/core";
const research = asTool(researcher, {
budget: { steps: 4, timeMs: 15_000 },
deliverable: findingsPart, // plain form — grants 1 nudge round
onError: "feedback",
});
// Include research in the caller agent's tools array.The researcher and findings part are application definitions. The child inherits ambient context; it cannot gain a different identity or wider access through this connection. Its own grants decide what it reads and which tools it can use.
The deliverable contract is build-gated — asTool throws a KeelError at
definition time unless all three hold:
- The part is in the child's
emits. A contract the child never declared cannot be promised on its behalf. - The part declares
persist: "content". A deliverable is substance; an ephemeral part cannot carry it. - The child carries no tool named
submit_<name>. The runtime derives that id for the contract's submit tool, so an agent tool cannot squat on it.
Unset options take deliberate defaults: id run-<agent.id>, access
"public" (the one asymmetry with defineTool, which requires an explicit
rule), input z.object({ brief: z.string() }), retries: 0, and
onError: "feedback". The plain deliverable: part form grants one nudge
round; pass { part, nudges } to set your own.
Return to the immediate caller
The child's result enters the parent's existing transcript. For a deliverable
contract, that result is { ok: true, deliverable: ... }; without one, it
contains the child's text and structured parts. Failures return a cause
unless the connection's policy ends the parent instead.
The parent gets another model step to use the result. If delegation consumed its last available step, the parent cannot simply claim completion from the child's output; it fails its own budget check.
For three levels of delegation, results return from the deepest child to its parent, then to the root. This is a call tree, not a transfer of conversation ownership.
Keep budgets and outcomes distinct
| Concern | Rule |
|---|---|
| Steps | Each child has its own grant; child steps are not automatically deducted from the parent's grant. |
| Time | A child's effective ceiling cannot exceed the caller's remaining time. |
| Retries | Default 0. Each retry opens a fresh child turn on a fresh budget grant — nothing carries over from the failed attempt. |
| Cancellation | Stop propagates through descendants and back to callers, without retrying a stopped child. |
| Failure | Default feedback lets the caller react; onError: "fail" fails the caller. |
| Records | Each turn records its own outcome and model usage, linked by parentTurnId. |
A shorter child timeout can become feedback while the parent still has time to continue. A parent deadline cannot be extended by opening another child. Checks are cooperative at execution boundaries.
Separate visibility from ownership
Child events remain available for inspection, but its text and parts are private to the caller by default. Explicit forwarding can expose compact progress and selected typed parts. It cannot satisfy the parent's completion check or forward a child's outcome as the root outcome.
Read Delegation for forwarding configuration, typed result shapes, and cancellation examples.
Inspect in Studio
Select the child record and note its parent. Check the child's outcome, then return to the caller's later model step and final outcome. An inner failure followed by an honest parent response can be a failed child and a completed root; those records describe different responsibilities.
Next: History and records.
