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

open-subagent

v0.1.8

Published

Multi-provider subagents for Claude Code, powered by pi.dev

Readme

open-subagent

Multi-provider subagents for Claude Code, powered by pi.dev (@earendil-works/pi-coding-agent). See SPEC.md for the full design.

open-subagent is a stdio MCP server that exposes an open_subagent tool which behaves like Claude Code's built-in Agent tool, except each "subagent type" (profile) can run on any model pi supports — Anthropic, OpenAI, Google, DeepSeek, Mistral, Groq, Bedrock, OpenRouter, and more.

Install

npm install -g open-subagent

Requires Node.js >= 22.19.0 (pi's own minimum) and the pi binary on PATH:

curl -fsSL https://pi.dev/install.sh | sh
# or
npm i -g --ignore-scripts @earendil-works/pi-coding-agent

npm install cannot prompt for setup answers (no TTY during a package install), so after installing, run the interactive setup once per project:

cd your-project
open-subagent init

Setup (open-subagent init)

open-subagent init writes a starter .open-subagent/config.json with four profiles — agent (catch-all), general (research/multi-step), explore (read-only search), plan (read-only architecture planning) — all pointed at one model you configure interactively:

  • provider name (e.g. ollama, openai, anthropic)

  • model id

  • base URL (optional — only for a custom/OpenAI-compatible endpoint; when given, it's merged into ~/.pi/agent/models.json as a custom provider, without touching any existing providers/models there)

  • model — when a base URL is given, init lists the models the endpoint actually serves and lets you pick one with an interactive list (arrow keys, type to filter, enter to select). Falls back to a numbered prompt when stdin isn't a TTY, and to typing an id when the endpoint won't list.

  • context window in tokens — init tries not to make you guess:

    1. llama.cpp's /props, whose default_generation_settings.n_ctx is the real per-slot figure (free, and usually instant);
    2. the endpoint's model list — vLLM's max_model_len, Ollama/OpenRouter's context_length, LM Studio's max_context_length, LiteLLM's max_input_tokens, …;
    3. failing that, it offers to measure it — a deliberately oversized request, refused during validation before any generation, with the limit read out of the refusal (error.n_ctx, or "maximum context length is N tokens"). Usually one request and about a second;
    4. failing that, it asks you, defaulting to 131072.

    This matters more than it looks. pi falls back to 16384 tokens for a model entry that declares no window, and an agent then stops with stopReason: "length" mid-reasoning and returns nothing at all. Declaring more than the endpoint allows is just as bad — the server rejects the request outright. Note the number that matters is the context available to one request, which is not always what the server was configured with: llama.cpp divides its KV cache across --parallel slots, so a 65536-token cache serving 4 slots gives each request 16384. For the same reason, open-subagent only ever believes fields describing what the server serves: llama.cpp reports n_ctx_train: 262144 right next to n_ctx: 16384, and declaring the trained size would make every request fail.

    open-subagent doctor warns about any profile whose model entry is missing a contextWindow; open-subagent doctor --probe measures each profile against its live endpoint and flags any that declares more than the endpoint allows.

  • whether to deny Claude Code's native Agent tool in this project (merged into .claude/settings.json's permissions.deny, preserving everything else already there)

  • whether to register the open-subagent MCP server in this project's .mcp.json (merged in without clobbering other servers)

  • whether to enable the dashboard (local web UI, off by default) and, if so, which port it listens on (default 8787) and whether to expose it to your LAN. The exposure question is only asked when you have already said yes to the dashboard, and defaults to no: the dashboard has no authentication, so binding 0.0.0.0 lets anyone who can reach the machine read every agent prompt, transcript and result.

It refuses to overwrite an existing .open-subagent/config.json unless you confirm (or pass --yes), and prints a summary of files written plus a reminder to restart your Claude Code session.

Every prompt can be skipped non-interactively with flags:

open-subagent init \
  --provider openai --model gpt-5 --api-key '$OPENAI_API_KEY' \
  --base-url https://api.example.com/v1 --context-window 131072 \
  --disable-agent --mcp \
  --dashboard --dashboard-port 8787 --no-dashboard-lan \
  --yes

--no-dashboard forces it off; --dashboard-port <n> implies --dashboard. --dashboard-lan binds 0.0.0.0 and --no-dashboard-lan forces loopback; --dashboard-host <h> sets any interface directly. Like --dashboard-port, --dashboard-lan and --dashboard-host imply --dashboard, and any of them suppresses the interactive exposure question. --context-window <n> only affects models.json, so it needs --base-url; it skips discovery entirely. --probe-context / --no-probe-context force the measurement step on or off without prompting.

Configure

Profiles can also be hand-edited or defined directly, in either (or both) of:

  • Global: ~/.config/open-subagent/config.json
  • Project: <project>/.open-subagent/config.json

A project profile with the same display name fully replaces the matching global profile (wholesale, not field-merged).

{
  "profiles": {
    "agent": {
      "description": "Catch-all for any task that doesn't fit a more specific profile. General coding and analysis on the configured model.",
      "provider": "ollama",
      "model": "qwen3.8-27B-coding-fast",
      "apiKey": "<your-key-or-$ENV_VAR>",
      "thinking": "medium"
    },
    "gpt-researcher": {
      "description": "Deep research and web synthesis. Use for open-ended research questions.",
      "provider": "openai",
      "model": "gpt-5.2",
      "apiKey": "$OPENAI_API_KEY",
      "systemPrompt": "You are a research subagent. Return raw findings, not prose for a human.",
      "tools": ["read", "grep", "find", "ls", "bash"],
      "thinking": "high",
      "readOnly": false,
      "background": true
    },
    "gemini-reader": {
      "description": "Huge-context file digestion on Gemini.",
      "provider": "google",
      "model": "gemini-3-pro",
      "apiKey": "!pass show google-ai-key",
      "readOnly": true
    }
  },
  "defaults": {
    "maxConcurrent": 4,
    "stateDir": "~/.local/state/open-subagent"
  }
}

open-subagent init generates exactly this shape — see .open-subagent/config.example.json for the full four-profile (agent/general/explore/plan) starter template it's built from.

apiKey accepts:

  • a literal string
  • $ENV_VAR or ${ENV_VAR} — resolved from the environment
  • !command — executed via the shell, result cached in-memory for the life of the server process
  • $$ — escapes to a literal $

Full field reference: see SPEC.md §2.2.

Turn cap

A run is killed once it reaches defaults.maxTurns turn_end events (default 500). Treat this as a runaway-loop backstop, not a work budget: one turn is essentially one tool call, so the cap should sit far above any task you actually expect to run. Bounding context growth is not a reason to set it low — pi auto-compacts.

A profile may override it:

{
  "profiles": {
    "explore": { "maxTurns": 60 },
    "agent": { "maxTurns": 800 }
  },
  "defaults": { "maxTurns": 500 }
}

If you want to bound how long an agent may occupy one of your maxConcurrent slots, set defaults.timeoutMs instead. Turns proxy spend; wall-clock proxies occupancy, and on a self-hosted endpoint occupancy is usually the scarce resource.

A capped run is not lost. The session file is recorded from the agent's first event, so an agent killed by maxTurns or timeoutMs can be continued with open_subagent_send, keeping its full transcript. Re-dispatching a fresh agent instead throws that work away.

Requirements check

init verifies the pi binary before writing anything, and exits non-zero if agents could not run — a config pointing at an unusable pi produces failures that look like open-subagent bugs rather than installation problems. Pass --ignore-pi-check to configure anyway; open-subagent doctor reports the same check at any time.

It catches two cases that are otherwise hard to read:

  • pi not installed / not on PATH. Spawning it fails with an empty stderr and a null exit code, which looks like an agent that simply did nothing. Worth knowing: the MCP server passes its own PATH to agents, which is not necessarily your shell's — so a pi that works in your terminal can still be invisible to a subagent. OPEN_SUBAGENT_PI_BIN points at a specific binary.
  • pi too old for the flags open-subagent passes, which shows up as Error: Unknown option: -na. The usual root cause is neither pi nor open-subagent: pi >= 0.8 declares engines: { node: ">=22.19.0" }, so npm running under an older Node quietly installs the last release that predates --name and --no-approve. The check says so explicitly, and names the Node version it found.

The same diagnosis is applied at run time, so an agent failing for either reason reports the cause instead of an unexplained exit code.

Register with Claude Code

Add to .mcp.json (project) or run claude mcp add:

{
  "mcpServers": {
    "open-subagent": {
      "command": "open-subagent",
      "args": ["serve"],
      "env": {}
    }
  }
}

Usage

Once registered, the orchestrator model sees four tools:

  • open_subagent — spawn a subagent on a configured profile. Foreground (run_in_background: false) blocks and returns the agent's final text plus an agentId: continuation line and a <usage> block. Background (the default) returns immediately with the agentId and an output-file path. description names the run (it shows up in open_subagent_list and the dashboard). depends_on: ["os-…"] holds the spawn until the named agents succeed, so dependent work can be ordered without telling an agent to poll for a file a sibling hasn't written yet. A dependency that fails or is killed cancels its dependents rather than releasing them: they are marked failed with a skipped: depends_on … reason naming the agent that broke, because work premised on output that was never produced is worse than no work at all.
  • open_subagent_output — fetch or poll an agent's result. wait: true (the default) blocks until it finishes, bounded by timeout_ms (default 120s) after which it returns a progress snapshot rather than hanging — the agent keeps running, so just poll again. wait: false returns that snapshot immediately: status, elapsed time, turns, tool calls, the tool currently running, and an excerpt of the latest assistant text.
  • open_subagent_list — every agent with its task, status, elapsed time, tool count and current activity. Optional status filter (queued/running/completed/failed/killed).
  • open_subagent_send — continue a finished agent with a follow-up message, resuming its pi session with full transcript history.
  • open_subagent_kill — stop a running or queued agent, with an optional reason recorded as its status. A queued agent is cancelled before it ever spawns; a running one has its process group killed.

There is deliberately no way to steer an agent mid-run. Doing so would mean the orchestrator reading partial transcripts to judge them, pulling the subagent's context back into the parent and undoing the point of delegating. The lever that costs nothing is ending a run, which is what open_subagent_kill is for; otherwise the leverage is in the prompt you spawn with.

Background failures announce themselves. MCP is request/response — a server cannot push into the conversation — so a background agent that died used to be invisible until something thought to poll. Failures are now queued and prepended to the next tool result of any kind:

[open-subagent] 2 background agents failed since your last call:
  os-3f2a: Cannot spawn pi: argument 18 contains a NUL byte...
  os-9c1d: skipped: depends_on did not succeed - os-3f2a (failed)

They are delivered once and then cleared.

Subagents run in the project directory and can read anything already on disk, so point them at files instead of pasting file contents into the prompt. Like Claude Code's native subagents they also load the project's CLAUDE.md / AGENTS.md (set "contextFiles": false on a profile to opt out).

The orchestrator is never told which model backs a profile: results name the profile only. Profiles are chosen by name and description, and knowing the model behind one changes how much work gets delegated to it. The dashboard and doctor still show models — a human is the audience there.

Run open-subagent doctor to print the resolved config (API keys masked to their source, never their value), confirm the pi binary is on PATH, and check every profile's declared context window. Add --probe to measure each one against its live endpoint. Run open-subagent init to (re-)generate the project config, register the MCP server, or deny Claude Code's native Agent tool — see Setup above. Run open-subagent dashboard to browse agents and transcripts in a browser — see Dashboard below.

Dashboard

A small read-only web UI over the same registry and transcripts the tools use: the list of agents this machine has run (named by their description), their status/profile/model/access/tool counts/duration/tokens, each agent's transcript, and its final output.

The transcript reads like a Claude Code session rather than a log dump: the prompt in a collapsible panel, assistant replies rendered as markdown, reasoning folded away behind a summary, and consecutive tool calls grouped under one line (Ran 3 commands, Read 4 files (1 failed)) that expands to show each call, its arguments and its output. Streaming deltas are dropped — they are most of the raw stream and none of the content.

It is disabled by default. Enable it in .open-subagent/config.json (or the global config):

{
  "defaults": {
    "dashboard": { "enabled": true, "port": 8787 }
  }
}

When enabled, the first session that starts spawns it as its own detached process and logs the URL to stderr; every later session finds it and adopts it. With "enabled": false (the default) no socket is opened and no HTTP code runs at all.

One dashboard serves the whole machine. The state dir is machine-wide, so the dashboard already lists agents from every session — it is not scoped to the session that started it. It is deliberately not run in-process by serve: that made every session race for the same port, and the winner took the dashboard down with it when that one session exited. Running it detached means it outlives whoever started it.

dashboard.json in the state dir records the running dashboard's pid, host and port. A clean shutdown removes it; a crash leaves it behind with a dead pid, which the next session detects and replaces.

Leftovers are cleaned up automatically. A dashboard still running from an older install serves the code it was started with, so on the next serve it is identified by /api/meta, stopped, and replaced with the current version — otherwise an upgrade would appear to do nothing until someone noticed and killed it by hand. Two things are never killed: a process on that port that does not identify itself as open-subagent, and an MCP server (serve) from 0.1.5 or earlier whose Claude Code session is still running, since that would strip a live session of its agents. Both cases are logged with the pid, and the session runs without a dashboard rather than guessing.

A session can be alive and still useless, though — abandoned, or running under a Node too old for pi — and then that refusal just leaves you killing pids by hand. --reclaim is the deliberate override:

open-subagent dashboard --port 8787 --reclaim

It stops whatever open-subagent process holds the port, live session or not, reports what it killed, and takes over. It does not override the identity checks: the holder must answer as open-subagent over HTTP and look like open-subagent in its command line, so an unrelated service on the port is still never touched.

It binds 127.0.0.1 unless defaults.dashboard.host says otherwise. init writes that field explicitly — including when it is the default — so a generated config states its exposure rather than implying it. There is no authentication of any kind: on a wider bind, everything the dashboard serves (full prompts, transcripts, tool output, results) is readable by anyone who can route to the machine, so restrict it at the firewall or leave it on loopback. While bound to loopback it also rejects requests carrying a non-loopback Host header, so a web page you visit can't reach it by pointing a domain at 127.0.0.1 — that guard cannot apply to a LAN bind.

To browse past agents outside a Claude Code session, run it standalone:

open-subagent dashboard --port 8787

--host overrides the bind address for that run, and --reclaim takes the port from a leftover process (see above).

Current limitations

| Native feature | open-subagent | Path | |---|---|---| | Haiku activity line in /tasks | elapsed time only (MCP limitation); use open_subagent_list | none available (harness-owned) | | /tasks transcript steering | not available | RPC mode + open_subagent_steer | | Permission prompts to user | pi -na + tool scoping instead | RPC extension_ui_request bridge | | isolation: worktree | not yet | server-side git worktree add before spawn | | Structured output | not yet | terminating-tool extension (§4.5) | | fork mode (inherit parent convo) | n/a (parent convo not accessible to MCP) | — |

Development

npm install
npm run build
npm test        # typechecks src + test, then runs vitest
npm run typecheck

Tests use a fake pi binary fixture (test/fixtures/fake-pi.mjs) that emits canned --mode json JSONL events, so the suite does not require a real pi install. Its event shapes are transcribed from real pi output and must stay that way — an earlier fixture invented its own field names (tool_name for toolName, output_tokens for output) and hid several production bugs behind a green suite.

Set OPEN_SUBAGENT_PI_BIN to point at a pi binary that isn't on PATH.