Pi 1.0 and Gondolin: Codemode, Pi Durable, and a Clean 3-Tier Sandbox
How I run Pi 1.0.4 on the host, experiment with Durable sessions, and isolate regular Pi in Gondolin without per-tool wrappers.
Pi 1.0 brought JavaScript tool orchestration, an experimental durable agent harness, and an execution-environment contract for that harness. In my dotfiles, I use Codemode in regular Pi sessions and Gondolin for full-VM isolation. I am exploring Durable to understand how I could integrate persisted agents into future projects.
Note
Tested with Pi 1.0.4, Gondolin 0.12.0, and Node 26.10.0 on macOS ARM64 on 2026-10-07: pi-gondo interactive startup and an authenticated ZAI response. Durable’s behavior was fact-checked against the Pi 1.0.4 source. Native image configurations also exist for Linux x86_64; current runtime verification covers macOS only.
My current working paths are pi on the host and pi-gondo inside a VM. I keep pi-durable as an experimental entrypoint. Each command launches its own runtime and execution environment.
Why I Retired the Tool Wrappers
In my earlier writeup on Gondolin sandboxing, I split containment into two approaches: pi-secure kept Pi on the host and sandboxed selected tools through an extension; pi-secure-vm ran the entire agent inside an Alpine micro-VM.
The tool-only extension at ~/.config/pi/extensions/gondolin-secure/index.ts wrapped bash, read, write, edit, grep, find, and ls individually. It translated paths, forwarded execution results, and maintained its own editing logic. Each intercepted tool was another integration point to keep aligned with Pi.
I removed that extension and retained the full-VM launcher as my active sandboxed path. Durable’s environment abstraction gives me a separate integration point to explore for tool-only isolation.
There was also an authentication boundary to clarify. My AWS authentication setup could mount the host’s ~/.aws directory read-only into the guest. That made AWS login state available, including cached SSO material. Read-only access prevents writes; it does not prevent the guest from reading credentials.
Pi’s Capabilities and How I Use Them
1. Codemode: JavaScript Tool Orchestration
Codemode lets the model write JavaScript that calls Pi’s other tools. Scripts run in a QuickJS sandbox without direct Node, filesystem, network, or timer APIs. They reach those capabilities through registered tools and supported model helpers.
I enable it alongside the default tools in ~/.pi/agent/settings.json:
{
"defaultTools": ["+codemode"]
}
A script can batch independent calls and choose which results to return:
const results = await Promise.allSettled([
tools.read({ path: "package.json" }),
tools.bash({ command: "git status --short" }),
]);
for (const result of results) {
if (result.status === "fulfilled") text(result.value);
else text(String(result.reason));
}
Only the script’s emitted output reaches the model. That lets me filter large tool results before they consume conversation context.
The called tools retain their existing behavior: edit performs its usual text replacements, and source-file validation remains a separate check. QuickJS isolates the orchestration script. Invoked tools use their configured execution path, so a host bash tool still runs on the host.
2. Pi Durable: Persisted Sessions and Subagents
@earendil-works/pi-durable is an experimental agent harness. It stores conversation entries, documents, and task state so interrupted work can be recovered when the storage is reopened. It supports several storage backends; the coding-agent example I am exploring uses SQLite.
I am using it to learn the persistence and subagent model for possible future project integrations. Regular Pi remains my working agent.
The Durable coding-agent example runs the harness, model runtime, SQLite storage, and TUI in one process. My pi-durable shell function targets its main.ts, with provider secrets supplied through my Proton Pass wrapper.
Its Subagent extension creates a child conversation owned by the tool call’s task, waits for its answer, and returns that answer to the parent. The implementation reuses an existing child and submission after a replay. Arbitrary external side effects are not automatically made replay-safe.
Pi also has a distinct experimental client/server architecture. Session workers own Durable harnesses and expose services such as AgentController through Chord RPC. That source-only server/client path is different from the single-process TUI my pi-durable wrapper launches.
3. Execution Environments: An Integration Boundary
Durable’s ExecutionEnv combines FileSystem and Shell capabilities. Its package entrypoint is @earendil-works/pi-durable/env.
The methods live directly on the environment: readTextFile, writeFile, exec, and the other filesystem operations. They receive a cancellation context and return structured results. Durable’s built-in coding tools use this environment instead of requiring a wrapper around every individual tool.
This contract governs environment-backed tool execution. Extension code that accesses host APIs directly remains outside that boundary.
My current Durable setup creates a local NodeExecutionEnv for each working directory. Its tools therefore run on the host.
I have a GondolinExecutionEnv prototype that maps filesystem operations and shell execution into a VM. It needs alignment with the current contract and environment conformance checks before integration.
For future projects, I am interested in keeping model access and conversation storage on the host while environment-backed tools run in a VM. My current Durable entrypoint uses the local environment; this host/VM split is an integration I am exploring.
4. Regular Pi Inside Gondolin
My active sandboxed command is pi-gondo. The gondolin-run.ts launcher boots a VM and launches regular Pi inside it, including its interactive TUI.
The launcher mounts the working directory at /workspace, denies access to configured secret-file patterns, and redirects paths such as node_modules into temporary VM storage. Other workspace writes go back to the host and persist after the VM closes.
The default configuration allows HTTP access to three hosts:
api.z.aifor the configured model provider.registry.npmjs.orgfor npm packages.github.comfor Git package downloads.
Project configurations and development images can allow additional hosts. For the configured ZAI secret, the host resolves the real key through Proton Pass and Gondolin substitutes it into requests to the permitted provider host. The guest receives a placeholder rather than that real key.
Pi settings are copied from the dotfiles source into a writable, ephemeral guest directory. I also seed the entire installed Git package cache from ~/.pi/agent/git. This includes public packages such as Ponytail and my private frangonf/pi-extensions package, so the minimal image does not need to authenticate those clones at startup.
The copied packages remain Git checkouts. Updating a host checkout affects the next VM’s seed; changes to the guest copy do not sync back. Missing Git packages still need to be fetched, and a missing private package still needs authentication. npm packages install inside the VM normally.
The host’s Pi auth.json stays outside the seed copy. Existing GitHub and GitLab configuration directories and the Uruky session cache are mounted read-only. The guest can read those authentication files, so they are part of the sandbox’s explicit trust boundary.
5. AWS Credentials Without Mounting the SSO Cache
AWS authentication is optional and is disabled in the default configuration. When I need it inside a VM, I authenticate on the host and reuse or create a project-scoped IAM role once. I configure the project’s aws.roleArn so the launcher assumes that role for a short session, typically one hour. The role’s permissions are limited to the actions and resources the project needs. The role persists; its session credentials expire. I restart the VM to obtain fresh credentials.
The launcher has two credential-resolution paths:
- With a role ARN, it calls
aws sts assume-roleon the host and receives temporary role credentials. - Without a role ARN, it calls
aws configure export-credentialsto resolve credentials from the host’s AWS configuration.
For the export path, I use a profile backed by SSO or another temporary credential provider. If an existing profile already provides appropriately scoped temporary credentials, I can use that path without introducing another role. Exporting credentials alone does not guarantee that they are temporary.
The launcher injects the resolved access key, secret key, and optional session token into the guest environment. These are real AWS credentials. SigV4 derives a signing key from the secret access key and uses it to sign the request. Signing with a placeholder produces the wrong signature; substituting the real credentials afterward cannot repair it. The guest therefore signs with the actual credentials, while the ZAI key can remain a proxy placeholder.
I leave the deprecated mountHostAwsConfig option disabled and launch from the project directory. Launching from my home directory would expose files such as ~/.aws through the workspace mount unless those paths were explicitly shadowed. The injected AWS credentials are a startup snapshot; the launcher does not refresh them during the session.
The Three Launch Modes
The “tiers” name three separate launch modes. The table separates Pi features I use for work from the Durable runtime I am exploring:
| Command | Agent and UI | Tool execution | Purpose |
|---|---|---|---|
pi | Regular Pi on the host | Host | Daily interactive work with Codemode and local customizations |
pi-durable | Experimental Durable TUI on the host | Host NodeExecutionEnv | Learning persistence and subagents for future integrations |
pi-gondo | Regular Pi inside a Gondolin VM | Guest, with configured host mounts | Full-VM isolation with explicit filesystem and network policy |
The shell wrappers keep those entrypoints separate. pi-secure and pi-secure-vm are compatibility aliases for pi-gondo; neither invokes the retired tool-only extension.
For the VM’s configured provider, I can launch with:
pi-gondo --provider zai --model glm-5.3
Results
- Retired per-tool interception: the removal commit deleted 621 lines of extension code, plus its manifest and lockfile: 818 lines in total.
- Host/guest version alignment: the minimal image pins Pi 1.0.4, matching the host version used here. Installed extensions can depend on newer Pi APIs, so the image pin needs to move with the host.
- Verified VM startup: the current image reaches the interactive TUI and completes an authenticated ZAI request with the configured extensions loaded.
- Explicit boundaries: Durable’s local environment, the unconnected Gondolin adapter, and the full-VM launcher have separate roles. Credential mounts and AWS injection remain visible parts of the sandbox policy.
I rebuild the minimal image and run the real-VM smoke check with:
mise run gondolin:image
python3 "$HOME/.config/gondolin/launcher/test_pi_boot.py"
The smoke check requires the installed image and configured secrets. It makes a real ZAI request. VM boot time alone is not Pi’s cold-start time: the ephemeral guest may also need to install npm packages before the agent is ready.
References
- Pi repository. Source and package ecosystem.
- Codemode documentation. QuickJS scripts, tool calls, and output filtering.
- Pi settings reference. Enabling Codemode with
defaultTools. - Pi Durable. Storage, tasks, execution environments, and recovery.
- Experimental Durable coding agent. The single-process TUI my wrapper targets.
- Experimental client/server services. Session workers and Chord RPC.
- QuickJS. The JavaScript engine used by Codemode’s script sandbox.
- Chord. State and service infrastructure used by the experimental client/server runtime.
- Gondolin documentation. Micro-VMs, filesystem providers, and network mediation.
- AWS STS AssumeRole. Temporary role credentials.
- AWS CLI export-credentials. Credential resolution and export.
- AWS SigV4 signing. Signing-key derivation and request signatures.
- Process-isolated Proton Pass injection. The host-side secret wrapper.
- Gondolin Sandboxing: Two Tiers. The earlier tool-only and full-VM approaches.
- AWS Auth in a Gondolin VM. SigV4 signing and the original host-cache mount.
This post was written with AI assistance.