@justjoshn/pi-pstack

pstack 0.15.36 for Pi with its soft-dependency team-kit skills vendored in: rigorous agent workflows and principles as Pi skills, with the host features they use: subagents, todos, modes, plan mode, the model rule, /loop, subscriptions and routines, AskQu

Packages

Package details

extensionskill

Install @justjoshn/pi-pstack from npm and Pi will load the resources declared by the package manifest.

$ pi install npm:@justjoshn/pi-pstack
Package
@justjoshn/pi-pstack
Version
0.15.36-pi.20
Published
Nov 6, 2026
Downloads
2,401/mo · 2,236/wk
Author
justjoshn
License
MIT
Types
extension, skill
Size
6.7 MB
Dependencies
3 dependencies · 5 peers
Pi manifest JSON
{
  "skills": [
    "./skills"
  ],
  "extensions": [
    "./src/index.ts"
  ]
}

Security note

Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.

README

pstack for Pi (pi-pstack)

pi-pstack is pstack 0.15.36 for Pi 1.5.0: rigorous agent workflows (/poteto-mode and its playbooks), the principle skills, and the review, research, and verification skills, plus the full soft-dependency team-kit set (deslop, control-cli, control-ui, CI/PR helpers, thermo-nuclear review, and the rest) vendored in so you do not install a separate team-kit package. A small extension provides the host features the skills call by name: /name skill commands, /mode, /plan, the Task subagent tool with cloud agents on their own machines, forks, and agent_status, /in-cloud, background shells (bash_background and AwaitShell), TodoWrite, AskQuestion, GenerateImage, SwitchMode, the model rule, /loop, subscriptions (timers, GitHub PRs, Slack channels, webhook routines), automations with memories (/automate, /automations), Projects (/project), Slack tools, request_secret, and visualize.

Get set up

  1. Clone the repository and install the package from the checkout. Pi loads a local package in place, so keep the checkout where it is.

    git clone https://github.com/just-joshn/pi-customizations.git
    cd pi-customizations
    bun install
    pi install ./extensions/pi-pstack

    Pi does not install dependencies for a local package, so bun install (or npm install in extensions/pi-pstack) fetches yaml for plan metadata and, for the macOS read-only shell (readonly subagents and /plan), @anthropic-ai/sandbox-runtime. On Linux the read-only shell runs bubblewrap from the system instead (sudo apt install bubblewrap), with no user-namespace change on Ubuntu 24.04 and later. Where no sandbox can start, a read-only shell command fails instead of running unsandboxed. macOS uses the built-in sandbox-exec. pi-pstack needs Node 22.19 or newer. To try it for one session without installing, run pi -e ./extensions/pi-pstack. To install the published package instead, run pi install npm:@justjoshn/pi-pstack. Installing loads pi-pstack's tools and system-prompt sections at once, but no skill runs until you invoke it. 60 workflow skills stay out of the model's automatic skill list. Setup, create-skill, and the team-kit shipping and review skills (deslop, control-cli, control-ui, CI and PR helpers, and the rest of that set) can load from your words alone.

  2. Run /setup-pstack. It lists the models the Task tool can use, asks for a reasoning budget, maps a model to each role, and writes the model rule. A new rule applies to new sessions.

  3. Start real work with /poteto-mode, a goal, and a check that can pass or fail.

The guide in docs/guide/ walks through setup, /poteto-mode, understanding code, building, verifying, overnight runs, and making pstack yours.

Remove the package with pi remove ./extensions/pi-pstack. Its state lives in ~/.pi/agent/pstack/, or $PI_CODING_AGENT_DIR/pstack/ when that variable is set (written <pstack dir> below): the model rule, routine credential files under routines/, and one folder per workspace under workspaces/ with subagent transcripts, agent stores, projects, artifacts, and isolated-worker worktrees. Delete that directory to remove the state too.

Use it

You type What happens
/poteto-mode <task> The skill attaches to that one message. Every skill Pi loaded works this way: pstack's (/how, /why, /arena, /interrogate, /principle-prove-it-works, /deslop) and your own, such as a /<handle>-mode skill from /automate-me. A /name later in the message attaches its skill too, such as design the metrics. /architect this first., and every skill a message names attaches once, in order. /skill:<name> works too and attaches only that skill.
/mode poteto-mode [task] Keeps the skill in context on every turn until you leave the mode: the skill rides every request in its own system-prompt section, which Pi re-sends only when it changes. The first request after entry says the user entered the mode, later requests carry a reminder that names the skill file, and the request after /mode off or a switch to another mode says the user left it. The footer shows a badge with the skill's icon and color metadata, such as a yellow ♛ mode: poteto-mode (other skills get ⚡). /mode off exits and /mode reports. Any loaded skill can back a mode.
/plan [task] Plan mode: the agent researches with read-only tools (read, grep, find, ls, the sandboxed bash_readonly, AskQuestion, TodoWrite, agent_status, Task, whose launches run readonly: a fresh one runs here with no worktree, and a resume or fork stays where its agent ran, and stop_agent), asks its clarifying questions early, and presents the plan with CreatePlan (plan, name, overview, todos, is_project, phases). CreatePlan writes the plan file under <pstack dir>/plans/ (<name>-<8 hex>.plan.md: the chat id in a comment, the todos as front matter, then the plan), one file per plan name in a chat. Todo dependencies and project phases stay in the file. Building imports the file's unfinished root todos into the session, including edits made during review. Worktree builders import their own lists before the first model request and retain the associated plan on resume. Later writable TodoWrite status updates synchronize those root todos without rewriting other metadata or the plan body. An omitted status imports as pending and is inserted when the todo is updated. A readonly resume keeps its todo changes inside the session. edit and write work on markdown files only, so the agent refines the plan in its file. After a run that saved a plan, an interactive session asks Yes, build locally, Yes, build in cloud (in a git repository: a background isolated worker builds it on its own branch), or No, propose changes. /plan build builds the saved plan too, and /plan off restores your tools. A bare /plan enters plan mode, and re-opens the build prompt when a plan is already saved. The footer shows plan.
/loop 30m <prompt> Runs the prompt now, then again every 30 minutes from each fire, so a long run does not push the next tick out. A tick that comes due while a run is still going fires once when that run settles; several missed ticks still fire once. The interval can come first or last, written as 30m, 10 minutes, or every 10 minutes. Without an interval the agent picks each wake with the Loop tool, and the loop ends when it picks none and no background subagent or background shell is still running. A run that waits on an event (CI finishing, a ref moving, a log line) starts a watcher with bash_background, whose notification wakes the agent, and keeps a long wake as the fallback. Different prompts create independent loops. The same prompt replaces its schedule. /loop lists them. /loop stop ends the only loop, or /loop stop <id> selects one when several are running. Escape interrupts one run and leaves the loop running. Loop results include the loop ID in text and details; pass loop_id to wake, stop, or replace one of several loops. The prompt can be a skill command, such as /loop 1h /poteto-mode run the audit tick. When a playbook says to arm /loop, the agent runs the prompt once in that turn and starts the loop with the Loop tool, whose first scheduled run comes after the interval or wake. Intervals go up to 24 days.
/routines Lists webhook routines with their URL, credential file, and status.
/setup-pstack Configures the per-role models.
/poteto-help <question> Answers a question about pstack and hands you a prompt to send.

/mode, /plan, /loop, and subscriptions need an interactive or RPC session. In the interactive / menu, skills appear under their own names next to Pi's /skill:<name> entries. Aliases work even with enableSkillCommands off. If you have a prompt template or extension command with a skill's name, yours keeps the name; a leading prompt template expands before any other skills the message names attach, and Pi's built-in commands are handled before any alias.

Host features

Feature Behavior
Task (description, prompt, subagent_type, model, run_in_background, resume, fork, fork_turns, readonly, file_attachments, environment, cloud_base_branch) Starts a separate pi --mode rpc process per subagent, so this Pi process can abort it. Foreground calls return the response and an agent ID. Background calls return at once, and a <system_notification> with a <task> block arrives after your turn ends, titled by the first line of the prompt (80 characters at most) and followed by a request to do any follow-up work; the notification card shows the call's description. Print and JSON runs wait for pending notifications before exiting, unless aborted. Several calls in one message run in parallel. A subagent's own subagents cannot spawn more, and every subagent is told to spawn none unless asked. A run lock per agent ID keeps out a second writer, even from another Pi process. A token-directory lease serializes stale-lock recovery while the PID file identifies the transcript writer. Children inherit the parent's project-trust decision. Foreground usage counts toward the session totals. The Task card leads with the call's description, then the agent type and the model that ran, and while a foreground child works it shows its last few tool steps and latest line instead of a blank wait.
resume Sends a follow-up to an idle agent with its context, model, type, and directory. The root chat can resume or fork a descendant that a worktree worker started inside that worktree, and the descendant still runs where it first ran. A follow-up to an agent that is still running, in this or another Pi process, fails with Sub-agent is currently running. You may send the follow-up message when it has completed. Pi's own /resume command reopens a chat session instead.
fork Starts a new agent whose transcript begins as a copy of an idle agent's transcript, in that agent's directory (its worktree for an isolated worker), so it sees the built checkout and uncommitted changes. Type, model, and readonly default from the source; readonly can be added, never lifted. fork_turns: N (only with fork) copies just the first N user turns. fork excludes resume, environment, and cloud_base_branch. Pi's own /fork command branches the chat session instead.
stop_agent agent_id. Stops a running subagent. One this Pi process owns gets an RPC abort, then a kill after a grace period. One another of your Pi processes owns gets a signal to the live pid in its run lock. An idle or finished agent is left alone, and the result says so.
agent_status Reads a subagent's state (running while its run lock names a live pid, idle, or missing), type, model, directory, branch, times, and last reply, without waking it. It also shows whether the agent is readonly, the agent it was forked from, its pid while running, and its transcript. Without agent_id it lists the workspace's 20 newest agents. In the root chat that includes every descendant, isolated workers too.
Subagent types The built-in generalPurpose (default), explore (a file-search specialist the caller tells how thorough to be, with grep, find, and ls on and no Task, that runs on the parent model's -fast variant when Pi lists one), and shell (a command specialist limited to bash and bash_background), then poteto-agent, ci-watcher, comment-sicko, and thermo-nuclear-code-quality-review, read from agents/*.md as plugin agents: on the parent model, in the foreground unless the call sets run_in_background, readonly only through permissionMode: readonly, and with the custom-subagent reminder ahead of each body. A type name matches regardless of case, hyphens, underscores, or a trailing agent, subagent, mode, or type. Unknown types fail with the list.
Models A Pi model spec such as claude-opus-5-5:xhigh or xai/grok-4.7:high, model parameters such as claude-opus-5-5[effort=high] or grok-4.7[fast=true] (picks a listed -fast id of that model), or inherit, inherit-parent, auto, or nothing for the parent model. A bare fast that names no listed model is ignored, and an empty model fails. A model Pi does not list, or a thinking level the model does not support, fails with the allowed list. A bare model id that several providers offer resolves to the parent chat's provider when it offers that id, and otherwise fails with the choices. Older rules that wrote claude-opus-5-5-xhigh still resolve. The system prompt's pstack_subagents section lists the types, the parent model, and the models with their levels.
Cloud agents (environment: "cloud") Each runs on its own machine: a Linux container with a fresh clone of the pushed remote on branch pstack/<agent id> (from cloud_base_branch, else the current branch when it is pushed, else the default branch), with the Pi process and model credentials on this host and every tool routed into the container. It runs detached, keeps running when the chat closes, and reports back when the chat is open again. Without a container engine it runs as a detached worker in a local git worktree, and the system prompt says which. See Cloud agents.
readonly: true The child gets read, grep, find, ls, TodoWrite, and a read-only bash, and no MCP or background shell. That shell runs each command in an OS sandbox that blocks every file write and Unix sockets, while network reads work, so git log, gh pr view, and git ls-remote work and touch fails. On Linux it is bubblewrap with every namespace but the network unshared, the filesystem bound read-only, and a seccomp filter that refuses socket(AF_UNIX) and io_uring_setup; on macOS it is @anthropic-ai/sandbox-runtime's sandbox-exec policy, whose proxy admits every host. Where no sandbox can start, the command fails rather than running unsandboxed. Readonly is not credential isolation for a local child; a readonly cloud agent gets a read-only clone on its own machine.
TodoWrite todos with id, content, status, and merge. Its description says when to keep a list, the four states, and one task in progress at a time. The list follows the session branch, shows above the editor, and rides every request's system prompt. Plan-imported lists persist as branch entries. Their dependencies survive status-only updates, and matching plan-file statuses are synchronized.
AskQuestion title, and per question id, prompt, options (at least two, each id, label, optional description), and allow_multiple, through Pi's select dialog. It is for decisions only the user can make, and a recommended option goes first with (Recommended). Every question ends with Other: (type to answer); a typed answer comes back as other with its text. In print and JSON runs the call comes back as Questions skipped by the user, continue with the information you already have. Subagents have no AskQuestion.
Always-applied rules team-kit's two rule files ship unchanged in rules/ and are not applied: the reference host never puts a plugin's rules in the model's context (parity status).
The model rule <pstack dir>/pstack-models.md, plain Markdown with one line per role. A session reads it when it starts and holds it in its system prompt, so a rule written mid-session applies to new sessions, and only a session whose workspace is under the home directory gets it, as with the reference host's rule file. A pstack-models.mdc from an earlier version is moved there once, without its frontmatter, and kept as pstack-models.mdc.migrated.
Subscriptions subscribe_github_pr (scope pr with prUrl, repo with repo, or author with author and repo) polls with gh every 60 seconds and wakes the chat when a PR's state, head, mergeability, review decision, reviews, or comments change, or when a check concludes or its conclusion changes; the first poll is a baseline. subscribe_timer takes a name, a prompt, and a 5-field cron or delaySeconds, either with once; sessionStrategy wake_self queues a wake for this owning session. With new_session, a live owner starts a background Task and receives its notification. An offline owner permits a detached run with a recorded outcome. Both reuse the model, thinking level, and project-trust decision the timer was created with. Re-subscribing an existing timer name returns created: false. list_subscriptions and unsubscribe manage them. The authoritative registry is <pstack dir>/durable/<workspace>/subscriptions.json. Any session in that workspace can list or cancel subscriptions. Session history does not recreate cancelled or completed subscriptions. Owner-directed events wait for the creating session rather than waking another workspace session. A detached run records its own final message, shown by list_subscriptions. A timer does not expire unless expiresInHours says so (at most 30 days); 0 means no expiry. A GitHub watch keeps a 7-day default because it is this port's own surface.
Webhook routines subscribe_webhook (name, prompt, optional sessionStrategy and environment) asks you to confirm in an interactive or RPC session, then serves http://127.0.0.1:<port>/routines/<slug> (port PSTACK_ROUTINES_PORT, else the port this workspace last bound, else 47913, where 0 picks a free port) and keeps a random sender key in <pstack dir>/routines/<slug>/credentials.json (mode 0600). The result names the URL and the file, never the key. A JSON POST with X-Automation-Key or Authorization: Bearer returns 200 after durable acceptance, and 503 with Retry-After while that routine already holds 20 undelivered events. With the default wake_self, the owner receives a [routine] turn holding the prompt and a <webhook_event> block (headers, body_digest, body, timestamp_ms). With new_session, each accepted POST starts a fresh detached run, the way a hosted automation runs with no chat open, and records its outcome in list_subscriptions. HTTP 200 does not mean the run finished. Each authenticated POST is a separate event, including identical bodies. Storage failure returns 503 rather than acknowledging a lost event, and a full delivery queue refuses the new event instead of dropping an earlier accepted one. A wrong key gets 401, an unknown routine 404, a method other than POST 405, a body over 64 KiB 413, and a body that is not a JSON object 400. A routine does not expire unless expiresInHours says so. make-bot-ui builds a page on top of this.
Durable supervisor Creating a subscription auto-starts pstack-routines-supervisor --workspace <cwd> when it is not already running. Creation waits for that subscription to arm, and webhook creation returns the bound listener's URL. The supervisor reads the durable registry, arms timers and GitHub polls, and owns the routines port. An online owner receives native Pi continuation messages. Owner-directed events accepted while the owner is offline wait for that same session to resume. A timer or webhook routine explicitly configured with sessionStrategy: "new_session" starts detached work while its owner is offline. You can still start the bin yourself. For reboot survival, run packaging/install-supervisor-unit.sh --workspace <cwd> (idempotent; installs the launchd or systemd unit from the examples under packaging/). Print and JSON runs still cannot create subscriptions.
Slack subscribe_slack (name, channel ID, prompt, optional filter, sessionStrategy, environment) polls the Slack Web API every 20 seconds (PSTACK_SLACK_POLL_MS) from a stored read position, so downtime loses nothing, and fires once per new top-level message, or per matching message and thread reply with a filter. Each fire carries a <slack_event> block whose trigger has source_channel_id, ts, thread_ts, user, text, and files. A top-level chat with a token gets slack_read_channel, slack_read_thread, slack_send (a thread reply with thread_ts), slack_react, and slack_update; subagents never get Slack tools or the token. The bot token comes from request_secret (connector slack, field token), /secret slack token, or SLACK_BOT_TOKEN; PSTACK_SLACK_API_URL points at another API root.
Automations A subscription with sessionStrategy: "new_session" is an automation: every fire starts a fresh run with no chat open, and with environment: "cloud" that run is a cloud agent on its own machine with a fresh clone. Each automation keeps MEMORIES.md under <pstack dir>/automations/<name>/, named in every fire. /automate (the automate skill) drafts one from a description, discovers the Slack channel, repository, and integrations, and creates it after approval; /automations lists them and edits a prompt (edit <name>) or disables one (disable <name>).
request_secret and /secret Asks the user for a secret in a masked field and stores it in <pstack dir>/connectors/<connector>/credentials.json (mode 0600) under a field, never in the conversation; /secret <connector> <field> does the same by hand.
/in-cloud The next task you submit (or /in-cloud <task>) runs as a background cloud agent instead of in this chat; its result comes back as a Task notification.
/project /project new <name> starts a Project with this chat as its coordinator, /project add <slug> adds this chat, /project leave, and /project lists them. A Project's tracker (notes.md), store, and chats ride a pstack_project section for the chat and every subagent it starts, and cloud machines mount the Projects folder. The footer shows project: <slug> and listening: <n> for the subscriptions the chat owns.
Stores and projects The system prompt names "the current agent's store" (per session), "the root agent's store" (the main chat's, named to every subagent however deep), and the workspace's projects/ folder, where a program's Project is <slug>/notes.md (its tracker) and <slug>/store/.
visualize title, kind (bar or line), labels, series (name, values), and unit render a Unicode chart in the result and write a standalone SVG to the session's artifacts folder.
Transcripts The system prompt names this workspace's Pi session directory, the current session file, and this workspace's subagent transcripts.
GenerateImage Generates an image (description of at least six words, filename, reference_image_paths, aspect_ratio) on Pi's image models through ctx.modelRegistry.generateImages(). It is offered when an image model is available (for example an OpenRouter image model), not to readonly subagents or in plan mode, and saves each image to the session's artifacts folder. Without an image model, the host section tells skills to draw a Mermaid diagram instead.
Background shells bash_background (command, description, block_until_ms, notify_on_output with pattern, reason, and debounce_ms, working_directory) runs a command in the background, or waits up to block_until_ms for it and then moves it there. Output goes to <pstack dir>/workspaces/<workspace folder>/terminals/<shell id>.txt, which starts with a header (pid, cwd, command, title, status, started_at, and running_for_ms, refreshed every 5 seconds) and ends with an exit footer. After the turn, a <system_notification> with a kind: shell task block arrives when the command finishes (status: success, error with exit_code=N, or aborted) and when a line matches notify_on_output (the first match at once, later ones counted and sent after each debounce_ms window, at least 5 seconds). AwaitShell (shell_id, block_until_ms, pattern) polls or waits on a job, or sleeps without one; seeing a job end there skips its notification. Print and JSON runs wait for their jobs before they exit. Ending the session stops running jobs. Readonly subagents and plan mode do not get these tools.
SwitchMode target_mode_id (plan or agent) and explanation. The agent proposes entering or leaving plan mode, and the switch happens only after you confirm it. Offered in interactive and RPC sessions.

Cloud agents

environment: "cloud" (and /in-cloud, plan mode's "build in cloud", and automations with environment: "cloud") give an agent its own machine:

  • Isolation. With a container engine (Docker Desktop, OrbStack, Colima, or PSTACK_CLOUD_DOCKER_CONTEXT), the machine is a container pstack-<agent id> from the repository's .pi/environment.json (image or build.dockerfile with build.context, install, start, terminals) or PSTACK_CLOUD_IMAGE, default node:24-bookworm. The clone of the pushed remote sits at <pstack dir>/workspaces/<workspace>/machines/<agent id>/repo, bind-mounted at the same path, with the Projects folder and the root agent's store. Nothing else from this host is there: no uncommitted or unpushed work, transcripts, simulators, or credentials, except the GitHub token from gh auth token for git push and gh. Ports and processes are the container's own.
  • Tools on the machine. The agent's Pi process stays on this host with your model credentials; bash, bash_background, read, write, edit, ls, find, and grep act on the machine, and its own subagents run there too.
  • Detached. The run is a pi --mode json process that survives the chat. agent_status shows its machine and last run; stop_agent stops it and its container; a resume boots the stopped container with its disk. A result that arrives while the chat is closed is delivered when that chat opens again. The container stops when its run ends.
  • Without an engine (or with PSTACK_CLOUD_DRIVER=worktree) the agent runs as a detached worker in a git worktree from your HEAD, sharing this host, and the system prompt tells the model so. PSTACK_CLOUD_DRIVER=container fails instead when no engine answers.

Subagent transcripts are Pi sessions in <pstack dir>/workspaces/<workspace folder>/subagents/, with the agent ID in each file name, next to a small record that resume, fork, and agent_status read. Records belong to the main chat's shared workspace state, so the root chat can act on a descendant that ran in a worktree. Children inherit your installed extensions and MCP servers, and local ones run in your working directory. Give each local subagent that writes its own git worktree, or launch it with environment: "cloud", because a child process does not isolate the filesystem. A child exits on its own if its parent Pi process dies.

Limits

  • A container machine runs on the container engine you point Pi at. With a local engine, a sleeping laptop pauses it, because the agent's Pi process runs on this host. A machine's disk stays under machines/<agent id>/ and its stopped container stays until you remove them (docker rm pstack-<agent id>, or the worktree-cleanup playbook). Worktree workers (no engine) share this host's ports, credentials, and simulators.
  • The supervisor can accept events after chat exit, but owner-directed events wait until the owning session resumes. Explicit new_session timers and webhook routines can spend tokens with no human watching. Detached runs do not supply the live owner's Task notification flow. For reboot survival, run packaging/install-supervisor-unit.sh --workspace <cwd>. If the supervisor cannot start, an explicitly reported in-process fallback stops when that Pi process exits. A GitHub subscription restarted after downtime takes a new baseline, so changes made while nothing was polling do not wake it. Each workspace binds its own routines port. With no explicit port, a busy default falls back to an ephemeral port that the workspace then reuses and reports, so two workspaces serve routines at once. An explicit PSTACK_ROUTINES_PORT is a configuration: if it is taken, the routine reports that error instead of moving. The listener binds 127.0.0.1, so a page served to other machines forwards through a local server, as make-bot-ui describes.
  • Event delivery is at-least-once. A process crash after Pi queues a message but before its durable acknowledgement can repeat a wake or a fresh run. Automation prompts must make external actions idempotent. Up to 20 undelivered events per subscription stay queued until delivery or cancellation, and a new event beyond that is refused rather than replacing an older accepted one. Expiry stops new occurrences rather than discarding accepted work. Failed detached executions retain their event for retry and expose the error in list_subscriptions. Cancelling a subscription drops pending undelivered events, but cannot undo work already handed to Pi.
  • The Benny automation pack under automations/benny/ runs as two Slack-triggered automations (subscribe_slack, new_session, environment: "cloud") created through /automate; it needs a Slack bot token and the supervisor running (install the unit for reboot survival).
  • /plan uses bash_readonly for its shell rather than replacing bash, so your shell settings stay as they are outside plan mode.
  • Background subagent token usage is recorded in the child's transcript and in the usage field of the notification's details, but does not enter the parent session's totals: Pi 1.5.0 has no public API for adding a background completion's usage to the parent totals. Native tool-result usage covers synchronous nested work, not later notifications.
  • /reload, /new, and Pi's /resume and /fork commands stop running background subagents, with a warning that names how many. Continue one later with Task resume and its agent ID.
  • A print or JSON run waits for its background subagents before it exits, with no timeout of its own, unless it is aborted.
  • Subagents start the same Pi script that runs the parent (process.argv[1]), or pi from PATH. Embedding the package through the SDK in another host is not supported.
  • Depth limits apply to Task. A writable subagent can still start pi through bash, and that process is outside the depth limit and parent watchdog.
  • A child loads your installed extensions plus this package. If you run one checkout with -e while a different checkout of pi-pstack is installed, the child loads both and fails on duplicate tools; use one copy, or a separate PI_CODING_AGENT_DIR, as live:e2e does.
  • Subagents run headless in RPC mode, so an extension that asks for permission through a dialog cannot ask there.
  • On macOS some TLS clients that ask the keychain to finish a certificate chain (/usr/bin/curl, LibreSSL builds) fail inside the read-only sandbox; gh, git, Homebrew curl, and Node's fetch work. Private and loopback addresses bypass the sandbox proxy and are refused.
  • The read-only shell sandboxes shell subprocesses only. On Linux it needs bubblewrap that can create its user namespace (Ubuntu's default bwrap AppArmor profile allows that) on x86-64 or arm64; bubblewrap loads the seccomp filter itself, so no nested namespace is needed. A missing bwrap, another architecture, or a namespace refusal blocks the shell command. Unix sockets stay blocked because a host socket service can mutate files or access the network on a command's behalf, and the shell never falls back to permitting them. Do not treat a readonly child as a credential boundary. Environment variables and readable credential files are still available to its read tools.
  • A subagent's background shells stop when the subagent finishes, and its launch result says so, because a subagent is its own process. A print or JSON run waits for its background shells, with no timeout of its own, so a job that never ends keeps the run open.
  • Whether a model follows a playbook is the model's call.
  • Workspace folders follow Pi's session-folder naming, so two paths that differ only by / versus - share a folder. Resume and fork refuse an agent whose record names the other directory, except that a record created inside this workspace's own worktrees belongs to this workspace. Subscription reads and mutations also reject a registry owned by the other workspace.
  • The run lock that keeps one writer per agent ID names the process writing the transcript. A lock file without a readable pid counts as held; the error names the file to delete. agent_status shows such an agent as idle. A token-directory lease beside the PID file serializes stale-lock recovery. A dead process or an unreaped zombie does not count as a live writer. Older installed versions without the lease do not participate in this protection.
  • skills/poteto-mode/scripts/watch-pr needs Bun and installs its one dependency on first run. check-plan.mjs needs only Node. worktree-audit.sh needs rg and jq, and works with BSD (macOS) or GNU stat and date.
  • statechart runs skills/statechart/scripts/statechart on Node 22.18 to 22.x, Node 23.6 or later, Bun 1.1.5 or later, or Deno 2.0 or later. The first run installs TLC and Java through mise and needs the network. Without a SPEC it prints Give one SPEC.tla. and its usage, then exits 2.

Maintain it

The skills, agents, guide, and automation pack are this package's own files; edit them in place. They track pstack 0.15.36 in behavior. docs/parity-status.md records what was reconstructed, what each host translation uses, and which differences remain host-bound. The record of the move to Pi-native text and the host features added for it is at the repository root, in parity/pi-pstack-0.15.36/ (design and decisions); earlier records are in the root's parity/pi-pstack-*. Four mechanisms Pi 1.5.0 does not export keep a hand-written implementation here: docs/native-gaps.md names each one, the evidence, and the public API that would replace it.

bun run typecheck
bun run test
bun run test:statechart                               # full compiler suite; set TLA2TOOLS and JAVA for an existing verified toolchain
bun run live:e2e -- --model deepseek/deepseek-flash      # real-model scenarios; needs credentials and gives the model under test your real auth.json (see the script header)

Licenses

The port's own code is MIT (LICENSE). pstack's files keep their MIT notice in licenses/pstack.LICENSE (© 2026 Lauren Tan), and the vendored team-kit skills and agents keep theirs in licenses/cursor-team-kit.LICENSE.