diff --git a/docs/module-graph.md b/docs/module-graph.md index 0724130ced..a91c5bed98 100644 --- a/docs/module-graph.md +++ b/docs/module-graph.md @@ -60,13 +60,13 @@ graph TD agent-core --> system-prompt agent-core --> tool-bash agent-core --> tools + subagent-inprocess --> agent + subagent-inprocess --> llm + subagent-inprocess --> session + subagent-inprocess --> subagent subagent-mock --> agent subagent-mock --> llm subagent-mock --> subagent - subagent-spawn --> agent - subagent-spawn --> llm - subagent-spawn --> session - subagent-spawn --> subagent tool-subagent --> agent tool-subagent --> llm tool-subagent --> subagent @@ -82,7 +82,9 @@ graph TD subagent-fork --> agent subagent-fork --> session subagent-fork --> subagent - subagent-fork --> subagent-spawn + subagent-fork --> subagent-inprocess + subagent-spawn --> subagent + subagent-spawn --> subagent-inprocess ``` | Package | Depends on | @@ -108,9 +110,10 @@ graph TD | `subagent` | `agent`, `llm`, `tools` | | `tool-bash` | `agent`, `bash`, `llm`, `tools` | | `agent-core` | `agent`, `agent-loop`, `invariants`, `llm`, `session`, `system-prompt`, `tool-bash`, `tools` | +| `subagent-inprocess` | `agent`, `llm`, `session`, `subagent` | | `subagent-mock` | `agent`, `llm`, `subagent` | -| `subagent-spawn` | `agent`, `llm`, `session`, `subagent` | | `tool-subagent` | `agent`, `llm`, `subagent`, `tools` | | `acp-agent` | `acp`, `agent-core`, `session-persistence-jsonl` | | `stdio-agent` | `agent`, `agent-core`, `session`, `session-persistence-jsonl`, `ui-stdio` | -| `subagent-fork` | `agent`, `session`, `subagent`, `subagent-spawn` | +| `subagent-fork` | `agent`, `session`, `subagent`, `subagent-inprocess` | +| `subagent-spawn` | `subagent`, `subagent-inprocess` | diff --git a/examples/coding-agent/cordis.yml b/examples/coding-agent/cordis.yml index 136031062a..0347115cd5 100644 --- a/examples/coding-agent/cordis.yml +++ b/examples/coding-agent/cordis.yml @@ -57,17 +57,21 @@ Use the subagent tool to delegate a focused, self-contained subtask to a fresh child agent (it works in its own context and returns only - its final result) — give it a complete, standalone instruction. + its final result) — give it a complete, standalone instruction. Use + subagent_fork instead when the subtask needs THIS conversation's + context: the child inherits the log so far. Check the [exit code: N] marker on every command; investigate failures before moving on. Verify your work by running the code or tests. Keep answers brief and factual. -# The subagent seam + an in-process spawn backend + the model-facing `subagent` -# tool, as leaf entries after the app (which provides ctx.agents/ctx.tools). The -# tool is bound to the `spawn` backend: a delegated task runs as a fresh child -# agent on this same process. (fork is available too — load dsh-subagent-fork -# and a second dsh-tool-subagent bound to it with a distinct toolName.) +# The subagent seam + BOTH in-process backends + two model-facing tools, as leaf +# entries after the app (which provides ctx.agents/ctx.tools). spawn (a fresh +# child) and fork (a child seeded with the parent's completed-turn prefix) are +# independent backends over the shared dsh-subagent-inprocess driver. Exposing +# both transports is pure config: load each backend, then load dsh-tool-subagent +# once per backend with a distinct toolName (the tool registry rejects a +# duplicate name) — no code change. - id: subagent name: '@deepseek-ai/dsh-subagent' @@ -76,7 +80,19 @@ config: providerName: spawn +- id: subagent-fork + name: '@deepseek-ai/dsh-subagent-fork' + config: + providerName: fork + - id: tool-subagent name: '@deepseek-ai/dsh-tool-subagent' config: provider: spawn + toolName: subagent + +- id: tool-subagent-fork + name: '@deepseek-ai/dsh-tool-subagent' + config: + provider: fork + toolName: subagent_fork diff --git a/packages/subagent/README.md b/packages/subagent/README.md index 582172dfd1..85ba55b626 100644 --- a/packages/subagent/README.md +++ b/packages/subagent/README.md @@ -5,10 +5,11 @@ The subagent seam: an agent delegating work to a child agent. Like the [bash](.. | Package | Role | ctx key | |---|---|---| | `subagent/` | Abstract subagent seam: named-provider registry + vocabulary | `ctx.subagents` | -| `subagent-spawn/` | In-process backend: a fresh child agent (+ the shared run driver) | (registers on `ctx.subagents`) | +| `subagent-inprocess/` | Shared in-process run driver (pure lib; registers nothing) | — | +| `subagent-spawn/` | In-process backend: a fresh child agent | (registers on `ctx.subagents`) | | `subagent-fork/` | In-process backend: a child seeded with the parent's completed-turn prefix | (registers on `ctx.subagents`) | | `tool-subagent/` | Model-facing `subagent` delegation tool over `ctx.subagents` | (registers on `ctx.tools`) | -The interface lives at `subagent/subagent/`. The in-process `subagent-spawn` / `subagent-fork` backends ship here; the out-of-process `dsh-subagent-acp` and the test-only `dsh-subagent-mock` (in [support](../support/README.md)) are separate. All **product** packages except the mock. +The interface lives at `subagent/subagent/`. The in-process `subagent-spawn` / `subagent-fork` backends share the `subagent-inprocess` driver (a pure library — both depend on it, neither on the other) and ship here; the out-of-process `dsh-subagent-acp` and the test-only `dsh-subagent-mock` (in [support](../support/README.md)) are separate. All **product** packages except the mock. The proposal and design rationale: [docs/rfc/proposed/feature/2026-06-21-subagent-capability-seam.md](../../docs/rfc/proposed/feature/2026-06-21-subagent-capability-seam.md). diff --git a/packages/subagent/subagent-fork/package.json b/packages/subagent/subagent-fork/package.json index 44027b4b0c..3348a77dd0 100644 --- a/packages/subagent/subagent-fork/package.json +++ b/packages/subagent/subagent-fork/package.json @@ -23,7 +23,7 @@ "@deepseek-ai/dsh-agent": "^0.0.1", "@deepseek-ai/dsh-session": "^0.0.1", "@deepseek-ai/dsh-subagent": "^0.0.1", - "@deepseek-ai/dsh-subagent-spawn": "^0.0.1", + "@deepseek-ai/dsh-subagent-inprocess": "^0.0.1", "cordis": "^4.0.0-rc.6" }, "dependencies": { @@ -36,6 +36,7 @@ "@deepseek-ai/dsh-llm": "workspace:^", "@deepseek-ai/dsh-session": "workspace:^", "@deepseek-ai/dsh-subagent": "workspace:^", + "@deepseek-ai/dsh-subagent-inprocess": "workspace:^", "@deepseek-ai/dsh-subagent-spawn": "workspace:^", "@deepseek-ai/dsh-system-prompt": "workspace:^", "@deepseek-ai/dsh-tools": "workspace:^", diff --git a/packages/subagent/subagent-fork/src/index.ts b/packages/subagent/subagent-fork/src/index.ts index 6c730e225e..02c1811d82 100644 --- a/packages/subagent/subagent-fork/src/index.ts +++ b/packages/subagent/subagent-fork/src/index.ts @@ -2,8 +2,10 @@ * 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. Shares the run driver with - * `@deepseek-ai/dsh-subagent-spawn`; the only difference is the seed. + * 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 @@ -23,7 +25,7 @@ import z from 'schemastery' import type { SessionEvent } from '@deepseek-ai/dsh-session' import type { Agent } from '@deepseek-ai/dsh-agent' import type { SubagentCapabilities, SubagentProvider, SubagentStartRequest } from '@deepseek-ai/dsh-subagent' -import { startInProcessRun } from '@deepseek-ai/dsh-subagent-spawn' +import { startInProcessRun } from '@deepseek-ai/dsh-subagent-inprocess' export const name = 'subagent-fork' export const inject = ['subagents', 'agents'] diff --git a/packages/subagent/subagent-fork/tsconfig.json b/packages/subagent/subagent-fork/tsconfig.json index d05e0f6081..bf2abbe698 100644 --- a/packages/subagent/subagent-fork/tsconfig.json +++ b/packages/subagent/subagent-fork/tsconfig.json @@ -27,7 +27,7 @@ "path": "../subagent" }, { - "path": "../subagent-spawn" + "path": "../subagent-inprocess" } ] } diff --git a/packages/subagent/subagent-inprocess/README.md b/packages/subagent/subagent-inprocess/README.md new file mode 100644 index 0000000000..af6d5792a9 --- /dev/null +++ b/packages/subagent/subagent-inprocess/README.md @@ -0,0 +1,28 @@ +# @deepseek-ai/dsh-subagent-inprocess + +The shared **in-process subagent run driver**. A pure library (no provider, no registration) that the in-process backends — [spawn](../subagent-spawn/README.md) (a fresh child) and [fork](../subagent-fork/README.md) (a child seeded with a prefix of the parent's log) — both build on. The backends are thin shells that differ ONLY in the session seed they pass; everything downstream lives here, so neither backend depends on the other. + +## What it exports + +### `startInProcessRun(ctx, request, options): SubagentRun` + +Runs a child as a child [`Agent`](../../core/agent) on the same cordis context (`ctx.agents`): + +1. computes child depth = `depthOf(parent) + 1`; if `request.maxDepth` is set and exceeded, throws `SubagentDepthError` (the `depthLimit` capability); +2. creates a child via `ctx.agents.create` with a fresh `AgentId`/`SessionId`, the parent's `cwd` + `parentSession` lineage, the optional `options.seed` (fork's completed-turn prefix; omitted for a fresh child), and `agentOptions` (the child inherits the **parent's model** by default — a child with no model can't run — overridable via `request.agentOptions.model`; the system prompt is NOT inherited); +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); +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`. + +`dispose()` delegates to `AgentHandle.dispose()` (stop loop → await quiescence → remove session); `cancel()` cancels the child's in-flight turn. A cancel landing before any `turn/end` (the pre-turn window) still settles `aborted`, honoring the cancel contract rather than the generic no-turn `error`. + +### `InProcessRunOptions` + +`{ providerName: string; seed?: SessionEvent[] }` — the per-backend inputs: the provider name (for error context) and the optional child-session seed. + +### `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). + +### `SubagentDepthError` + +Thrown by `startInProcessRun` when a spawn would exceed the request's `maxDepth` cap; carries `attemptedDepth` and `maxDepth`. diff --git a/packages/subagent/subagent-inprocess/package.json b/packages/subagent/subagent-inprocess/package.json new file mode 100644 index 0000000000..c2f6896c62 --- /dev/null +++ b/packages/subagent/subagent-inprocess/package.json @@ -0,0 +1,40 @@ +{ + "name": "@deepseek-ai/dsh-subagent-inprocess", + "description": "Shared in-process subagent run driver: drives a child agent on ctx.agents (used by the spawn and fork backends)", + "version": "0.0.1", + "private": true, + "type": "module", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "exports": { + ".": { + "types": "./lib/index.d.ts", + "default": "./lib/index.js" + }, + "./src/*": "./src/*", + "./package.json": "./package.json" + }, + "files": [ + "lib", + "src" + ], + "license": "BSD-3-Clause", + "peerDependencies": { + "@deepseek-ai/dsh-agent": "^0.0.1", + "@deepseek-ai/dsh-llm": "^0.0.1", + "@deepseek-ai/dsh-session": "^0.0.1", + "@deepseek-ai/dsh-subagent": "^0.0.1", + "cordis": "^4.0.0-rc.6" + }, + "devDependencies": { + "@deepseek-ai/dsh-agent": "workspace:^", + "@deepseek-ai/dsh-agent-loop": "workspace:^", + "@deepseek-ai/dsh-invariants": "workspace:^", + "@deepseek-ai/dsh-llm": "workspace:^", + "@deepseek-ai/dsh-session": "workspace:^", + "@deepseek-ai/dsh-subagent": "workspace:^", + "@deepseek-ai/dsh-system-prompt": "workspace:^", + "@deepseek-ai/dsh-tools": "workspace:^", + "cordis": "^4.0.0-rc.6" + } +} diff --git a/packages/subagent/subagent-spawn/src/in-process.ts b/packages/subagent/subagent-inprocess/src/index.ts similarity index 91% rename from packages/subagent/subagent-spawn/src/in-process.ts rename to packages/subagent/subagent-inprocess/src/index.ts index e121611697..d2840881af 100644 --- a/packages/subagent/subagent-spawn/src/in-process.ts +++ b/packages/subagent/subagent-inprocess/src/index.ts @@ -1,15 +1,16 @@ /** - * The shared in-process subagent run driver. A subagent backend that runs the - * child as a child {@link Agent} on the SAME cordis context (`ctx.agents`) — - * the cheapest transport, reusing the agent factory's quiescent - * {@link AgentHandle} teardown. Both in-process backends use this: - * `@deepseek-ai/dsh-subagent-spawn` (a fresh child) and - * `@deepseek-ai/dsh-subagent-fork` (a child seeded with a prefix of the - * parent's log) differ ONLY in the `seed` they pass — everything downstream - * (drive the child, read its final output, map the stop reason, dispose) is - * identical and lives here. + * The shared in-process subagent run driver: run a child as a child + * {@link Agent} on the SAME cordis context (`ctx.agents`) — the cheapest + * transport, reusing the agent factory's quiescent {@link AgentHandle} + * teardown. The concrete in-process backends are thin shells over this driver, + * differing ONLY in the `seed` they pass (a fresh child vs. a child seeded with + * a prefix of the parent's log); everything downstream — drive the child, read + * its final output, map the stop reason, dispose — is identical and lives here. * - * @module @deepseek-ai/dsh-subagent-spawn/in-process + * This package owns no provider and registers nothing; it is a pure library the + * backend packages depend on, so neither backend needs to know about the other. + * + * @module @deepseek-ai/dsh-subagent-inprocess */ import { randomUUID } from 'node:crypto' diff --git a/packages/subagent/subagent-inprocess/tests/subagent-inprocess.spec.ts b/packages/subagent/subagent-inprocess/tests/subagent-inprocess.spec.ts new file mode 100644 index 0000000000..7219e03988 --- /dev/null +++ b/packages/subagent/subagent-inprocess/tests/subagent-inprocess.spec.ts @@ -0,0 +1,85 @@ +import { describe, expect, it } from 'vitest' +import { Context } from 'cordis' +import LlmService from '@deepseek-ai/dsh-llm' +import SessionStore from '@deepseek-ai/dsh-session' +import SystemPrompt from '@deepseek-ai/dsh-system-prompt' +import ToolRegistry from '@deepseek-ai/dsh-tools' +import AgentRegistry, { AgentId, type Agent } from '@deepseek-ai/dsh-agent' +import AgentLoop from '@deepseek-ai/dsh-agent-loop' +import * as Invariants from '@deepseek-ai/dsh-invariants' +import SubagentService from '@deepseek-ai/dsh-subagent' +import { MockAdapter, textResponse } from '../../../core/agent-loop/tests/mock-adapter.ts' +import { depthOf, SubagentDepthError, startInProcessRun } from '../src/index.ts' + +type Script = ConstructorParameters[0] + +/** + * Drives the shared in-process run driver DIRECTLY (no provider package), so the + * driver's own contract — depth read/cap, the one-shot drive, the result read — + * is covered independently of which backend (spawn/fork) calls it. The only + * mocked boundary is the model; the real agent loop, SubagentService, and + * dsh-invariants are mounted, so a malformed child session log fails the test. + */ +async function setup(script: Script) { + const ctx = new Context() + await ctx.plugin(LlmService) + await ctx.plugin(SessionStore) + await ctx.plugin(SystemPrompt) + await ctx.plugin(ToolRegistry) + await ctx.plugin(AgentRegistry) + await ctx.plugin(Invariants) + await ctx.plugin(AgentLoop, { agents: [] }) + await ctx.plugin(SubagentService) + ctx.llm.registerAdapter(['mock'], new MockAdapter(script)) + const parent = ctx.agentLoop.create(AgentId('parent'), { model: 'mock' }) + return { ctx, parent } +} + +function text(blocks: { type: string; text?: string }[]): string { + return blocks.filter(b => b.type === 'text').map(b => b.text).join('') +} + +describe('depthOf', () => { + it('reads 0 for an agent with no subagentDepth, the set value otherwise', async () => { + const { parent } = await setup([]) + expect(depthOf(parent)).toBe(0) + const withDepth = { options: { subagentDepth: 3 } } as unknown as Agent + expect(depthOf(withDepth)).toBe(3) + }) +}) + +describe('startInProcessRun', () => { + it('drives a fresh child (no seed) to completion and returns its output', async () => { + const { ctx, parent } = await setup([textResponse('driver child answer')]) + const run = startInProcessRun(ctx, { prompt: [{ type: 'text', text: 'do X' }], parent }, { providerName: 'spawn' }) + const result = await run.result + expect(result.stopReason).toBe('completed') + expect(text(result.output)).toBe('driver child answer') + expect(depthOf(ctx.agents.get(run.id)!)).toBe(1) + await run.dispose() + }) + + it('throws SubagentDepthError when the child would exceed maxDepth', async () => { + const { ctx, parent } = await setup([]) + expect(() => startInProcessRun(ctx, { prompt: [{ type: 'text', text: 'p' }], parent, maxDepth: 0 }, { providerName: 'spawn' })) + .toThrow(SubagentDepthError) + }) + + it('seeds the child session when a seed is supplied', async () => { + // Drive the parent through one real turn, then seed the child with that + // completed-turn prefix — the child must SEE the parent's history but its + // result is scoped to its OWN events (not the seeded parent message). + const { ctx, parent } = await setup([textResponse('parent turn'), textResponse('seeded child reply')]) + parent.send([{ type: 'text', text: 'parent q' }]) + await parent.whenIdle() + const seed = parent.session.events.slice() + const run = startInProcessRun(ctx, { prompt: [{ type: 'text', text: 'child q' }], parent }, { providerName: 'fork', seed }) + const result = await run.result + expect(result.stopReason).toBe('completed') + expect(text(result.output)).toBe('seeded child reply') + const child = ctx.agents.get(run.id)! + // The child inherited the parent's prefix. + expect(child.session.events.slice(0, seed.length).some(e => e.type === 'user/message')).toBe(true) + await run.dispose() + }) +}) diff --git a/packages/subagent/subagent-inprocess/tsconfig.json b/packages/subagent/subagent-inprocess/tsconfig.json new file mode 100644 index 0000000000..f90eba8f7e --- /dev/null +++ b/packages/subagent/subagent-inprocess/tsconfig.json @@ -0,0 +1,30 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "lib" + }, + "include": [ + "src" + ], + "references": [ + { + "path": "../../../vendor/cosmokit" + }, + { + "path": "../../../vendor/cordis" + }, + { + "path": "../../core/agent" + }, + { + "path": "../../llm/llm" + }, + { + "path": "../../core/session" + }, + { + "path": "../subagent" + } + ] +} diff --git a/packages/subagent/subagent-spawn/README.md b/packages/subagent/subagent-spawn/README.md index 4e6e22f68d..97dfae9304 100644 --- a/packages/subagent/subagent-spawn/README.md +++ b/packages/subagent/subagent-spawn/README.md @@ -2,17 +2,11 @@ The in-process **spawn** subagent backend: a [`SubagentProvider`](../subagent/README.md) that runs each child as a **fresh** child [`Agent`](../../core/agent) on the same cordis context (`ctx.agents`) — its own session, its own (or the parent's) model, zero inherited conversation. The cheapest transport, reusing the agent factory's quiescent [`AgentHandle`](../../core/agent) teardown. -It also exports the **shared in-process run driver** (`startInProcessRun`) that the [fork](../subagent-fork/README.md) backend builds on — spawn and fork differ only in the session seed. +The run mechanics live in the shared [`@deepseek-ai/dsh-subagent-inprocess`](../subagent-inprocess/README.md) driver (`startInProcessRun`); this backend just passes **no seed** (a fresh child). The [fork](../subagent-fork/README.md) backend is an independent peer over the same driver — neither knows about the other. ## What it does -`start(request)` → -1. computes child depth = `depthOf(parent) + 1`; if `request.maxDepth` is set and exceeded, throws `SubagentDepthError` (the `depthLimit` capability); -2. creates a child via `ctx.agents.create` with a fresh `AgentId`/`SessionId`, the parent's `cwd` + `parentSession` lineage, and `agentOptions` (the child inherits the **parent's model** by default — a child with no model can't run — overridable via `request.agentOptions.model`; the system prompt is NOT inherited); -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); -4. reads the result: the last `assistant/message` content (deep-cloned — the log is frozen) and the last `turn/end.reason` mapped to a `SubagentStopReason`. - -`dispose()` delegates to `AgentHandle.dispose()` (stop loop → await quiescence → remove session); `cancel()` cancels the child's in-flight turn. +`start(request)` delegates to `startInProcessRun(ctx, request, { providerName })` with no seed: a fresh child agent with the parent's `cwd`/`parentSession` lineage and (by default) the parent's model. See the [driver README](../subagent-inprocess/README.md) for the full lifecycle (depth check, one-shot drive, result read, dispose). ## Capabilities @@ -23,7 +17,3 @@ It also exports the **shared in-process run driver** (`startInProcessRun`) that | Key | Meaning | |---|---| | `providerName` | Registry name on `ctx.subagents` (default `spawn`). | - -## Depth tracking - -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`. Read it with the exported `depthOf(agent)`. diff --git a/packages/subagent/subagent-spawn/package.json b/packages/subagent/subagent-spawn/package.json index 184296f01a..359d91e962 100644 --- a/packages/subagent/subagent-spawn/package.json +++ b/packages/subagent/subagent-spawn/package.json @@ -20,10 +20,8 @@ ], "license": "BSD-3-Clause", "peerDependencies": { - "@deepseek-ai/dsh-agent": "^0.0.1", - "@deepseek-ai/dsh-llm": "^0.0.1", - "@deepseek-ai/dsh-session": "^0.0.1", "@deepseek-ai/dsh-subagent": "^0.0.1", + "@deepseek-ai/dsh-subagent-inprocess": "^0.0.1", "cordis": "^4.0.0-rc.6" }, "dependencies": { @@ -38,6 +36,7 @@ "@deepseek-ai/dsh-llm-deepseek": "workspace:^", "@deepseek-ai/dsh-session": "workspace:^", "@deepseek-ai/dsh-subagent": "workspace:^", + "@deepseek-ai/dsh-subagent-inprocess": "workspace:^", "@deepseek-ai/dsh-system-prompt": "workspace:^", "@deepseek-ai/dsh-tool-bash": "workspace:^", "@deepseek-ai/dsh-tool-subagent": "workspace:^", diff --git a/packages/subagent/subagent-spawn/src/index.ts b/packages/subagent/subagent-spawn/src/index.ts index bbfcc03719..2ea082e20a 100644 --- a/packages/subagent/subagent-spawn/src/index.ts +++ b/packages/subagent/subagent-spawn/src/index.ts @@ -5,9 +5,9 @@ * context). The cheapest transport, reusing the agent factory's quiescent * teardown. * - * The fork sibling (`@deepseek-ai/dsh-subagent-fork`) shares this package's run - * driver ({@link startInProcessRun}) and differs ONLY in seeding the child with - * a prefix of the parent's log. + * The run mechanics live in `@deepseek-ai/dsh-subagent-inprocess` + * ({@link startInProcessRun}); this backend just passes NO seed (a fresh + * child). The fork backend is an independent peer over the same driver. * * Plugin export shape: named `name`/`inject`/`Config`/`apply`, NO default. * @@ -17,10 +17,7 @@ import type { Context } from 'cordis' import z from 'schemastery' import type { SubagentCapabilities, SubagentProvider, SubagentStartRequest } from '@deepseek-ai/dsh-subagent' -import { startInProcessRun } from './in-process.ts' - -export { startInProcessRun, depthOf, SubagentDepthError } from './in-process.ts' -export type { InProcessRunOptions } from './in-process.ts' +import { startInProcessRun } from '@deepseek-ai/dsh-subagent-inprocess' export const name = 'subagent-spawn' export const inject = ['subagents', 'agents'] diff --git a/packages/subagent/subagent-spawn/tests/subagent-spawn.spec.ts b/packages/subagent/subagent-spawn/tests/subagent-spawn.spec.ts index c57819d86a..ccfd6492f4 100644 --- a/packages/subagent/subagent-spawn/tests/subagent-spawn.spec.ts +++ b/packages/subagent/subagent-spawn/tests/subagent-spawn.spec.ts @@ -12,7 +12,7 @@ import * as Invariants from '@deepseek-ai/dsh-invariants' import SubagentService from '@deepseek-ai/dsh-subagent' import { MockAdapter, maxTokensResponse, textResponse } from '../../../core/agent-loop/tests/mock-adapter.ts' import * as spawn from '../src/index.ts' -import { depthOf, SubagentDepthError } from '../src/in-process.ts' +import { depthOf, SubagentDepthError } from '@deepseek-ai/dsh-subagent-inprocess' type Script = ConstructorParameters[0] diff --git a/packages/subagent/subagent-spawn/tsconfig.json b/packages/subagent/subagent-spawn/tsconfig.json index 5e6c9f9100..dc7b5cc8cd 100644 --- a/packages/subagent/subagent-spawn/tsconfig.json +++ b/packages/subagent/subagent-spawn/tsconfig.json @@ -17,17 +17,11 @@ { "path": "../../../vendor/schemastery" }, - { - "path": "../../core/agent" - }, - { - "path": "../../llm/llm" - }, - { - "path": "../../core/session" - }, { "path": "../subagent" + }, + { + "path": "../subagent-inprocess" } ] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b262257b14..bd7133ca0c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -360,6 +360,9 @@ importers: '@deepseek-ai/dsh-subagent': specifier: workspace:^ version: link:../subagent + '@deepseek-ai/dsh-subagent-inprocess': + specifier: workspace:^ + version: link:../subagent-inprocess '@deepseek-ai/dsh-subagent-spawn': specifier: workspace:^ version: link:../subagent-spawn @@ -373,6 +376,36 @@ importers: specifier: ^4.0.0-rc.6 version: 4.0.0-rc.6(@cordisjs/plugin-include@1.0.4)(@cordisjs/plugin-loader@1.0.0-rc.4) + packages/subagent/subagent-inprocess: + devDependencies: + '@deepseek-ai/dsh-agent': + specifier: workspace:^ + version: link:../../core/agent + '@deepseek-ai/dsh-agent-loop': + specifier: workspace:^ + version: link:../../core/agent-loop + '@deepseek-ai/dsh-invariants': + specifier: workspace:^ + version: link:../../support/invariants + '@deepseek-ai/dsh-llm': + specifier: workspace:^ + version: link:../../llm/llm + '@deepseek-ai/dsh-session': + specifier: workspace:^ + version: link:../../core/session + '@deepseek-ai/dsh-subagent': + specifier: workspace:^ + version: link:../subagent + '@deepseek-ai/dsh-system-prompt': + specifier: workspace:^ + version: link:../../core/system-prompt + '@deepseek-ai/dsh-tools': + specifier: workspace:^ + version: link:../../core/tools + cordis: + specifier: ^4.0.0-rc.6 + version: 4.0.0-rc.6(@cordisjs/plugin-include@1.0.4)(@cordisjs/plugin-loader@1.0.0-rc.4) + packages/subagent/subagent-spawn: dependencies: schemastery: @@ -406,6 +439,9 @@ importers: '@deepseek-ai/dsh-subagent': specifier: workspace:^ version: link:../subagent + '@deepseek-ai/dsh-subagent-inprocess': + specifier: workspace:^ + version: link:../subagent-inprocess '@deepseek-ai/dsh-system-prompt': specifier: workspace:^ version: link:../../core/system-prompt diff --git a/tsconfig.build.json b/tsconfig.build.json index 4f0528961d..d9b00156b3 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -35,6 +35,7 @@ { "path": "./packages/subagent/subagent" }, { "path": "./packages/support/subagent-mock" }, { "path": "./packages/subagent/tool-subagent" }, + { "path": "./packages/subagent/subagent-inprocess" }, { "path": "./packages/subagent/subagent-spawn" }, { "path": "./packages/subagent/subagent-fork" } ]