- Status: proposed
- Working name:
pi-code-tool-ts - Last updated: 2026-07-19
- Reference roadmap: PI_CODE_MODE_ROADMAP.md
Build a stock Pi extension that gives the model a persistent, fully trusted Bun and TypeScript workbench whose programs can call Pi tools, process intermediate results outside model context, and return only useful output.
Ordinary tool calling forces the model back into the loop after most operations:
model → search → model → read → model → read → model → aggregate → answer
Code mode lets the model express the workflow once:
model → TypeScript program → search/read/filter/aggregate → result → model
The expected benefits are:
- Fewer model inference round trips.
- Less intermediate tool output in model context.
- Lower token usage and latency on multi-tool work.
- Reliable loops, branching, filtering, aggregation, and parallelism.
- A typed programming interface over Pi tools.
- A foundation for richer trusted, remote, and sandboxed runtimes later.
This feature is not meant to replace direct tool calls for simple one-step operations.
V0 is intentionally trusted execution.
The model receives one Pi tool named code and writes TypeScript such as:
const files = await tool.find({ pattern: "**/*.ts" });
const matches = await Promise.all(
files.map(path => tool.grep({ pattern: "TODO", path })),
);
display(matches.filter(result => result.count > 0));The program runs in a persistent Bun subprocess and may use normal Bun, Node, filesystem, process, network, and npm capabilities. It has the same practical trust level as giving the model an unrestricted shell.
V0 must state this honestly. A subprocess provides lifecycle and crash isolation; it is not a security sandbox.
- A stock Pi extension that registers
code. - Real TypeScript execution through Bun.
- A persistent subprocess per Pi session.
- Top-level await.
- Final-expression display.
- Persistent top-level variables, functions, classes, and imports.
tool.read,tool.grep,tool.find,tool.ls,tool.write,tool.edit, andtool.bashadapters.- Sequential and parallel tool calls from TypeScript.
- Streaming console output.
- Structured
display()output for text and JSON. - Timeouts, cancellation, reset, crash recovery, and session disposal.
- Output limits and full-output artifacts.
- Clear traces and errors for code and nested tool calls.
- A benchmark showing whether code mode saves turns, context, or time on representative tasks.
- Security sandboxing.
- Remote execution.
- Python, Ruby, or Julia.
- Durable restoration of arbitrary live closures after Pi restarts.
- Branch-safe replay of side effects.
- Per-tool confirmation prompts as a security boundary.
- Saved reusable tools.
- Images, charts, or Jupyter MIME bundles.
- Nested model completions or subagents.
- Automatic bridging of every extension and MCP tool.
- A Worker-based runtime.
These remain roadmap items rather than disappearing from scope.
The intended V0 is a focused intersection of the strongest ideas in Oh My Pi and pi-code-tool, not a complete clone of either.
Reference: can1357/oh-my-pi. The research workspace used a local clone for source inspection; third-party source is not included in this repository.
OMP currently provides:
- Persistent Python and Bun/JavaScript by default.
- Optional Ruby and Julia.
- A Bun subprocess with Worker and inline fallbacks.
- TypeScript stripping, import rewriting, top-level await, final-expression display, and persistent lexical declarations.
- Generic access to internal session tools through
tool.<name>. - Streaming output, JSON, Markdown, images, truncation, and artifacts.
completion(),agent(),parallel(),pipeline(), progress events, and budget helpers.- Extensive lifecycle and regression coverage.
OMP's relevant limitations for this project:
- Its full runtimes are trusted rather than sandboxed.
- It is deeply coupled to OMP's private session/tool APIs.
- State is primarily live-process state rather than fully durable branch-safe replay.
- It does not provide a first-class generic remote runtime backend.
- It does not expose the granular capability-policy product we eventually want.
Lessons to borrow:
- JavaScript runtime semantics and source transformation.
- Subprocess-first lifecycle and hard cancellation.
- Tool-call IPC protocol.
- Output streaming and artifact handling.
- Compatibility tests and failure cases.
Any copied or adapted code must retain MIT attribution and be called out in the relevant commit learning note.
Reference: pi-code-tool package and source.
pi-code-tool currently provides:
- Sandboxed Python through Pydantic Monty.
- Direct Pi adapters for
read,grep,find,ls,bash,edit, andwrite. - Mid-execution approval suspension and later resumption.
- Branch-safe state through transcript replay and a tool-call cache.
- Static checking against generated Python stubs.
- Resource limits, traces, and streaming output.
- Saved reusable tools.
- A mature installable package and test suite.
Its intentional constraints:
- Guest programs use a restricted Python subset.
- No arbitrary Python packages or full CPython standard library.
- No full Bun, Node, or npm environment.
- No OMP-style JavaScript notebook and rich orchestration runtime.
Lessons to borrow after V0:
- Durable session reconstruction.
- Side-effect-safe replay.
- Approval suspension/resumption.
- Generated tool typings/stubs.
- Saved human-readable tools.
- Branch-correct state and test strategy.
| Area | OMP | pi-code-tool |
Trusted V0 | Long-term target |
|---|---|---|---|---|
| Guest language | Full JS/TS, Python, Ruby, Julia | Restricted Python | Full Bun/TS | TS sandbox + full Bun |
| Context-saving tool composition | Yes | Yes | Yes | Yes |
| Persistent notebook bindings | Yes, live | Replay-based variables | Yes, live | Live plus durable state |
| npm/Bun/Node APIs | Yes | No | Yes | Yes when backend permits |
| Pi portability | OMP internal | Stock Pi package | Stock Pi package | Stock Pi package |
| Sandbox | No | Yes | No | Yes, optional backend |
| Remote full runtime | No generic backend | No | No | Yes |
| Durable branch restoration | Limited | Yes | No | Yes |
| Rich output/nested agents | Strong | Focused/minimal | Basic | Strong |
| Approval model | Outer exec trust | Mutation gates/global auto-approve | Explicit full trust | Per-capability profiles |
V0 should reach approximately:
- 75–80% of OMP's user-visible Bun/TypeScript code-mode core.
- The central context-saving behavior of
pi-code-tool. - Less security and durability than
pi-code-tool, explicitly and intentionally. - Less rich output and orchestration than OMP.
V0 is successful when it is useful and understandable, not when its feature count matches either project.
Pi process
├─ extension and `code` tool
├─ session/runtime manager
├─ Pi tool adapters
├─ output renderer
└─ Bun IPC transport
│
▼
Persistent Bun subprocess
├─ TypeScript/source transformation
├─ notebook state
├─ console/display capture
├─ tool proxy
└─ user program
The host owns:
- Pi integration.
- Tool adapters and validation.
- Runtime lifecycle.
- Timeout and cancellation decisions.
- Output truncation and artifacts.
- Session identity.
The child owns:
- TypeScript execution.
- Persistent lexical state.
- Imports relative to the session working directory.
- Console and display capture.
- Suspending and resuming promises around host-tool calls.
Host and child communicate only through a versioned, serializable protocol.
The exact package name can change, but the intended separation is:
pi-code-mode-ts/
├─ extensions/
│ └─ index.ts
├─ src/
│ ├─ extension.ts
│ ├─ protocol.ts
│ ├─ process-manager.ts
│ ├─ tool-registry.ts
│ ├─ output.ts
│ └─ runtime/
│ ├─ entry.ts
│ ├─ engine.ts
│ ├─ transform.ts
│ └─ prelude.ts
├─ test/
├─ examples/
├─ docs/
│ ├─ adr/
│ └─ learning/
└─ package.json
Each roadmap unit below is intended to become one successive commit. A commit may be split further if implementation reveals two independently understandable ideas. Multiple units should not be combined merely because an LLM can implement them in one pass.
Each commit must:
- Leave the build and existing tests green.
- Add or update tests for its behavior.
- Include a small runnable demonstration when user-visible.
- Add a learning note under
docs/learning/. - Avoid unrelated refactoring.
- Record copied/reference code and license attribution.
- State limitations and the next missing behavior.
- Be independently revertible.
Every implementation commit adds a file such as:
docs/learning/M1.2-child-handshake.md
It contains:
- Problem — what was impossible before this commit.
- Mental model — the simplest explanation of the new mechanism.
- Code tour — the files and important functions in reading order.
- Protocol or data flow — inputs, outputs, state, and failure path.
- Try it — one command and expected result.
- Tests — what is proven and what remains unproven.
- Tradeoffs — why this implementation was chosen.
- Review questions — two or three questions a human should be able to answer before continuing.
The learning note is part of the feature, not optional commentary.
For each unit, the LLM should:
- Read this document, the current milestone note, relevant ADRs, and the previous learning note.
- Restate the single unit's behavioral outcome.
- Identify files expected to change.
- Write or update the narrowest meaningful tests.
- Implement only that unit.
- Run the stated verification commands.
- Write the learning note and update the milestone checklist.
- Report the diff, tradeoffs, test evidence, and remaining limitation.
- Stop for human review before beginning the next unit.
The LLM should not create a commit unless explicitly asked. After human review, the unit should be committed without bundling subsequent work.
Before accepting a unit, the human builder should be able to:
- Explain the new mechanism without reading its implementation line by line.
- Run the demonstration and intentionally trigger one failure path.
- Identify where state lives and who owns cleanup.
- Explain what the tests prove and do not prove.
- Revert the commit without breaking earlier milestones.
If this is not possible, the unit or its learning note is too large.
Suggested commit shape:
feat(runtime): add versioned child handshake
- establish host/child protocol version negotiation
- reject incompatible child versions
- document lifecycle and failure behavior
Suggested prefixes:
chore:package and tooling only.test:executable specification without new product behavior.feat:new behavior.fix:correction to established behavior.docs:learning material or decisions without runtime changes.
Dependency additions should be isolated when practical so their purpose and generated lockfile changes are easy to review.
This is the canonical commit-sized implementation sequence. Current delivery status lives in PROJECT_PROGRESS.md, and detailed per-milestone handoffs live under docs/milestones/. The milestone labels in PI_CODE_MODE_ROADMAP.md describe strategic architecture phases and are not implementation-unit identifiers.
Outcome: a human understands the product boundary, can load the extension skeleton in Pi, and can run a test suite before runtime complexity begins.
Commit outcome:
- Create the package layout, Pi manifest, TypeScript configuration, and Bun test command.
- Add a minimal extension entrypoint that loads without registering experimental behavior.
Proof:
bun testpasses.- Pi loads the extension without error.
Human understanding:
- How Pi discovers and loads an extension.
- Which code runs in the Pi process.
Commit outcome:
- Add configuration requiring an explicit trusted-execution opt-in.
- Show a clear warning or description that code has shell-equivalent authority.
Proof:
- The
codetool is unavailable or refuses execution without opt-in. - Tests cover enabled and disabled configurations.
Human understanding:
- Why a child process is not a sandbox.
- What authority the model receives.
Commit outcome:
- Define executable fixtures for the eventual V0 behaviors without implementing them all.
- Mark future behaviors as skipped or expected failures with milestone references.
Fixtures cover:
- Basic TypeScript.
- Top-level await.
- Persistent declarations.
- Imports.
- Tool calls.
- Parallel tool calls.
- Timeout recovery.
- Output truncation.
Proof:
- The acceptance suite runs and clearly distinguishes implemented from pending behavior.
Human understanding:
- What V0 means in observable terms.
Outcome: Pi can reliably start, communicate with, stop, and replace a Bun child process.
Commit outcome:
- Add serializable host-to-child and child-to-host message types.
- Cover
init,ready,run,result,error, andclose.
Proof:
- Round-trip serialization tests.
- Unknown message and version mismatch tests.
Human understanding:
- Why the protocol is a compatibility boundary.
- Which side owns each message.
Commit outcome:
- Spawn
process.execPathas a Bun child with IPC. - Require
init/readybefore use.
Proof:
- Integration test observes the child PID and successful handshake.
- Startup failure produces a bounded, useful error.
Human understanding:
- Difference between the Pi process and runtime process.
- Startup and failure sequence.
Commit outcome:
- Add one
runrequest at a time. - Return a serializable final value or normalized error.
Proof:
- Arithmetic, object, thrown-error, and unserializable-result tests.
Human understanding:
- Where code is evaluated.
- How a run is correlated with its result.
Commit outcome:
- Capture
console.log,console.error, stdout, and stderr as ordered runtime events.
Proof:
- Interleaved output test preserves per-run ordering.
Human understanding:
- Difference between streaming events and the final result.
Commit outcome:
- Abort an asynchronous run.
- Kill a process stuck in a synchronous infinite loop.
- Reject outstanding requests predictably.
Proof:
- Infinite-loop test terminates within a bounded interval.
- The Pi test process remains responsive.
Human understanding:
- Why killing the child is the only reliable synchronous-loop interrupt.
- Which state is lost after a kill.
Commit outcome:
- Reset replaces runtime state.
- Unexpected exit marks the session dead and permits a fresh process.
- Pi session disposal leaves no child behind.
Proof:
- Reset, crash, restart, and orphan-process tests.
Human understanding:
- Runtime state machine: starting, ready, running, closing, dead.
Outcome: separate code calls feel like one persistent TypeScript notebook.
Commit outcome:
- Use Bun's transpiler to strip TypeScript syntax before execution.
- Preserve source identity for useful errors.
Proof:
- Type annotations, interfaces, generics, and syntax-error tests.
Human understanding:
- Transpilation versus type-checking.
- Why V0 executes TypeScript without promising static correctness.
Commit outcome:
- Detect and display the last expression while preserving statement behavior.
Proof:
- Expression, statement-only, object-literal, and thrown-expression tests.
Human understanding:
- How notebook output differs from
console.log.
Commit outcome:
- Wrap asynchronous cells without losing final-expression behavior.
Proof:
- Resolved, rejected, and mixed statement/await tests.
Human understanding:
- Why top-level await requires a wrapper.
Commit outcome:
- Preserve top-level
varand function declarations across runs.
Proof:
- Define in one run and use in the next.
- Reset removes them.
Human understanding:
- Global properties versus lexical bindings.
Commit outcome:
- Transform top-level lexical declarations into durable session bindings.
- Adapt relevant OMP behavior with attribution where useful.
Proof:
- Destructuring, classes, duplicate declarations, and cross-cell use.
Human understanding:
- Why ordinary
evaldoes not provide notebook persistence automatically. - What transformation changes semantically.
Commit outcome:
- Functions retain references to earlier bindings.
- A failed transformation or execution does not silently corrupt established state.
Proof:
- Closure mutation and failed-cell regression tests.
Human understanding:
- Difference between lexical persistence and serializable state.
Commit outcome:
- Rewrite static and dynamic imports to resolve as if issued from the active project.
- Support normal project and npm dependencies available to Bun.
Proof:
- Local module, package, dynamic import, missing module, and cwd tests.
Human understanding:
- Why imports otherwise resolve relative to the runtime implementation.
Outcome: TypeScript can compose Pi tools without returning to the model after each call.
Commit outcome:
- Extend the protocol with correlated
tool-callandtool-replymessages. - Pause the child promise until a reply arrives.
Proof:
- Fake host function returns a value, throws, and runs concurrently.
Human understanding:
- How code pauses without pausing the Pi process.
Commit outcome:
- Register named host functions with schemas and executors.
- Reject unknown tools and invalid arguments before execution.
Proof:
- Registry lookup and validation tests.
Human understanding:
- Why stock Pi metadata is not itself a generic execution handle.
Commit outcome:
- Inject
tool.<name>(args)into the runtime. - Normalize successful and failed results.
Proof:
- One model-like TypeScript program calls a fake tool twice.
Human understanding:
- Proxy syntax versus protocol messages and real host execution.
Commit outcome:
- Adapt Pi's read implementation and result shape.
- Preserve cancellation and output limits.
Proof:
- Read a project file from code and use its contents without displaying them.
Human understanding:
- The first complete guest-code-to-Pi-tool round trip.
Commit outcome:
- Add the remaining read-only repository tools.
- Normalize their outputs into code-friendly structures.
Proof:
- A program finds files, searches them, filters results, and displays a summary.
Human understanding:
- Where context savings occur.
Commit outcome:
- Add trusted mutation and command adapters.
- Mark each nested call clearly in traces.
- Document that V0 does not claim inner-tool prompts can contain unrestricted Bun code.
Proof:
- A temporary workspace test writes, edits, and invokes a harmless command.
Human understanding:
- Why full Bun can bypass the adapter layer.
- Why adapters remain useful for structured results and observability.
Commit outcome:
- Route overlapping tool calls to the correct promises.
- Abort outstanding host calls when the run ends.
Proof:
Promise.allresult ordering, mixed success/failure, and cancellation tests.
Human understanding:
- Correlation IDs and concurrent in-flight state.
Commit outcome:
- Generate concise tool declarations and prompt guidance from the registry.
- Keep model instructions aligned with actual enabled adapters.
Proof:
- Snapshot tests for tool descriptions and declarations.
Human understanding:
- How a typed API reduces tool-selection and argument mistakes.
Outcome: the feature is understandable and safe to operate as a trusted tool in a real Pi session.
Commit outcome:
- Connect the runtime manager to Pi's tool interface.
- Scope one runtime to one Pi session and working directory.
Proof:
- End-to-end Pi extension test executes TypeScript.
Human understanding:
- Complete call path from model tool call to runtime result.
Commit outcome:
- Show incremental console and nested-tool status without flooding model context.
Proof:
- UI/update integration test and transcript inspection.
Human understanding:
- User-visible progress versus model-visible content.
Commit outcome:
- Support text and JSON values separately from console output.
- Define which values enter model context.
Proof:
- Text, object, array, empty, and unserializable display tests.
Human understanding:
- Why deliberate display is the context-control boundary.
Commit outcome:
- Cap model-visible output.
- Store complete oversized output and return a reference.
Proof:
- Byte, line, and large nested-tool-output tests.
Human understanding:
- Why code mode can still overflow context without output discipline.
Commit outcome:
- Report source-oriented TypeScript errors, runtime duration, backend PID, and nested tool trace.
Proof:
- Syntax, runtime, timeout, tool failure, and process-crash snapshots.
Human understanding:
- How to distinguish transform, runtime, transport, and host-tool failures.
Commit outcome:
- Expose
reseton thecodetool. - Tie cleanup to Pi session disposal and extension reload.
Proof:
- State disappears after reset and no child remains after disposal.
Human understanding:
- Runtime lifetime relative to Pi session lifetime.
Outcome: V0 is not only demonstrated; its benefits and limitations are measured and reproducible.
Commit outcome:
- Port or independently reproduce the relevant OMP JavaScript compatibility cases.
- Record attribution and intentional semantic differences.
Proof:
- Compatibility matrix generated from tests.
Human understanding:
- What “OMP-like” means precisely.
Commit outcome:
- Exercise repeated runs, resets, crashes, parallel calls, cancellation races, and cleanup.
Proof:
- No orphan child processes or unresolved promises after the suite.
Human understanding:
- Which failures appear only under repetition or concurrency.
Commit outcome:
- Add representative read/search/filter/aggregate tasks.
- Measure model turns, input tokens, output tokens, wall time, success, and tool calls.
Comparisons:
- Normal Pi tool calling.
- This TypeScript code mode.
pi-code-toolwhere the task is supported by both.- OMP where practical.
Proof:
- Reproducible benchmark command and machine-readable results.
Human understanding:
- Where code mode helps and where it adds overhead.
Commit outcome:
- Install, configuration, trust model, architecture, troubleshooting, and extension-development guides.
Proof:
- A fresh checkout can be installed and demonstrated from the documentation alone.
Human understanding:
- How to use and extend the system without reconstructing its history.
Commit outcome:
- Close or explicitly defer every V0 acceptance fixture.
- Publish the compatibility, benchmark, and known-limitations summaries.
- Tag the trusted runtime as V0-ready; package publication remains a separate explicit action.
Proof:
- Clean install, test, smoke, and representative Pi session.
Human understanding:
- What V0 promises and what it deliberately does not promise.
These milestones preserve the longer competitive direction without burdening the trusted V0.
- Persist serializable state in tool-result details.
- Reconstruct state after restart and conversation branching.
- Replay code with cached tool results so side effects do not repeat.
- Define behavior for closures and non-serializable values.
Outcome: approach pi-code-tool's durable state guarantees.
- Add per-tool
allow,ask, anddenypolicies. - Suspend code at gated tool calls and resume later.
- Support interactive and headless profiles.
- Preserve pending approvals across restart where possible.
Outcome: approvals become useful for sandboxed/remote backends and externally consequential tools.
- Define a transport-neutral runtime interface.
- Run full Bun in one disposable remote VM/container backend.
- Sync repositories and return reviewable patches.
- Add scoped credential injection, network policy, resource limits, and automatic teardown.
Outcome: retain full Bun compatibility without giving generated code direct access to the local machine.
- Evaluate Zapcode against the V0 compatibility suite.
- Add a deny-by-default backend if its supported subset is adequate.
- Advertise backend-specific language capabilities to the model.
- Maintain the same host-tool API where possible.
Outcome: safe local code mode for workflows that do not require npm or full Bun APIs.
- Markdown, tables, and images.
- Nested
completion()andagent()calls. parallel()andpipeline()helpers.- Progress, budgets, and timeout pausing around nested model work.
Outcome: approach OMP's richer orchestration experience.
- Save successful TypeScript as human-readable reusable tools.
- Generate adapters/declarations for cooperating extension and MCP tools.
- Add tool search when the API surface becomes large.
Outcome: exceed a one-off REPL and become a reusable agent programming environment.
V0 is complete only when all of the following are true:
- A user can install the extension and deliberately opt into trusted execution.
- The model can write TypeScript that calls at least seven Pi tools.
- State persists naturally across separate code calls.
- Synchronous infinite loops cannot hang Pi permanently.
- Process crashes and resets recover predictably.
- Large intermediate results remain outside model context unless displayed.
- Output is bounded and full output is recoverable.
- The test suite covers the public behavior and major lifecycle failures.
- The benchmark identifies at least one task class where code mode materially improves turns, tokens, or latency without reducing correctness.
- The documentation allows a human to explain the architecture, trust boundary, and principal tradeoffs.
- Every milestone unit exists as a comprehensible, green, independently reviewable commit.
At the end of each milestone, pause before continuing.
Question: Is the subprocess lifecycle reliable and simple enough to maintain?
Stop or redesign if process cleanup, IPC, or timeout recovery is unreliable.
Question: Does OMP-like notebook fidelity justify the source transformation complexity?
Compare against an explicit state object before committing to additional transformation features.
Question: Does TypeScript programmatic tool calling work reliably with the target models?
Run real tasks before investing in rich UI work.
Question: Does the measured benefit justify maintaining a separate project from pi-code-tool?
If not, publish the findings and stop rather than expanding scope by inertia.
Begin with M0.1 — Scaffold the package only.
Do not begin subprocess, transformation, or tool-bridge work in the same commit. The first commit should establish the project shape, test command, Pi loading path, and first learning note so every later unit follows the intended development discipline.