Skip to content
Merged
2 changes: 2 additions & 0 deletions integrations/isolation/acq-kits/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ not agent behavior. (Behavioral patterns live in `skills/`, `prompts/`, etc.)
| [`git-ssh-sign/`](git-ssh-sign/) | Sign git commits and tags with the SSH key forwarded from the host agent (vendored from sbx-kits-contrib). |
| [`openchamber/`](openchamber/) | Run OpenChamber, a browser UI for OpenCode, inside the sandbox alongside the terminal TUI. Opt-in (see its parity note). |
| [`paseo/`](paseo/) | Self-host the [Paseo](https://github.com/getpaseo/paseo) daemon + browser web UI (for coding agents) inside the sandbox on one port. Opt-in; agent-generic wrapper, no shared TUI session. |
| [`prime-agent/`](prime-agent/) | Install [PrimeIntellect-ai/prime-agent](https://github.com/PrimeIntellect-ai/prime-agent), a terminal AI coding assistant with an embedded IPython tool. Opt-in; TUI, no ports. |
| [`pi-coding-agent/`](pi-coding-agent/) | Install [earendil-works/pi](https://github.com/earendil-works/pi) (`@earendil-works/pi-coding-agent`), a plain terminal AI coding agent (read/write/edit/bash tools only). Opt-in; TUI, no ports. |

Each kit is self-contained: a `spec.yaml` (`hybrid/v1`), any `files/` payload, a
`scripts/verify` host-side check, a `README.md` (with a **backend parity** note),
Expand Down
31 changes: 26 additions & 5 deletions integrations/isolation/acq-kits/kits.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,29 @@ kits:
(files/home/prime-agent-install-models-json.sh) and bootstrap the IPython
kernel (files/home/prime-agent-bootstrap-kernel.sh). Publishes NO ports
(prime-agent is a TUI, not a web UI) — unlike openchamber/paseo. SKELETON
only (#369): the three scripts are placeholders; the real install/CA-bundle/
kernel logic lands in #370-#373, and the model catalog
(files/home/prime-agent-config/models.json) is generated by the USAi catalog
emitter (#362). Model-provider egress is the usai-provider kit's job, not
this one.
only: the three scripts are placeholders; the real install/CA-bundle/
kernel logic and the generated model catalog
(files/home/prime-agent-config/models.json) land in follow-up work tracked
in the patterns repo's prime-agent epic. Model-provider egress is the
usai-provider kit's job, not this one.

pi-coding-agent:
backends: [sbx, msb]
parity: |
Opt-in mixin that installs earendil-works/pi (MIT), npm package
@earendil-works/pi-coding-agent — a plain terminal AI coding agent that
grants the model only read/write/edit/bash tools by default (no built-in
MCP, sub-agents, permission popups, or plan mode). Written entirely in the
neutral vocabulary with no backend shortcut or extras: a single
startup-phase script (files/home/pi-coding-agent-install.sh) installs the
CLI via the standard npm registry path (npm install -g --ignore-scripts,
pinned version, unprivileged per-user npm prefix, no pi.dev egress).
Publishes NO ports (a TUI, not a web UI) — like prime-agent, unlike
openchamber/paseo. No install phase — unlike prime-agent's still-in-flight
skeleton, this kit is startup-only throughout, matching openchamber's
settled pattern; shipped ahead of prime-agent to validate that pattern on
a materially simpler target with no native-module or Python-kernel
bootstrap step. Model-provider config (USAi models.json) is deliberately
deferred — no emitter exists yet to populate it — and will land in a
follow-up alongside a real pi USAi-catalog emitter. Model-provider egress
is the usai-provider kit's job, not this one.
131 changes: 131 additions & 0 deletions integrations/isolation/acq-kits/pi-coding-agent/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# pi-coding-agent (acq mixin kit)

Installs [earendil-works/pi](https://github.com/earendil-works/pi) (MIT),
npm package [`@earendil-works/pi-coding-agent`](https://www.npmjs.com/package/@earendil-works/pi-coding-agent)
— a plain terminal AI coding agent that grants the model only
`read`/`write`/`edit`/`bash` tools by default. Opt-in mixin; composes with the
`usai-provider` kit and the other GSA kits.

pi is a **TUI, not a web UI**, so this kit publishes **no ports** (contrast
the sibling `paseo` and `openchamber` kits, which each publish a browser UI
port), and — unlike the sibling `prime-agent` kit — needs **no embedded
Python/IPython kernel**, so this is the simplest kit in the family.

## Sequencing note

This kit ships **ahead of** the in-flight `prime-agent` kit
(GSA-TTS/agentic-coding-patterns#368) by design: a completed consensus vote
picked pi specifically to validate the unprivileged/agent-owned/startup-only
install pattern on a materially simpler target before returning to
prime-agent's heavier kernel-bootstrap complexity. prime-agent's own epic
remains open, not abandoned.

## Status

`spec.yaml` and the install script are complete for **CLI install only**.
Model-provider configuration (a USAi `models.json` for pi) is **deliberately
deferred**, not merely out of scope: an earlier draft shipped a hand-written
placeholder catalog with an empty `models` array and a script to install it,
and review found that pairing provides zero functional value with no USAi
catalog **pi emitter** yet to populate it (contrast prime-agent's own
placeholder, which ships a real, populated model list generated by an
existing emitter). That step was cut rather than shipped as functionless
scaffolding, and will land together with a real `pi.mjs` emitter in a
follow-up.

Until then, authenticate `pi` manually inside the sandbox — `/login`, an
environment variable, or `~/.pi/agent/auth.json` per
[pi's own provider docs](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/providers.md).

## What it does

- **Startup phase only** (every start, agent user, uid `1000`) — no install
phase. `pi-coding-agent-install.sh` installs the `pi` CLI via
`npm install -g --ignore-scripts`, pinned to a specific reviewed version,
into an unprivileged per-user npm prefix (`$HOME/.local` — see
[`docs/decisions/local-prefix-not-npm-global.md`](docs/decisions/local-prefix-not-npm-global.md)
for why not the `$HOME/.npm-global` convention the sibling kits use).
Idempotent: a no-op if `pi` is already on `PATH`. Rebuilds the CA trust
bundle and the `pi` wrapper (see
[`docs/decisions/ca-bundle-wrapper-not-env-var.md`](docs/decisions/ca-bundle-wrapper-not-env-var.md))
on **every** boot, not just a fresh install.

There is no supervisor loop and nothing running in the background — `pi` is a
TUI the agent runs directly; this kit does not manage a shared session the
way the `openchamber`/`paseo` web-UI kits do.

## Install method: npm registry, not `pi.dev/install.sh`

pi.dev publishes a curl-pipe-to-shell installer, but this kit does **not**
fetch it (a mutable live script with an interactive Node/npm preflight path,
and `pi.dev` egress this kit's allow-list deliberately omits — see the
install script's own header comment for the full rationale). The short
version: `npm install -g @earendil-works/pi-coding-agent` needs only
`registry.npmjs.org`.

**Open question, not yet verified:** does the installed `pi` binary itself
phone home to `pi.dev` at runtime (update checks, telemetry)? If a future
live trace of a running `pi` session finds that it does, add the host then
with a live-tested justification — it is not allow-listed speculatively now.

## Egress allow-list

Deny-by-default. The allow-list in `spec.yaml` is pi's own install host only:

- `registry.npmjs.org` — npm package metadata and tarballs (no wildcard
subdomain needed)

Deliberately **absent** (see "Install method" above for why): `pi.dev`,
`github.com` / `*.githubusercontent.com`.

Model-provider egress (`api.gsa.usai.gov`) is **not** here — that is the
`usai-provider` kit's job.

## Supply-chain notes

- **Version pin**: `PI_CODING_AGENT_VERSION` defaults to a specific, reviewed
npm release rather than `latest` — this kit's behavior must not silently
drift to whatever npm resolves as newest on every sandbox boot. Bump the
pin deliberately, as its own reviewed change.
- **Node version is checked explicitly**: pi requires Node.js >=22.19.0 (its
own published npm registry metadata's `engines.node`). npm's own
`engine-strict` defaults to `false`, so a plain `npm install` would
otherwise proceed silently on an older Node and only fail later, opaquely,
the first time `pi` itself runs. This kit checks the version explicitly
before installing and fails clearly if it's too old — mirroring pi's own
installer's preflight check.
- **Registry fetch is bounded**: `npm install` runs with `--fetch-timeout` and
`--fetch-retries=0` — verified live that against an unreachable or
silently-dropping registry, npm's default retry/backoff behavior can hang
well past a minute, which would delay sandbox startup. Retries are set to 0
deliberately: this is a startup-phase script that runs on every boot, not a
one-shot manual install, so the *next* boot already retries naturally.
- **`--ignore-scripts` is not a complete mitigation.** It blocks npm
lifecycle hooks at install time, not code that runs when `pi` is later
**invoked** (see the install script's own comment on this exact flag) —
the ephemeral, single-tenant sandbox remains the real containment
boundary.
- **CA-bundle handling is load-bearing, not defensive** — see
[`docs/decisions/ca-bundle-wrapper-not-env-var.md`](docs/decisions/ca-bundle-wrapper-not-env-var.md)
for the full design and why a runtime wrapper, not an install-time
`export`, is required for the bundle to actually reach `pi`'s own later
invocations.

## Naming

The kit is named `pi-coding-agent` — matching the npm package name — rather
than the bare upstream shorthand `pi`. A bare `pi` kit name would collide
conceptually with the ubiquitous mathematical constant/variable name in every
log line, script, and error message this kit produces, a real ambiguity risk
unique to this product's name that none of the sibling kits'
(`openchamber`/`paseo`/`prime-agent`/`zscaler-ca-certificate`) share. Every
file, script, and log line in this kit says `pi-coding-agent`, never bare
`pi`, for the same reason.

## Backend parity

Written entirely in the neutral hybrid/v1 vocabulary (caps / files /
commands), with no `backend_shortcuts` or `backend_extras`. acq's kit
translation feeds the same spec to whatever backend it has active (sbx, msb).
No published port and no supervisor loop, so no per-backend detached/port
primitives are needed.
155 changes: 155 additions & 0 deletions integrations/isolation/acq-kits/pi-coding-agent/TROUBLESHOOTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
# Troubleshooting — pi-coding-agent kit

Failure modes specific to the pi-coding-agent kit, which installs
[earendil-works/pi](https://github.com/earendil-works/pi)
(`@earendil-works/pi-coding-agent`) via the npm registry at every sandbox
start. Most diagnosis is done with `acq exec <sandbox> -- sh -c '…'`. Unlike
the sibling `openchamber`/`paseo` kits, a bare `acq exec` here does **not**
need any `PATH=` prepend to find `pi` — the install prefix is `$HOME/.local`,
already first on the guest's default `PATH` (see
[`docs/decisions/local-prefix-not-npm-global.md`](docs/decisions/local-prefix-not-npm-global.md)).

## Node.js and/or npm not found

**Symptoms:** the startup log shows
`pi-coding-agent: node and/or npm not found on PATH; cannot install pi this
boot (installing Node is out of scope for this kit — see spec.yaml)`, and `pi`
never appears.

**Cause.** This kit deliberately does not install Node — it assumes the base
image already carries it (the same assumption `openchamber` makes for its own
base image). A customized or minimal base image may not.

**Fix:** use the `opencode` base template (the default, which ships Node), or
add a Node install step ahead of this kit in your kit composition.

## Node.js too old

**Symptoms:** the startup log shows
`pi-coding-agent: pi requires Node.js >=22.19.0, found vX.Y.Z; cannot install
pi this boot (installing/upgrading Node is out of scope for this kit)`.

**Cause.** pi's own published npm registry metadata (`engines.node`) requires
Node `>=22.19.0`. npm's `engine-strict` defaults to `false`, so a plain
`npm install` would otherwise proceed silently on an older Node and only fail
later, opaquely, the first time `pi` itself runs — this kit checks explicitly
up front instead, mirroring pi's own installer's preflight check.

**Fix:** upgrade the base image's Node to `>=22.19.0`, or pair this kit with a
Node-upgrade step. Confirm the guest's version:

```bash
acq exec <sandbox> -- sh -c 'node --version'
```

## Malformed `PI_CODING_AGENT_VERSION` — install refused

**Symptoms:** the startup log shows
`pi-coding-agent: PI_CODING_AGENT_VERSION='<value>' doesn't look like a
version; refusing to install` (or `... contains characters outside
[0-9A-Za-z.-]; refusing to install`), and `pi` is never installed.

**Cause.** The kit validates the version pin's shape before it ever reaches
the npm package-spec string (`@earendil-works/pi-coding-agent@<version>`).
This fails **closed**: an unexpected value (typo, stray whitespace, an
accidentally-injected flag-looking token) is refused rather than silently
passed through. Note that this is a defensive **format** check, not a
security boundary on its own — the value is always passed as a single quoted
argv word, never re-parsed by a shell, so it was never shell-injectable; the
check exists so a malformed pin fails loudly and immediately instead of
producing a confusing downstream npm error.

**Fix:** set `PI_CODING_AGENT_VERSION` to either `latest` or a plain
`MAJOR.MINOR.PATCH`-shaped string (matching `[0-9A-Za-z.-]` only) in the
kit's `environment:`/spec configuration, then recreate the sandbox (or
restart it so the startup step re-runs).

## npm install failure or timeout

**Symptoms:** the startup log shows
`pi-coding-agent: npm install exited <rc> (prefix=...); pi unavailable this
boot. See <log path>`, and `pi` is not on `PATH`.

**Cause.** Most commonly: egress to `registry.npmjs.org` is blocked (this
kit's only allow-listed host), a proxy/TLS issue (see the next entry), or a
slow/silently-dropping connection. The install runs with
`--fetch-timeout=30000 --fetch-retries=0` deliberately — this is a
**startup-phase** script that runs on every boot, not a one-shot manual
install, so retrying against a systemic failure just delays the sandbox for
no benefit; the *next* boot retries naturally.

**Fix:** check the install log and egress:

```bash
acq exec <sandbox> -- sh -c 'cat ~/.local/state/pi-coding-agent/install.log 2>/dev/null | tail -n 40'
acq exec <sandbox> -- sh -c 'echo "${HTTPS_PROXY:-unset}"'
```

If `registry.npmjs.org` is not reachable under org network policy, confirm it
is on the sandbox's effective allow-list (`sbx policy log <sandbox>` /
`acq policy log <sandbox>` under org governance may show it superseded).

## CA-decode failure (malformed `PROXY_CA_CERT_B64`)

**Symptoms:** the startup log shows
`pi-coding-agent: PROXY_CA_CERT_B64 failed to base64-decode; proxy CA NOT
added to the bundle (see <err path>)`, typically followed by an npm/TLS
failure like `unable to get local issuer certificate` or
`SELF_SIGNED_CERT_IN_CHAIN` on an inspected network.

**Cause.** `PROXY_CA_CERT_B64` (base64-encoded PEM, supplied by whatever
injects a proxy CA into the sandbox — e.g. the `zscaler-ca-certificate` kit)
failed to decode. Unlike the sibling `openchamber` kit's equivalent block
(which swallows this same decode failure via `2>/dev/null`), this kit's
install script surfaces it explicitly, on the theory that a silently
incomplete CA bundle produces a much more confusing downstream TLS error than
telling you up front that the base64 itself was bad.

**Fix:** confirm the value is actually valid base64-encoded PEM from the
source that's supposed to provide it (commonly the `zscaler-ca-certificate`
kit — pair it if you're on an inspected network), then recreate/restart the
sandbox:

```bash
acq exec <sandbox> -- sh -c 'echo "PROXY_CA_CERT_B64 len=${#PROXY_CA_CERT_B64}"'
acq exec <sandbox> -- sh -c 'echo "$PROXY_CA_CERT_B64" | base64 -d | head -c 40' # should start with -----BEGIN CERTIFICATE-----
```

## pi installs but making an LLM-provider call fails TLS

**Symptoms:** `pi` runs, but any call it makes to a model provider on an
inspected network fails with a TLS trust error (`unable to get local issuer
certificate`, `SELF_SIGNED_CERT_IN_CHAIN`).

**Cause.** `NODE_EXTRA_CA_CERTS` only **appends** to Node's built-in trust
roots — it needs the sandbox's proxy CA. Per the wrapper design in
`docs/decisions/`, the CA bundle is **rebuilt once per boot** by the startup
script, and the wrapper at `$HOME/.local/bin/pi` simply `exec`s the real `pi`
binary with that boot's already-current bundle path — it does not rebuild
anything itself at invocation. A plain `export` inside the install script's
own process would not persist to a separately-launched `pi` process later,
which is exactly why the wrapper exists.

**Fix:** confirm the wrapper still references `NODE_EXTRA_CA_CERTS` and
`exec`s the real binary (this is the normal, expected shape — not a sign of
breakage):

```bash
acq exec <sandbox> -- sh -c 'cat ~/.local/bin/pi' # should show a small script that sets NODE_EXTRA_CA_CERTS and execs the real pi binary
```

If the wrapper is missing `NODE_EXTRA_CA_CERTS` entirely (not just a static
one-time export baked in at some earlier boot, but absent altogether), it has
regressed; see `docs/decisions/` for the intended design and file an issue.

## No model provider configured

**Symptoms:** `pi` runs but has no LLM provider to talk to.

**Cause.** This kit deliberately defers model-provider configuration (no
USAi `models.json` catalog exists for pi yet — see the README's "Status"
section).

**Fix:** authenticate `pi` manually inside the sandbox — `/login`, a provider
environment variable, or `~/.pi/agent/auth.json` — per
[pi's own provider docs](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/providers.md).
Loading
Loading