Skip to content

spawn: children never receive session_start — extension tools advertised but broken (MCP "MCP not initialized") #25

Description

@grzegorznowak

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

  1. spawn/index.tsexecuteSpawn calls createAgentSession({...}) with an in-memory SessionManager, then immediately session.prompt(fullPrompt) — never session.bindExtensions(...).
  2. 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.
  3. 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.
  4. 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

  1. Settings packages: npm:pi-mcp-adapter + git:github.com/agenticoding/pi-agenticoding@main.
  2. ~/.pi/agent/mcp.json with any MCP server using "lifecycle": "lazy" (or no lifecycle key).
  3. In a parent session, spawn a child asking it to call mcp({}).
  4. Observed: MCP not initialized (verified on Pi 0.84.2 / pi-agenticoding 0.4.0).
  5. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions