pi-submarine
A no-nonsense Pi extension for focused foreground child-session delegation.
Package details
Install pi-submarine from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:pi-submarine- Package
pi-submarine- Version
0.3.0- Published
- Sep 6, 2026
- Downloads
- 540/mo · 211/wk
- Author
- dnouri
- License
- MIT
- Types
- extension
- Size
- 129.5 KB
- Dependencies
- 0 dependencies · 3 peers
Pi manifest JSON
{
"extensions": [
"./src/index.ts"
],
"image": "https://raw.githubusercontent.com/dnouri/pi-submarine/0.3.0/media/pi-submarine.jpg"
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
pi-submarine 🥽
pi-submarine is a minimal Pi extension for delegating tasks to child
Pi sessions, also known as subagents. The parent receives compact
run metadata, the child session ID, and the child's final answer.
The diagram shows the delegation model: each subagent() call runs one
focused child Pi session, calls in the same turn run in parallel, and each
call returns a compact result without the child transcript.
Features
- Fresh child sessions for isolated work, or forked child sessions when the child should inherit the current conversation branch.
- Named agents defined by simple markdown files with five frontmatter
knobs:
description,model,thinkingLevel,agentsMd, andskills. - Per-agent model and thinking-level defaults with optional model and
thinking-level overrides on each
subagentcall. - Agent discovery for user-level and project-level markdown agents,
including
subagent_listfor showing what is visible from a cwd. - Runtime status updates that report activity, turn counts, nested children, and context usage.
- An append-only
<parent-session>.jsonl.subagents.mdactivity log that can be tailed while children run. - Nested subagents, with a depth limit to prevent accidental circular delegation.
- Support for parallel subagents through Pi's native multi tool calling.
- Resumable child sessions through
subagent_resumewhen continuing the same child context is useful after an abort, failure, or deliberate follow-up.
The package is deliberately narrow: one subagent call runs one
foreground child Pi session and waits for it. It does not provide
built-in agent roles, background jobs, chains, dashboards, or a
workflow engine.
Usage
The extension registers three tools:
subagent({ agent?, task, model?, thinkingLevel?, context?, cwd? })
subagent_resume({ sessionId, message })
subagent_list({ cwd? })
All three tools use strict parameter schemas: unknown properties are rejected.
[!IMPORTANT]
pi-submarinetreats project-local Pi inputs for the child cwd as trusted. Review unfamiliar.pi/and.agents/files before running subagents.
The common calls are intentionally small:
subagent({ task: "Inspect src/runner.ts and summarize the control flow." })
subagent({ agent: "vision", task: "Read any screenshots or images and return their contents", model: "glm-5v-turbo" })
subagent({ context: "fork", task: "Use the current conversation branch to check my last plan." })
Fresh runs are independent child sessions. They can use files and
tools in their cwd, but they do not see the current parent
conversation, so put the needed background, paths, constraints, and
desired output in task. Fresh calls do not forward images attached
to the parent turn; include child-readable image paths in task.
Use context: "fork" only when the child should inherit a copy of the
current conversation, including images already present on that branch.
subagent({ agent?, task, model?, thinkingLevel?, context?, cwd? })
Runs one focused task in a child Pi session and returns compact session metadata plus the child's final answer.
Arguments:
taskis required. It is the prompt for the child session.agentis optional. Omit it for the generic default mode. Pass a filename stem such as"vision"to use a markdown agent namedvision.md. A literalagent: "subagent"means the markdown filesubagent.md; omission is the only way to request the default mode, although a literalagent: "default"is tolerated and resolves to the default mode whenever nodefault.mdis visible.modelis optional. Pass a model ID such as"glm-5v-turbo"or a canonicalprovider/model-idreference. It overrides the named agent's frontmatter model for this call.thinkingLevelis optional. Pass one of Pi's thinking levels (off,minimal,low,medium,high,xhigh,max). It overrides the named agent's frontmatter level for this call.contextis optional and defaults to"fresh". Use"fresh"for a new child session. Use"fork"only when the child must inherit the current conversation branch.cwdis optional and valid only with fresh runs. Usually omit it; the child then uses the caller cwd. Set it only when another directory should be the child's workspace/project: relative tool paths and bash run there, and project-agent discovery, context files such asAGENTS.md/CLAUDE.md, skills, and Pi resources follow that cwd. Relativecwdvalues resolve from the caller cwd.
Model references must match Pi's current model catalog and have
configured authentication. For a bare model ID, pi-submarine first
prefers a matching model from the parent's provider, then the only
authenticated match; otherwise it asks for a canonical reference. If
neither the call nor the agent chooses a model, fresh runs inherit the
parent model and fork runs restore the model from the copied branch.
An explicit thinking level must be supported by the resolved model:
if it is not, the run fails before child artifacts are created and the
error lists the supported levels rather than silently clamping. When
no level is requested, fresh runs use Pi's default for the model and
fork runs restore the level recorded on the copied branch.
Fresh runs create a new child session below the current root session's
.subagents/ directory; nested subagents share that directory. The
tail-able Markdown activity log is the sibling
<parent-session>.jsonl.subagents.md file. Fork runs branch from the
current conversation leaf into the same .subagents/ directory.
If a named agent cannot be found from an explicit cwd but would have
been found from the caller cwd, subagent fails with a hint to omit
cwd and put external paths in task instead.
A successful result looks like this:
## Subagent vision result
Subagent session ID: 019...
<child assistant answer>
Use the Subagent session ID for later continuation.
subagent_resume({ sessionId, message })
Continues an existing child Pi session in the current parent/root
session. It requires a persisted parent Pi session so it can find the
current parent/root manifest. It reopens the recorded child JSONL
session, appends message, waits for the next child answer, and
returns the same compact result shape.
Arguments:
sessionIdis required. Use the child Pi session ID from an earliersubagentorsubagent_resumeresult, or from recovery text after an interrupted or failed child run.messageis required. It is appended to that same child conversation.
Resume lookup is scoped to the current parent/root manifest. It does
not search globally, fork, or copy the child session. Use subagent
for unrelated work; use subagent_resume when continuing the same
child context is clearer than starting over.
For original fresh runs, resume reloads current prompt resources from
the recorded cwd, including named-agent agentsMd and skills
controls. For original fork runs, resume uses fork-style resources.
All resume calls send message as plain user text, because the
initial subagent prompt envelope is already in the child transcript.
subagent_list({ cwd? })
Lists the markdown agents visible from a working directory, plus the
special default mode where agent is omitted.
Arguments:
cwdis optional. Usually omit it to inspect the caller project context. An explicitcwdis an advanced override for listing another directory's visible agents; relative paths resolve from the caller cwd.
The text result shows each markdown agent's name, source label (user
or project), description, and path, plus a Callable models section
listing one line per model with configured authentication and its
supported thinking levels, marking the current session model. The
structured details include the resolved cwd, source counts,
user/project agent directories, the callable model catalog, and the
current model. If an explicit cwd hides project agents visible from
the caller cwd, the result includes a warning.
Markdown agents
Markdown agents are prompt resources. Treat them like trusted code:
review project .pi/agents/*.md files before running subagents in an
unfamiliar checkout.
User agents live under the Pi agent directory in agents/*.md
($PI_CODING_AGENT_DIR/agents when that environment variable is set,
otherwise Pi's normal agent directory). Project agents live in the
nearest ancestor .pi/agents/*.md from the effective cwd. Project
agents override user agents with the same filename stem.
For project agents, normally pass the agent name and omit cwd. For
example, create .pi/agents/vision.md:
---
description: Reads screenshots, diagrams, and other images.
model: glm-5v-turbo
thinkingLevel: low
agentsMd: auto
skills: none
---
Read every image path named in the task. If no path is given, find the
relevant image files in the cwd first. Return visible text and a concise
description, separating direct observations from inferences.
The frontmatter model is the default:
subagent({ agent: "vision", task: "Read screenshots/login.png and return its contents." })
A call-level model selects or overrides it for one invocation:
subagent({ agent: "vision", task: "Read any screenshots or images and return their contents", model: "glm-5v-turbo" })
If the task needs files outside the project, put those paths in task
unless you intentionally want the external directory to define the
child's project context.
Agent files use the small frontmatter block shown above, not YAML.
description is required. model is optional and uses the same model
reference rules as the call-level argument. thinkingLevel is
optional and may be one of off, minimal, low, medium, high,
xhigh, or max; it uses the same call-level override and validation
rules as model. agentsMd is optional and
may be none or auto; it defaults to none. skills is optional
and may be auto, none, or a comma-separated list of skill names;
it defaults to auto. Unknown keys, blank frontmatter lines,
comments, quoted values, arrays, block scalars, duplicate keys,
missing delimiters, and empty bodies are rejected.
Discovery is non-recursive and ignores hidden files, nested
directories, uppercase .MD, and *.chain.md files.
Every initial subagent call sends the child a single XML-style user
prompt envelope with subagent context and the task. Named agents add a
sanitized <name-agent> block containing the markdown body between
that context and the task. Fresh default-mode runs do not load
markdown agents, suppress AGENTS.md / CLAUDE.md context files, and
keep normal skills. For named fresh runs, agentsMd controls
context-file loading and skills controls skill loading; the agent
body stays in the user prompt envelope rather than the child system
prompt. Fork runs ignore those frontmatter resource controls but use
the same user prompt envelope for omitted and named agents. A
frontmatter model or thinking level applies to both fresh and forked
initial runs. Resuming restores the model and thinking level recorded
in the child session rather than reapplying the current agent-file
defaults.
Artifacts and progress
subagent and subagent_resume require a persisted parent Pi
session. Once a child episode starts, pi-submarine writes artifacts
beside the root parent session when possible, even if the episode
later fails or is aborted:
<parent-session>.jsonl
<parent-session>.jsonl.subagents.md
<parent-session>.jsonl.subagents/
manifest.jsonl
<child-session>.jsonl
manifest.jsonl records lifecycle data used for resume and
nesting. <parent-session>.jsonl.subagents.md is an append-only
status stream suitable for tail -f, not a transcript. Full child
messages remain in the child session JSONL.
While a child runs, Pi frontends receive portable text partial updates like this:
Log: ~/.pi/agent/sessions/.../parent.jsonl.subagents.md
- subagent (6% ctx, 1 turn) -> vision (? ctx, 2 turns): using read
The same update includes structured details.run data:
type SubagentRunView = {
episodeId: string
sessionId: string
agent: string
status: "running" | "completed" | "failed" | "aborted"
turnCount: number
lastActivityAt: string
activity: string
activityLog: string
contextUsage?: {
tokens: number | null
contextWindow: number
percent: number | null
}
children: SubagentRunView[]
}
sessionId is the public continuation handle. episodeId identifies
one subagent or subagent_resume lifecycle episode; multiple
episodes can share one child sessionId. Nested child run metadata
stays in structured run details; nested transcripts and final answers
remain in their child sessions instead of being pasted into the parent
model context.
Context usage comes from Pi's AgentSession.getContextUsage(). If Pi
reports unknown usage, the progress text shows ? ctx; if usage is
unavailable, the segment omits context usage. No custom TUI renderer
is required for correctness.
Errors and limits
- Tool errors are thrown from
execute(), which Pi records as failed tool results. - Parent abort signals are forwarded to the child session. If a child
session already exists, the durable status is
abortedand the model-visible error text includes theSubagent session IDand examples forsubagent_resume. - Non-abort child failures after a trusted child session exists stay
durable
failed; the error text includes the public session ID only as a cautious “may be resumable” handle. Preflight and lookup failures before a trusted child session exists do not invent a continuation handle. - Requested and recorded models are checked before the child is prompted. If a model is unavailable or unauthenticated, or Pi reports that it would fall back, the run fails instead of silently using a substitute.
- Requested thinking levels are validated against the resolved model before child artifacts are created. Unsupported levels fail with the supported list instead of silently clamping to a neighboring level.
pi-submarine's wrapper text does not add session-file paths, activity-log paths, stack traces, child transcripts, or the Markdown activity log to model-visible success, interruption, or recovery text. The original provider or extension error message is preserved and may contain its own details.- Manifest and activity-log append failures are logged and do not fail an otherwise successful child episode. Continuation needs the current-root manifest start record, so resume is not guaranteed after a degraded manifest write.
- In one Pi process,
pi-submarinerejects concurrent attempts to append to the same child session, includingsubagent_resumewhile the originalsubagentis still active. This is not a cross-process lock. - Nested subagents share the Node.js event loop and Pi extension runtime. Nesting deeper than 4 is rejected to stop accidental circular delegation.
pi-submarinedoes not add a wall-clock timeout around Pi'ssession.prompt().- Forked children start from the current branch and may see the current parent turn. Phrase fork tasks so parent-only final-answer markers do not conflict with what the child should produce.
Install
pi install npm:pi-submarine
Requires Pi 0.79.1 or newer. The package manifest registers
./src/index.ts, which Pi loads through its TypeScript extension
loader. The npm package contains src/, README.md, and npm's
automatic package.json.
Development
For local development, load the extension directly:
pi -e ./src/index.ts
Run the checks with:
npm install
npm run typecheck
npm test
npm run build
npm run smoke:registration
The model-facing tool labels, descriptions, schema descriptions,
guidelines, and prompt envelope helpers live in src/tool-prompts.ts.
Keep that file, src/index.ts, and this README in sync when the tool
contract changes.
