Skip to content

SPEC.md — @robota-sdk/agent-subagent-runner

Scope

Optional package that provides child-process subagent execution for the Robota agent runtime. Implements ISubagentRunner from @robota-sdk/agent-executor by forking Node.js child processes, routing jobs via an IPC protocol, and returning lifecycle handles to the caller.

This package is opt-in: install only when child-process subagent support is needed. Applications that do not use subagents should not carry this dependency.

Boundaries

  • Depends on @robota-sdk/agent-core, @robota-sdk/agent-executor, @robota-sdk/agent-framework, and @robota-sdk/agent-provider.
  • Must NOT import from @robota-sdk/agent-command or @robota-sdk/agent-cli.
  • Must NOT import from @robota-sdk/agent-session directly — session lifecycle is accessed through agent-framework facades.
  • Owns the IPC wire protocol between parent runner and child worker.
  • Owns the worker entry point (child-process-subagent-worker.ts) and the worker path resolver.
  • Does NOT own subagent lifecycle state machines — those live in agent-executor.
  • Does NOT own provider creation contracts — provider config is received as a serialized profile from the parent and reconstructed in the worker via agent-provider.

Architecture Overview

agent-cli / composition root
  └── createChildProcessSubagentRunnerFactory()
        └── ChildProcessSubagentRunner (ISubagentRunner)
              ├── fork() → child-process-subagent-worker.ts
              │     ├── ISubagentWorkerStartMessage  (parent → child)
              │     ├── ISubagentWorkerSendMessage   (parent → child)
              │     ├── ISubagentWorkerCancelMessage (parent → child)
              │     ├── ISubagentWorkerReadyMessage  (child → parent)
              │     ├── ISubagentWorkerTextDeltaMessage (child → parent)
              │     ├── ISubagentWorkerToolStartMessage (child → parent)
              │     ├── ISubagentWorkerToolEndMessage   (child → parent)
              │     ├── ISubagentWorkerResultMessage    (child → parent)
              │     ├── ISubagentWorkerErrorMessage     (child → parent)
              │     └── ISubagentWorkerCancelledMessage (child → parent)
              ├── ChildProcessSubagentResultController  (lifecycle + result promise)
              └── child-process-subagent-transport.ts   (IPC send/receive helpers)

worker-path-resolver.ts → getDefaultSubagentWorkerPath()
  Returns path to compiled worker: {dirname}/child-process-subagent-worker.js

Opt-in wiring: The composition root (agent-cli) imports createChildProcessSubagentRunnerFactory and getDefaultSubagentWorkerPath from this package and passes the factory to createAgentRuntime(). The agent-framework runtime accepts the factory as an optional port (TSubagentRunnerFactory); no default is injected.

Type Ownership

Type / InterfaceKindOwnerDescription
IChildProcessSubagentRunnerOptionsinterface (local)this pkgConstructor options: workerPath, providerConfig, execArgv, killGraceMs, env, worktreeIsolation, logsDir
ISubagentWorkerStartPayloadinterface (local)this pkgIPC payload for start message: jobId, request, agentDefinition, parentConfig, parentContext, providerProfile, permissionMode, logsDir
TSubagentWorkerParentMessagetype aliasthis pkgUnion of all parent → child IPC message types
TSubagentWorkerChildMessagetype aliasthis pkgUnion of all child → parent IPC message types
TSubagentWorkerWireValuetype aliasthis pkgUnion of all IPC messages for wire-level validation
ISubagentRunner (consumed)interfaceagent-executorPort implemented by ChildProcessSubagentRunner
ISubagentJobStart (consumed)interfaceagent-executorInput to runner.start()
ISubagentJobHandle (consumed)interfaceagent-executorReturn value of runner.start()
ISerializableProviderProfile (consumed)interfaceagent-frameworkSerialized provider profile sent to worker

Public API Surface

ExportKindDescription
ChildProcessSubagentRunnerclassImplements ISubagentRunner. Forks child processes and returns ISubagentJobHandle.
createChildProcessSubagentRunnerFactoryfactoryReturns TSubagentRunnerFactory for injection into createAgentRuntime().
IChildProcessSubagentRunnerOptionsinterfaceConstruction options (workerPath, killGraceMs, logsDir, worktreeIsolation, etc.)
getDefaultSubagentWorkerPathfunctionReturns the compiled worker entry path for the installed package version.
isSubagentWorkerParentMessagetype guardRuntime validation for parent → child IPC messages.
isSubagentWorkerChildMessagetype guardRuntime validation for child → parent IPC messages.
ISubagentWorkerStartPayloadinterfaceIPC start message payload shape.
TSubagentWorkerParentMessagetype aliasUnion of all parent → child message types.
TSubagentWorkerChildMessagetype aliasUnion of all child → parent message types.
TSubagentWorkerWireValuetype aliasUnion of all IPC message types for wire-level validation.

Extension Points

  • Custom worker path: Pass workerPath in IChildProcessSubagentRunnerOptions to use a non-default worker script (e.g., for testing or extended worker behavior).
  • Custom worktree adapter: Pass worktreeAdapter to override the default git worktree isolation adapter. Useful for environments without git or for test doubles.
  • Custom provider config: Pass providerConfig to override the parent provider configuration serialized into the start payload.
  • Log directory: Pass logsDir to enable transcript logging per job; reads are exposed through ISubagentJobHandle.readLog().
  • Kill grace period: killGraceMs (default 2000ms) controls how long the runner waits for a graceful shutdown before sending SIGTERM.

Error Taxonomy

Error scenarioBehavior
Worker process exits unexpectedlyISubagentJobHandle.result rejects with exit code and any captured stderr
Worker sends error messageresult rejects with the worker error message string
Worker sends cancelled messageresult rejects with a cancellation result; handle emits a cancellation event
IPC message fails validationisSubagentWorkerChildMessage / isSubagentWorkerParentMessage return false; message dropped
TimeoutIf a timeout is configured in ISubagentJobStart, the runner cancels the child and rejects
Fork failureChildProcessSubagentRunner.start() throws synchronously if child_process.fork() fails

Test Strategy

Current State

  • 1 test file: src/__tests__/child-process-subagent-runner.test.ts — 6 integration specs covering: result resolution with child pid, text/tool progress events, transcript path and log reading, follow-up prompt forwarding, cancellation flow, and IPC message validation guards.
  • Test fixture: src/__tests__/fixtures/subagent-worker-fixture.mjs — mock worker used in tests.

Gaps

  • No unit tests for ChildProcessSubagentResultController in isolation.
  • No unit tests for child-process-subagent-transport.ts helpers.
  • No unit tests for worker-path-resolver.ts.

Class Contract Registry

Class Implementations

ClassDefined InImplementsNotes
ChildProcessSubagentRunnersrc/child-process-subagent-runner.tsISubagentRunnerMain runner; uses fork() and IPC
ChildProcessSubagentResultControllersrc/child-process-subagent-runner-result.ts(internal)Wraps child process lifecycle into result promise

Cross-Package Port Consumers

Port (Owner)ConsumerNotes
ISubagentRunner (agent-executor)ChildProcessSubagentRunnerInterface implemented by this package
ISubagentJobStart (agent-executor)runner.start()Input job descriptor
ISubagentJobHandle (agent-executor)return of runner.start()Lifecycle handle returned to caller
TSubagentRunnerFactory (agent-framework)createChildProcessSubagentRunnerFactoryFactory type accepted by createAgentRuntime()
ISerializableProviderProfile (agent-framework)ISubagentWorkerStartPayloadProvider profile serialized into IPC start payload
GitWorktreeIsolationAdapter (agent-executor)default worktree adapterOptional isolation; skipped when worktreeIsolation: false

Released under the MIT License.