pi-fork-join

In-process shared-prefix concurrent fork/join for the Pi coding agent. Read-only forks by default; write forks are isolated in their own git worktrees. The parent keeps its context window; children return dense structured reports.

Packages

Package details

extension

Install pi-fork-join from npm and Pi will load the resources declared by the package manifest.

$ pi install npm:pi-fork-join
Package
pi-fork-join
Version
0.1.0
Published
Aug 30, 2026
Downloads
176/mo · 7/wk
Author
guan810
License
MIT
Types
extension
Size
37.6 KB
Dependencies
0 dependencies · 5 peers
Pi manifest JSON
{
  "extensions": [
    "./src/index.ts"
  ]
}

Security note

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

README

pi-fork-join

In-process shared-prefix concurrent fork/join for the Pi coding agent.

Keep your main agent's context window clean while running several independent investigations in parallel. Each fork is a child AgentSession in the same process that inherits the current active branch as its shared prefix, runs a bounded task, and returns only a dense structured report. The parent's context window never receives the forks' intermediate tool logs or thinking.

pi-fork-join is the in-process realization of the "shared-prefix concurrent fork" idea: the execution engine is brand new (no child pi process, no JSONL snapshot round-trip), while the configuration surface is adapted from the pi-fork extension (MIT).

Install

pi install git:github.com/InertialG/pi-fork-join

Or run from source:

pi -e ./src/index.ts

Usage

The extension registers one tool, fork_join:

{
  "tasks": [
    { "id": "data", "task": "Inspect the data layer; return evidence and risk." },
    { "id": "concurrency", "task": "Audit the concurrency/state model; return risks." },
    { "id": "impl", "task": "Implement step 3 and land the patch.", "write": true }
  ],
  "maxConcurrency": 4
}

Each fork inherits the parent conversation up to, but excluding, the assistant message that contains the current fork_join call — the call itself, its sibling task descriptions, and any parallel-status text are not visible inside a branch. fork_join blocks until all forks finish and returns their reports in input order. The parent is responsible for synthesis and any final serial edits.

Context semantics

fork_join locates the current call in the active branch and uses the entry before it as the shared fork base:

C0: existing conversation and tool results
C1: current assistant message
    ├─ optional text
    └─ fork_join toolCall

child_i = C0 + private_task_i

This means:

  • Every child sees the same history that existed before the current call.
  • No child sees the fork_join call, sibling tasks, or parallel-status text.
  • Prior tool results and earlier fork reports that already entered the parent history are treated exactly like any serial result — nothing is deleted or semantically pruned.
  • Locating the call is a hard requirement: if it cannot be found, or is ambiguous, the whole fork_join fails rather than silently falling back to the current leaf (which would leak the running call into children).

A single task failing does not discard the batch; the parent still receives the status of every task.

Isolation model

Isolation is enforced by tool policy, not by filesystem copying — and it is best-effort, not a sandbox. A read-only fork excludes the edit/write tools, which is what prevents the classic parallel-edit clobber race. But both read-only and write forks still have bash, which can create, modify, and delete files. Treat read-only as "no edit/write tools" — not as "cannot touch the filesystem". The parent agent retains final authority and should not delegate destructive or security-sensitive work to any fork without awareness.

Fork kind Tools Working directory When
Read-only (default) read, bash, grep, find, lsno edit/write shared parent cwd investigation, review, evidence collection
Write (write: true) above plus edit, write its own git worktree a fork that must call edit/write

Design rationale:

  • Read-only is the default and needs no worktree. Because no fork has edit/write, concurrent forks cannot clobber each other through those tools regardless of scheduling order. Read-only forks also see the parent's current dirty working tree (uncommitted changes), which is exactly what an investigating agent needs. They still have bash, so they can write files if they try — that is an accepted limitation, surfaced honestly here and in the tool description, not silently hidden.
  • Worktree only for write forks. A write fork is isolated in its own git worktree so its edit/write cannot race the parent or other forks. A worktree forks from the last commit: the parent's uncommitted changes are deliberately not visible inside it. Worktree isolation is also best-effort against a cooperative model; a fork that deliberately writes to an absolute parent path can escape it. Write forks require a git repository; if none exists, that fork fails with a clear error.
  • Non-git directories are fine for read-only forks (they use the shared cwd). Only write: true needs git.

Concurrency

  • Forks run on the single event loop of the parent process. LLM calls, tool subprocesses, and file I/O are all async I/O, so several forks genuinely overlap (wall-clock ≈ slowest fork, not the sum). There is no CPU parallelism; that is a deliberate and correct trade for this I/O-bound workload.
  • Concurrency is bounded by maxConcurrency (default 4, max 8) via a worker pool. This keeps provider calls and worktrees within a sane budget.
  • Cancellation: when the parent tool call is aborted, every running fork's session is aborted. Fork results report cancelled.
  • Failure isolation: each fork runs in its own AgentSession; one fork failing does not cancel the others. Failures are collected per-fork.

Context isolation

  • Only the final assistant report of each fork is returned to the parent.
  • Fork transcripts, tool calls, and thinking never enter the parent context.
  • The shared prefix is rebuilt from the current active branch only (sessionManager.getBranch()); sibling/abandoned branches are excluded.
  • Messages are deep-cloned before seeding, so a fork can never mutate the parent's history.

Settings

Configure under the pi-fork-join key in ~/.pi/agent/settings.json (global) or .pi/settings.json (project). Project overrides global.

{
  "pi-fork-join": {
    "defaultMaxConcurrency": 4,
    "costFooter": true,
    "leanChildren": true
  }
}
  • defaultMaxConcurrency — used when a fork_join call omits maxConcurrency.
  • costFooter — show fork cost as a dim footer status line.
  • leanChildren — children load only the base system prompt + project AGENTS.md, skipping extensions/skills/prompts/themes. Default true (prevents child extension bloat and accidental recursive fork_join).

Development

npm install            # peer deps for typechecking
npm test               # node --test unit tests (pure modules + real git worktrees)
npm run typecheck      # tsc --noEmit against the installed pi packages

The unit tests exercise the pure modules (scheduler, tool policy, report assembly/extraction, config, and git worktree create/remove round-trips). src/fork-runner.ts and src/index.ts are type-checked against the installed pi SDK; integration against a live model/provider requires running inside pi (pi -e ./src/index.ts and calling fork_join).

Important caveats

  • Single-threaded: forked CPU-bound work still serializes on the event loop. This extension is tuned for I/O-bound agent investigation.
  • Read-only is not a sandbox: a read-only fork still has bash, which can create/delete files. It only lacks edit/write tools. The parent agent must not rely on a read-only fork being unable to touch the filesystem.
  • Context pollution on repeated calls: each fork inherits the shared prefix. If you call fork_join repeatedly in one session, later forks inherit the earlier fork reports and may drift into meta-commentary. Prefer a single fork_join call for all parallel tasks, and run clean experiments in a fresh session.
  • Worktree sees only committed state: a write: true fork cannot see your uncommitted parent changes. Use read-only forks (shared cwd) when a fork must inspect the dirty working tree.
  • Report-only contract: only the final report returns to the parent; fork tool logs and thinking never enter the parent context.

License

MIT. The configuration surface and early design were adapted from pi-fork (MIT); the in-process execution engine and shared-prefix context implementation are original to this package.