Summary
Spawned children never receive the session_start extension event: executeSpawn creates the child session via createAgentSession and goes straight to session.prompt(), skipping session.bindExtensions(...) — the step all app modes (interactive, print, rpc) use to emit session_start to the session's extension runner.
The consequence: any extension whose tools depend on session_start-initialized state is advertised in children but silently broken, contradicting the documented contract that children inherit "active registered executable tools".
Environment
- pi-agenticoding 0.4.0 (installed via
git:github.com/agenticoding/pi-agenticoding@main)
- Pi 0.84.2 (
@earendil-works/pi-coding-agent SDK 0.82+)
- Reproduced with
pi-mcp-adapter 2.26.0 as the MCP extension, lifecycle: "lazy" in mcp.json
Expected vs actual
Expected: children get a working tool surface matching the documented contract (spawn/index.ts header, SPAWN_DESCRIPTION, docs/architecture.md: "Children also inherit cwd and active registered tools executable in that session").
Actual: children advertise the mcp + mcpScript tool names, but every call returns MCP not initialized — the adapter's runtime never starts because its pi.on("session_start", ...) handler never fires in the child.
Root cause trace
spawn/index.ts → executeSpawn calls createAgentSession({...}) with an in-memory SessionManager, then immediately session.prompt(fullPrompt) — never session.bindExtensions(...).
bindExtensions (SDK agent-session.js) is what emits session_start to the extension runner (await this._extensionRunner.emit(this._sessionStartEvent)), plus extendResourcesFromExtensions. The app modes (interactive-mode.js, print-mode.js, rpc-mode.js) all call it after session creation; spawn is the only session-creation path that skips it.
- Extensions like
pi-mcp-adapter initialize on session_start (pi.on("session_start", ...) → initializeMcp). Their load-time fallback (startLoadTimeInitialization) only runs when a server is configured lifecycle: "eager" / "keep-alive" — with the default lazy, the child's state stays null → MCP not initialized.
- Because the child session's
AgentSession constructor activates all extension-registered tools (_refreshToolRegistry with includeAllExtensionTools: true), the broken tool names are still advertised — the child's tool surface is an emergent side effect of the SDK constructor default, not a curated spawn-level decision.
Reproduction
- Settings packages:
npm:pi-mcp-adapter + git:github.com/agenticoding/pi-agenticoding@main.
~/.pi/agent/mcp.json with any MCP server using "lifecycle": "lazy" (or no lifecycle key).
- In a parent session,
spawn a child asking it to call mcp({}).
- Observed:
MCP not initialized (verified on Pi 0.84.2 / pi-agenticoding 0.4.0).
- Workaround that confirms the root cause: setting
"lifecycle": "eager" makes the adapter's load-time fallback run in children → MCP works. Also verified that with eager, direct MCP tools (directTools: true) work in children.
Suggested fixes (one of)
- Parity: in
executeSpawn, after createAgentSession, call session.bindExtensions({...}) with minimal print-mode bindings (mode "print", no UI) so extensions receive session_start like every other session. Children would then get working MCP with lazy lifecycle too. Cost: per-child extension init runs (same cost profile as eager).
- Isolation: pass
excludeTools for extension tools not in the inherited list, so children advertise exactly the intended surface (no broken tools, but children lose MCP entirely unless explicitly enabled).
Either way, the documented "executable tools" contract should hold — tools advertised in a child must either work or not be advertised.
Related observation (unverified)
Because children auto-load the same settings packages/extensions, the child's own extension runner re-registers pi-agenticoding's spawn/handoff tools (recursion prevention is currently enforced only by name-filtering the inherited list and by prompt text). Worth confirming whether children can in fact call spawn/handoff; if so, that's the same "emergent tool surface" gap.
Notes
- No comment, doc, openspec story, or test in the repo mentions child
session_start/bindExtensions — the omission appears to be an undocumented gap rather than a deliberate decision.
- This was investigated as part of a real session; findings and the config workaround are documented in the notebook page
pi-agenticoding-spawn-mcp-tools of the reporting session.
Summary
Spawned children never receive the
session_startextension event:executeSpawncreates the child session viacreateAgentSessionand goes straight tosession.prompt(), skippingsession.bindExtensions(...)— the step all app modes (interactive, print, rpc) use to emitsession_startto the session's extension runner.The consequence: any extension whose tools depend on
session_start-initialized state is advertised in children but silently broken, contradicting the documented contract that children inherit "active registered executable tools".Environment
git:github.com/agenticoding/pi-agenticoding@main)@earendil-works/pi-coding-agentSDK 0.82+)pi-mcp-adapter2.26.0 as the MCP extension,lifecycle: "lazy"inmcp.jsonExpected vs actual
Expected: children get a working tool surface matching the documented contract (
spawn/index.tsheader,SPAWN_DESCRIPTION,docs/architecture.md: "Children also inherit cwd and active registered tools executable in that session").Actual: children advertise the
mcp+mcpScripttool names, but every call returnsMCP not initialized— the adapter's runtime never starts because itspi.on("session_start", ...)handler never fires in the child.Root cause trace
spawn/index.ts→executeSpawncallscreateAgentSession({...})with an in-memorySessionManager, then immediatelysession.prompt(fullPrompt)— neversession.bindExtensions(...).bindExtensions(SDKagent-session.js) is what emitssession_startto the extension runner (await this._extensionRunner.emit(this._sessionStartEvent)), plusextendResourcesFromExtensions. The app modes (interactive-mode.js,print-mode.js,rpc-mode.js) all call it after session creation; spawn is the only session-creation path that skips it.pi-mcp-adapterinitialize onsession_start(pi.on("session_start", ...)→initializeMcp). Their load-time fallback (startLoadTimeInitialization) only runs when a server is configuredlifecycle: "eager"/"keep-alive"— with the defaultlazy, the child's state stays null →MCP not initialized.AgentSessionconstructor activates all extension-registered tools (_refreshToolRegistrywithincludeAllExtensionTools: true), the broken tool names are still advertised — the child's tool surface is an emergent side effect of the SDK constructor default, not a curated spawn-level decision.Reproduction
npm:pi-mcp-adapter+git:github.com/agenticoding/pi-agenticoding@main.~/.pi/agent/mcp.jsonwith any MCP server using"lifecycle": "lazy"(or no lifecycle key).spawna child asking it to callmcp({}).MCP not initialized(verified on Pi 0.84.2 / pi-agenticoding 0.4.0)."lifecycle": "eager"makes the adapter's load-time fallback run in children → MCP works. Also verified that witheager, direct MCP tools (directTools: true) work in children.Suggested fixes (one of)
executeSpawn, aftercreateAgentSession, callsession.bindExtensions({...})with minimal print-mode bindings (mode"print", no UI) so extensions receivesession_startlike every other session. Children would then get working MCP withlazylifecycle too. Cost: per-child extension init runs (same cost profile aseager).excludeToolsfor extension tools not in the inherited list, so children advertise exactly the intended surface (no broken tools, but children lose MCP entirely unless explicitly enabled).Either way, the documented "executable tools" contract should hold — tools advertised in a child must either work or not be advertised.
Related observation (unverified)
Because children auto-load the same settings packages/extensions, the child's own extension runner re-registers pi-agenticoding's
spawn/handofftools (recursion prevention is currently enforced only by name-filtering the inherited list and by prompt text). Worth confirming whether children can in fact callspawn/handoff; if so, that's the same "emergent tool surface" gap.Notes
session_start/bindExtensions— the omission appears to be an undocumented gap rather than a deliberate decision.pi-agenticoding-spawn-mcp-toolsof the reporting session.