npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@tylerho/pi-subagents

v0.1.0

Published

Background subagents on a pi or Claude Code backend, with fire-and-forget spawn and deferred result delivery.

Readme

@tylerho/pi-subagents

Background subagents on a pi or Claude Code backend, with fire-and-forget spawn and deferred result delivery.

Install

pi install npm:@tylerho/pi-subagents


Subagents

Background subagents on one of two backends — pi (in-process SDK session) or Claude Code (Claude Agent SDK) — unified behind a single Effect v4 service interface. The backbone for delegation: workflows, memory consolidation, and recaps all share its delegation defaults and activity counters. Children are fully autonomous, headless, and self-contained: they cannot re-orchestrate (subagent_*, workflow, ask_user, enter_worktree, exit_worktree are excluded from their toolset), cannot ask the user, and model-origin children cannot see the parent conversation — /btw asides are the exception, seeded with the parent's conversation up to the spawn point.

Key concepts

  • Two backends, one API. harness: "pi" | "claude" selects where the subagent runs. The pi backend is an in-process createAgentSession() (real session files visible in /resume, per-cwd resources with trust gating, the child tool denylist). The claude backend is one @anthropic-ai/claude-agent-sdk query() in streaming-input mode — the CLI owns conversation continuity, tool execution, and ~/.claude/projects transcripts; it runs bypassPermissions, disallows Claude's native Agent/Task subagent tools so orchestration stays inside this extension's manager and cap, and (when no claude binary is on PATH) reports unavailable.
  • Effect v4 layering. backends/ produce a scoped SubagentSession exposing a normalized SubagentEvent stream; the manager spawns one pump fiber per subagent that folds that stream into a mutable SubagentSnapshot; runtime.ts composes the layer into one ManagedRuntime; index.ts is the async boundary where tool handlers run effects via runTool() (typed failures → thrown Error, AbortSignal interruption → interruptMessage).
  • Fire-and-forget by default. subagent_spawn returns SpawnDetails immediately. Settlement runs through a deferred-result queue: a settled result is delivered as a follow-up message (customType: "subagent-result", deliverAs: "followUp", triggerTurn: true) when the parent is idle or on the next agent_settled. A later subagent_wait/subagent_cancel marks the settle consumed (waitInterest refcount) so it is not delivered twice. The follow-up's model-facing content is a preview only (first 16 lines / 2 KB of the output) plus a pointer to subagent_wait for the full text — the complete output lives in details.fullOutput (24 KB cap) for the TUI's expanded renderer, so the parent model can act on it but never re-dumps it into the transcript.
  • Origins. Model-origin spawns are visible to model-facing tools and the /subagents dashboard; user asides from /btw (btw-* ids) are filtered out by isModelVisible everywhere in the tool layer and the dashboard — they are revisited through the /btw panel. A btw result is appended as a synchronous session entry (pi.appendEntry("btw-result"), safe while the parent is streaming) plus a ui.notify, never a model-context follow-up. btw children run with no tools at all (noTools: "all", so built-in and extension tools are excluded persistently) and get a prompt prefix telling them to answer from context — one-off questions, not delegated work.
  • btw context inheritance. A btw child is a fork, not a clean slate: at spawn time the command handler snapshots the parent conversation (buildSessionContext(ctx.sessionManager.getEntries(), ctx.sessionManager.getLeafId()).messages — two adjacent synchronous reads, so it is race-free even while the parent is streaming; an in-flight assistant message is only persisted at its message_end, so the snapshot ends at the parent's last completed message, the same cut Claude Code applies). The pi backend seeds those messages into the child's session file before createAgentSession, which restores them as the child's history — compaction, resume, and the transcript treat them like the child's own. Summary roles are rewritten to the plain user text convertToLlm would produce. The inherited range is fenced with btw-context-start/btw-context-end custom entries (BTW_CONTEXT_START/BTW_CONTEXT_END in by-the-way.ts) so the persisted-transcript loader hides it in the /btw panel; the child's own result extraction also excludes the seeds by object identity, so a child that fails before answering reports failure instead of echoing a parent message.
  • Ids are slugs, not counters. Each id is sa-<slug> (or btw-<slug>), where the slug is derived from the spawn title (slugifyTitle in manager.ts: lowercase alphanumerics + hyphens, ≤24 chars, "subagent" fallback). Repeats get a numeric suffix (sa-refactor-util-2). Ids stay unique per parent session and are the handle for subagent_wait/cancel/check/send.
  • Caps. MAX_RUNNING = 50 running subagents across all backends (reserved synchronously before the first yield so parallel spawns cannot race past it; idle restarts via send count too). MAX_TRACKED = 4_096 settled snapshots per parent session, pruned oldest-settled-first; session files survive on disk.
  • Transcripts are bounded in memory, full on disk. The manager keeps 512 transcript items / 64 KiB per text; the takeover view lazily streams the FULL persisted JSONL (pi ~/.pi/agent/sessions/<escaped-cwd>/<timestamp>_<id>.jsonl, Claude ~/.claude/projects/<escaped-cwd>/<sessionId>.jsonl) through src/persisted/ parsers (format auto-detected per record, line-by-line, never slurped). The persisted snapshot is merged with the live in-memory transcript (mergeTranscripts): live items always win so streamed content never vanishes when it finalizes; the persisted list supplies pruned history and full multi-line tool outputs. The persisted load is refreshed once when the run settles. For btw sessions the loader skips the fenced inherited-context range, so the /btw panel shows the aside, not the whole parent conversation.
  • Model defaults & cost ceiling. No model hint → each harness's configured default from shared/subagent-models.json (set by /subagent-model), not the parent's model — delegated work must not silently run on an expensive interactive model. Agent-chosen models above the ceiling (default $10/Mtok output, env PI_SUBAGENT_COST_CEILING) are rejected with actionable alternatives; the configured default and /btw asides are exempt (deliberate user intent).
  • Trust gating. A child in the same directory inherits the parent's trust decision. An alternate working_dir is trusted only when pi's persisted ProjectTrustStore explicitly trusts it (or a containing directory); unreadable/invalid trust data fails closed. Claude children in untrusted cwds get settingSources: ["user"] so an untrusted project's config cannot reconfigure the child.
  • Child safety rails. Headless children exclude subagent_spawn, subagent_wait, subagent_cancel, subagent_check, subagent_list, workflow, ask_user, enter_worktree, exit_worktree; every child tool call is wrapped with a 30-minute execution timeout (shared/tool-call-timeout.ts, re-applied on agent_start so tools registered mid-session are covered).
  • Task rail (TUI). A belowEditor widget shows running/finished subagents. A down double-tap (≤500 ms) in the default editor view (no modal focused, editor text empty) expands it; ↓/↑ navigate (no wrap — at-bottom reveals finished, at-top closes), Enter opens the takeover view. Key-release and repeat events are filtered (isKeyRelease/isKeyRepeat); in any modal the gesture yields to the focused component.
  • Lifecycle. session_start wires the rail + terminal handler; session_shutdown unregisters everything and disposes the runtime, whose manager finalizer (disposeAll) force-closes every subagent scope (5 s bound per close) — a safety net even if the extension forgot to dispose.

API

Tools (registered for the parent LLM)

subagent_spawn — fire-and-forget spawn. Params: prompt (string, must be self-contained: no parent context is visible to the child), name (string, title; trimmed, truncated to 160 chars, default "subagent"), harness ("pi" | "claude"), working_dir (optional string, must exist and be a directory; default parent cwd), model (optional string: pi "provider/model-id" or bare id, claude alias like "sonnet"), reasoning_effort (optional "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max"). The id derives from the name as a slug (see Key concepts). Returns text like Spawned sa-refactor-util "refactor util" (pi, deepseek/deepseek-v4-pro). Runs in background; result arrives automatically. subagent_wait(["sa-refactor-util"]) to block. plus details: { id, title, cwd, harness, model }. Rejects at the 50-agent cap (ConcurrencyLimitError), unknown/unavailable harness, bad working_dir, unknown model, or cost-ceiling violation.

{ "prompt": "Refactor src/util.ts: split the two exported functions into separate files, run the test suite, report the diff stat.", "name": "refactor util", "harness": "pi" }

subagent_wait — block until all listed subagents settle, then return their final outputs. Params: ids (array of string, max 64). Streams Waiting for ... via onUpdate. Unknown ids (or btw ids) fail the call listing known model ids. Result: one ## <id> "<title>" finished/failed section per agent with error text and output (48 KB total / 16 KB per agent budget; per-section [omitted: ...] fallbacks), plus details: { results: [{ id, title, status }] }. Interruption (tool abort) releases the wait and leaves subagents running. Consumes the deferred automatic delivery.

{ "ids": ["sa-refactor-util"] }

subagent_cancel — abort running subagents. Params: ids (array of string). Marks consumed before interrupting (5 s graceful session.interrupt, force-close fallback that settles first so the "stream ended" fallback cannot misreport), waits for settlement, reports Cancelled <id> "<title>". or <id> "<title>" was already <status>. plus details: { results: [{ id, title, status }] }. Partial session transcripts stay on disk.

subagent_check — non-blocking status peek. Params: id (string). Returns id [status] "title" (backend: model, ctx%/capacity, elapsed, cwd), turn count, error text, and up to 2 KB / 20 lines of the latest output (includes the live streaming assistant text). Does NOT consume the result. details: { id, status, turns }.

subagent_list — list all tracked subagents. No params. One describeSubagent() line per model-origin subagent (id [status] "title" (backend: model, ctx%, elapsed, cwd)); "No subagents." when empty. details: { subagents: [{ id, title, harness, status }] }.

Commands (all TUI-only; notify + return in non-TUI modes)

| Command | Args | Behavior | |---|---|---| | /btw | optional prompt text | Spawns a "pi"-harness subagent with origin: "btw", title from deriveBtwTitle (first prompt line, ≤60 chars), then opens the btw panel (BtwPanel) — a CC-style bottom dock listing past asides as /btw <question> lines above the selected aside's answer: ←/→ switches between asides (each is its own isolated session), ↑/↓ or j/k scrolls the answer, n asks a new question via ctx.ui.input, c copies the answer, ctrl+t toggles reasoning (hidden by default with the main session's collapsed 💭 thinking · snippet label), Esc closes. With no args and history the panel opens on the newest aside; with no history it goes straight to the question input. The child runs without tools (see Key concepts) on a session file seeded with the parent conversation up to the spawn point (see btw context inheritance); the prompt is prefixed with BTW_PROMPT_PREFIX telling it to answer from that shared context in a single response. Result arrives as a btw-result entry + notify, not a model follow-up. | | /subagents | — | Opens the fullscreen dashboard overlay (SubagentDashboard) of model-origin subagents only (btw asides are excluded): j/k or ↑/↓ select, Enter takes over, x aborts a running agent. Empty state notifies "No subagents yet." | | /subagent-model | — | Picker flow per harness: pick harness (shows current defaults) → pi: curated model list cheapest-first (cost + "over ceiling" markers) + supported thinking-level selector; claude: alias list (sonnet, haiku, opus, fable) + effort list. Saves atomically to shared/subagent-models.json, notifies the choice. |

Events

  • session_start — capture the ExtensionContext; in TUI mode register the task-rail widget (setWidget("subagent-task-rail", …, { placement: "belowEditor" })) and the raw onTerminalInput handler (down double-tap gesture, see Key concepts).
  • agent_settledflushResults(): drain the deferred-result queue and deliver each as a subagent-result follow-up message.
  • session_shutdown — teardown: clear sessionContext, remove the task-rail widget, unsubscribe the raw-input handler, reset rail + result queue, await runtime.dispose() (disposes every subagent scope; child session_shutdown hooks are emitted bounded at 5 s).

Message / entry renderers

  • registerMessageRenderer("subagent-result") — renders the follow-up message for settled subagents (status glyph, id · title · finished/failed, markdown body when expanded, 8-line preview otherwise).
  • registerEntryRenderer("btw-result") — renders /btw answers as session entries. Collapsed: just the by the way · title · answered/failed header plus a /btw to reopen hint, so answers don't flood the transcript; expanded: the full markdown body inline.

Config

  • extensions/shared/subagent-models.json — persisted per-harness defaults { pi: { provider, model, effort }, claude: { model, effort } }; the on-disk file is mutable (user-set via /subagent-model, currently pi: deepseek/deepseek-v4-flash @ max, claude: sonnet @ high), and the code fallback constants DEFAULT_SUBAGENT_MODELS default to pi: deepseek/deepseek-v4-pro @ high, claude: sonnet @ high. Read via loadSubagentModels() (per-harness fallback to defaults), written atomically (temp + rename) by /subagent-model.
  • Env PI_SUBAGENT_COST_CEILING — USD/Mtok output ceiling for agent-chosen models (default 10; "off" disables). Set at sonnet-5's price so haiku-4.5/sonnet-5 are selectable while the $15+ tier is not.

Other exported functions (internal modules; import for tests/builders)

  • domain.ts: BACKEND_NAMES, REASONING_EFFORTS, types BackendName/SubagentOrigin/ReasoningEffort/SubagentStatus/ParentContext/SpawnTask/SubagentMeta/TranscriptPart/TranscriptItem/LiveToolState/QueuedMessage/RunOutcome/SubagentEvent/SubagentSnapshot, tagged errors SpawnError/BackendUnavailableError/ConcurrencyLimitError/SendError, helpers latestText/formatElapsed.
  • backend.ts: SubagentBackend, SubagentSession, BackendCapabilities (interface), BackendRegistry (service).
  • backends/pi.ts: piBackend; backends/claude.ts: claudeBackend, contextOccupancyTokens (per-request context occupancy; the result-message usage is a whole-run aggregate and must never be used as occupancy); backends/stub.ts: makeStubBackend (scripted test session; FAIL:-prefixed prompts settle as errors), StubProfile (interface).
  • manager.ts: SubagentManager (service), SubagentManagerLive (layer), MAX_RUNNING = 50, MAX_TRACKED = 4_096, slugifyTitle, SubagentManagerShape, SubagentReadModel, CancelResult.
  • runtime.ts: createSubagentRuntime(), SubagentRuntime (type, ReturnType<typeof createSubagentRuntime>), runTool(runtime, effect, { signal?, interruptMessage? }).
  • prompt.ts: all SUBAGENT_*_TOOL_DESCRIPTION / *_PARAMETER_DESCRIPTIONS / SUBAGENT_SPAWN_PROMPT_SNIPPET / SUBAGENT_SPAWN_PROMPT_GUIDELINES, buildSubagentSpawnResult, buildSubagentResultMessage (preview + subagent_wait pointer, never the full output) — the single source of model-facing strings.
  • by-the-way.ts: deriveBtwTitle, isModelVisible, BTW_TITLE_MAX_LENGTH; result-delivery.ts: createDeferredResultDelivery; format.ts: formatContextUtilization, contextPercent, formatCompactTokens, ContextUtilization (interface).
  • ui/: openSubagentPicker, openSubagentTakeover, reconcileDashboardSelection, configuredKeys, statusGlyph, statusWord, DashboardSelection (interface, takeover.ts); openBtwPanel, BtwPanelResult (btw-panel.ts); TaskRailController, visibleRailSubagents, createTaskRail (task-rail.ts); findFocusedComponent, isDefaultEditorFocused (focus.ts); buildTranscriptLines, buildBtwAnswerLines, sanitizeText, mergeTranscripts (transcript.ts); pickHarness, pickPiModel, pickPiEffort, pickClaudeModel, pickClaudeEffort (model-picker.ts).
  • persisted/: readPersistedTranscript (lazy async generator), loadPersistedTranscript, SessionFormat (type) + detectSessionFormat (transcript.ts); parsePiEntry (pi.ts); ClaudeParser (interface) + createClaudeParser (claude.ts); shared parseJsonLine/safeJsonPreview/previewOf/textOf/isRecord.

Examples

  1. Delegate and keep working — the parent spawns a self-contained task and continues; the result arrives automatically as a subagent-result follow-up (a preview in context; the full output renders on expand): subagent_spawn { prompt: "Read docs/errors.md and list the three most common failure modes with their codes, as a bullet list.", name: "errors survey", harness: "pi" }{ id: "sa-errors-survey", … }; later the agent uses subagent_check { id: "sa-errors-survey" } to peek without consuming, or subagent_wait { ids: ["sa-errors-survey"] } to pull the complete output when it cannot proceed without it.
  2. Deliberate harness + model choice — a Claude Code task: subagent_spawn { prompt: "In this repo, find every TODO and categorize by owner file; write the result to TODOS.md.", name: "todo sweep", harness: "claude", model: "sonnet", reasoning_effort: "medium" }. Omit model/reasoning_effort unless the user named them — both harnesses default to /subagent-model and the ceiling rejects self-chosen expensive models.
  3. User aside — the user types /btw what changed in the subagent API in this update?; a btw-what-changed-in-the-… subagent inherits the conversation up to that point and answers while the main agent keeps working; the answer appears as a one-line by the way · … session entry, reopened in the /btw panel or expanded inline.
  4. Manage/subagents opens the dashboard of model-origin subagents (select with j/k or ↑/↓, x aborts a running agent, Enter takes over: scroll the transcript with ↑/↓/pgup/pgdn, ctrl+t toggles thinking (hidden by default, like the main session), ctrl+o toggles tool calls, ctrl+c aborts the run, the interrupt binding closes the view, type at the input line to steer/continue); /btw with no args opens the aside panel (←/→ switches between past questions, n asks a new one, c copies the answer); /subagent-model changes the per-harness defaults.