|
|
|
@@ -8,13 +8,15 @@ The shared **in-process subagent run driver**. A library with no provider or imp
|
|
|
|
|
|
|
|
|
|
Runs a child as a child [`Agent`](../../core/agent) on the same cordis context (`ctx.agents`):
|
|
|
|
|
|
|
|
|
|
1. reads every public request and seed field once before asynchronous owner setup: the parent and signal remain identity capabilities, while tool filter, seed, agent options, output schema, and prompt are each materialized by the shared one-pass lossless-JSON snapshot. It rejects a malformed `request.maxDepth`, validates the parent's `subagentDepth`, computes child depth = `depthOf(parent) + 1`, rejects cap overflow with `SubagentDepthError`, reports an invalid schema as `OutputSchemaError`, and derives both the child prefix and `seedLength` from the same detached seed;
|
|
|
|
|
1. reads every public request and seed field once before asynchronous owner setup: the parent and signal remain identity capabilities, while tool filter, seed, agent options, output schema, and prompt are each materialized by the shared one-pass lossless-JSON snapshot. It rejects malformed `request.maxDepth` and `persona` values, validates the parent's `subagentDepth`, computes child depth = `depthOf(parent) + 1`, rejects a child depth outside the safe-integer domain with `RangeError`, rejects a defined `maxDepth` cap breach with `SubagentDepthError`, reports an invalid schema as `OutputSchemaError`, and derives both the child prefix and `seedLength` from the same detached seed;
|
|
|
|
|
2. first installs provider ownership, then attaches the request abort listener and creates one run-owner Cordis fiber under `parent.ctx`; an already-unloading provider therefore leaves no child or orphaned listener. Async child creation goes through that fiber's `ctx.agents` service with fresh IDs, lineage/seed, inherited model, and an unpublished setup transaction for persona, tool restriction, and structured output. Parent teardown, provider teardown, manual `run.dispose()`, and cancellation before readiness all dispose this exact node, preventing publication after it becomes inactive and sharing the same quiescence boundary. `startInProcessRun` still returns its `SubagentRun` immediately: `run.started` resolves only after `ctx.agents.create()` has published the child and rejects when pre-readiness cancellation rolls the transaction back;
|
|
|
|
|
3. drives the one-shot: `child.send(prompt)` then `await child.whenIdle()` (ordering matters — `send` enqueues synchronously, so `whenIdle` observes the queued work and resolves on the child's `running → idle` transition, never before the turn starts); there is deliberately NO re-prompt for a structured child that finished cleanly without calling `structured_output` — the shortfall maps to an `error` result for the parent;
|
|
|
|
|
4. reads the result, scoped to the child's OWN events (everything at or after `seedLength`, so a seeded child that produced no message of its own never returns the seeded parent's last message): the last `assistant/message` content (deep-cloned — the log is frozen) and the last `turn/end.reason` mapped to a `SubagentStopReason`. A structured run surfaces the captured value as `result.structured`; a structured child that finished cleanly WITHOUT ever capturing settles `error` (a clean finish without the demanded result is a failure, not a success with a missing field).
|
|
|
|
|
|
|
|
|
|
`SubagentService` waits for `run.started` before emitting `subagent/start`, so a synchronous start observer can resolve the published child with `ctx.agents.get(run.id)`; the result driver awaits the same boundary before sending the prompt. An attempt that never publishes rejects readiness and emits no false start/end pair; its result reports a deliberate cancel/dispose as `aborted` and propagates an infrastructure fault. `dispose()` awaits creation or rollback and then delegates to `AgentHandle.dispose()` (stop and drain → remove agent → detach session → unwind scope). Before readiness, `cancel()` deactivates the creation owner: before creation notification begins, no agent/session lifecycle edge escapes; if cancellation is triggered synchronously by a creation observer, every begun edge is paired by rollback and the driver never unlocks or starts. After readiness, cancellation reaches the live child immediately. Either path records the cancellation, so a cancel landing before any `turn/end` settles `aborted`, honoring the cancel contract rather than the generic no-turn `error`.
|
|
|
|
|
|
|
|
|
|
The child receives a fresh flat registration scope. Its `toolFilter` masks the live global tool layer and scope-local registrations are merged afterward; parent ownership does not import the parent's tool restrictions or establish an authority subset. The [agent-scope RFC](../../../docs/rfc/implemented/architecture/2026-07-08-agent-scope-contexts.md#security-and-authority-are-explicit-non-goals) owns that explicit non-goal.
|
|
|
|
|
|
|
|
|
|
### `InProcessRunOptions`
|
|
|
|
|
|
|
|
|
|
`{ seed?: SessionEvent[] }` — the optional child-session seed: absent for spawn, or the parent's balanced completed-turn prefix for fork.
|
|
|
|
@@ -32,8 +34,8 @@ Runs a child as a child [`Agent`](../../core/agent) on the same cordis context (
|
|
|
|
|
|
|
|
|
|
### `depthOf(agent): number`
|
|
|
|
|
|
|
|
|
|
Delegation depth rides on a merge-extensible `AgentOptions.subagentDepth` field (0 for a top-level agent, parent + 1 for a child), so a nested spawn reads its parent's depth from `parent.options.subagentDepth`. `depthOf` reads it (absent ⇒ 0).
|
|
|
|
|
Delegation depth rides on a merge-extensible `AgentOptions.subagentDepth` field (0 for a top-level agent, parent + 1 for a child), so a nested spawn reads its parent's depth from `parent.options.subagentDepth`. `depthOf` treats only `undefined` as absent (top-level depth 0) and rejects every malformed present value instead of letting it disable the cap comparison.
|
|
|
|
|
|
|
|
|
|
### `SubagentDepthError`
|
|
|
|
|
|
|
|
|
|
Thrown by `startInProcessRun` when a spawn would exceed the request's `maxDepth` cap; carries `attemptedDepth` and `maxDepth`.
|
|
|
|
|
Thrown by `startInProcessRun` when a spawn would exceed the request's defined `maxDepth` cap; carries `attemptedDepth` and `maxDepth`. A valid parent at `Number.MAX_SAFE_INTEGER` instead produces `RangeError`, because its child depth cannot be represented within the stored safe-integer domain even when `maxDepth` is omitted.
|
|
|
|
|