docs: trim generated prose
This commit is contained in:
@@ -1,23 +1,17 @@
|
||||
# @deepseek-ai/dsh-subagent-fork
|
||||
|
||||
The in-process **fork** subagent backend: a [`SubagentProvider`](../subagent/README.md) that runs each child as a child [`Agent`](../../core/agent) **seeded with a prefix of the parent's session log** — so the child inherits the parent's conversation context instead of starting fresh. Shares the run driver (`startInProcessRun`) with [`dsh-subagent-spawn`](../subagent-spawn/README.md); the only difference is the seed. The shared `run.started` boundary resolves only after the seeded child is published, so `subagent/start` observers see a live registry entry.
|
||||
In-process provider that starts a child [`Agent`](../../core/agent) from the parent's completed conversation prefix. It shares [`startInProcessRun`](../subagent-inprocess/README.md) with the [spawn provider](../subagent-spawn/README.md); the seed is the only backend difference.
|
||||
|
||||
## The seed boundary (the crux)
|
||||
## Seed boundary
|
||||
|
||||
At the moment a subagent tool's `execute` runs, the parent's CURRENT turn is open and unbalanced: the log holds the `assistant/message` carrying this spawn's tool-call and the dangling `tool/call` with no `tool/result` yet. Seeding that raw prefix would give the child an open turn that the session constructor and the dev-mode [invariants](../../support/invariants) replay **reject**.
|
||||
|
||||
So the fork seeds only the **balanced completed-turn prefix** — the parent's log up to and including its last `turn/end`, excluding the in-flight turn entirely (`completedTurnPrefix`). Because the live log keeps `seq === index`, the slice is contiguous-from-0 and a valid seed. A parent on its very first (not-yet-complete) turn forks an *empty* seed — i.e. effectively a fresh child.
|
||||
|
||||
The seam this rides on: `CreateAgentOptions.seed` (added on `dsh-agent`, threaded through `AgentLoop.createAgent` → `ctx.sessions.prepare({ seed })`), the same primitive `resume` uses.
|
||||
The delegating tool runs inside an open parent turn whose tool call has no result yet. Forking that tail would create an invalid, unbalanced child log, so the provider copies only the prefix through the last `turn/end`. A first-turn fork therefore starts with an empty seed. `CreateAgentOptions.seed` carries the contiguous prefix into session preparation.
|
||||
|
||||
## Capabilities
|
||||
|
||||
`{ outputSchema: true, depthLimit: true, toolFilter: true, persona: true }` — identical to spawn because the shared driver owns depth, model, persona, tool-filter, and structured-output behavior.
|
||||
`{ outputSchema: true, depthLimit: true, toolFilter: true, persona: true }`
|
||||
|
||||
## Config
|
||||
|
||||
| Key | Meaning |
|
||||
|---|---|
|
||||
| `providerName` | Registry name on `ctx.subagents` (default `fork`). |
|
||||
|
||||
See [`dsh-subagent-spawn`](../subagent-spawn/README.md) for the run lifecycle, model inheritance, and depth tracking — all shared.
|
||||
|
||||
@@ -1,22 +1,8 @@
|
||||
/**
|
||||
* The in-process FORK subagent backend: registers a {@link SubagentProvider} on
|
||||
* `ctx.subagents` that runs each child as a child {@link Agent} SEEDED with a
|
||||
* prefix of the parent's session log — so the child inherits the parent's
|
||||
* conversation context instead of starting fresh. The run mechanics live in
|
||||
* `@deepseek-ai/dsh-subagent-inprocess` ({@link startInProcessRun}); this
|
||||
* backend just computes the seed. The spawn backend is an independent peer over
|
||||
* the same driver.
|
||||
*
|
||||
* The seed boundary is the crux: at the moment a subagent tool's `execute`
|
||||
* runs, the parent's CURRENT turn is open and unbalanced (it holds the
|
||||
* `assistant/message` with this spawn's tool-call, plus the dangling `tool/call`
|
||||
* with no `tool/result`). Seeding that raw prefix gives the child an open turn
|
||||
* the session constructor and the dev-mode invariants replay REJECT. So the
|
||||
* fork seeds only the **balanced completed-turn prefix**: the parent's log up
|
||||
* to and including its last `turn/end`, excluding the in-flight turn entirely.
|
||||
*
|
||||
* Plugin export shape: named `name`/`inject`/`Config`/`apply`, NO default.
|
||||
*
|
||||
* `ctx.subagents` that runs each child as a child {@link Agent} SEEDED with a prefix of the
|
||||
* parent's session log — so the child inherits the parent's conversation context instead of
|
||||
* starting fresh.
|
||||
* @module @deepseek-ai/dsh-subagent-fork
|
||||
*/
|
||||
|
||||
@@ -45,12 +31,9 @@ export const Config: z<Config> = z.object({
|
||||
})
|
||||
|
||||
/**
|
||||
* The balanced completed-turn prefix of `parent`'s log: every event up to and
|
||||
* including the last `turn/end`. Empty if the parent has never completed a turn
|
||||
* (the in-flight turn is excluded, so a parent on its very first turn forks an
|
||||
* empty — i.e. fresh — child). The result is contiguous from seq 0 (the live
|
||||
* log keeps `seq === index`), so it is a valid session seed; the in-flight,
|
||||
* unbalanced turn is dropped so the invariants replay accepts it.
|
||||
* The balanced completed-turn prefix of `parent`'s log: every event up to and including the
|
||||
* last `turn/end`.
|
||||
*
|
||||
* @param parent - the agent whose session log to slice.
|
||||
* @returns the seed events, contiguous from seq 0; empty when no turn has completed.
|
||||
*/
|
||||
|
||||
@@ -132,10 +132,8 @@ describe('dsh-subagent-fork', () => {
|
||||
})
|
||||
|
||||
it('produces an invariant-CLEAN seed: forking mid-turn excludes the open turn', async () => {
|
||||
// Drive the parent so it has ONE completed turn, then start a SECOND turn
|
||||
// that is still open (a hanging model call), and fork while it's in flight.
|
||||
// The fork must seed only the completed first turn — an unbalanced seed
|
||||
// would make the invariants replay throw inside ctx.subagents.start.
|
||||
// Drive the parent so it has one completed turn, then start a SECOND turn that is still
|
||||
// open (a hanging model call), and fork while it's in flight.
|
||||
const { ctx, parent } = await setup([textResponse('done'), 'hang', textResponse('child')])
|
||||
parent.send([{ type: 'text', text: 'q1' }])
|
||||
await parent.whenIdle()
|
||||
@@ -180,12 +178,7 @@ describe('dsh-subagent-fork', () => {
|
||||
})
|
||||
|
||||
it('does NOT return the seeded parent output when the child produces no message of its own', async () => {
|
||||
// Regression: readResult must scope to the child's OWN events (after the
|
||||
// seed). The parent completes a turn with a distinctive assistant message,
|
||||
// then the fork child's own turn finishes with a bare `stop` and NO
|
||||
// assistant/message. Scanning the whole (seeded) log would return the
|
||||
// parent's "parent stale" message with stopReason 'completed'; scoped to the
|
||||
// child's own events the output is empty.
|
||||
// Regression: readResult must scope to the child's own events (after the seed).
|
||||
const { ctx, parent } = await setup([textResponse('parent stale'), emptyStop])
|
||||
parent.send([{ type: 'text', text: 'parent question' }])
|
||||
await parent.whenIdle()
|
||||
|
||||
Reference in New Issue
Block a user