Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions integrations/isolation/acq-kits/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ not agent behavior. (Behavioral patterns live in `skills/`, `prompts/`, etc.)
| [`zscaler-ca-certificate/`](zscaler-ca-certificate/) | Trust the public Zscaler Root CA in the sandbox (msb: native `--trust-host-cas`; sbx: file-drop + `update-ca-certificates`). |
| [`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). |
| [`opencode/`](opencode/) | Install the OpenCode terminal AI coding agent as a pinned, SHA-256-verified release binary. Agent-harness kit; no ports. |
| [`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. |
Expand Down
17 changes: 17 additions & 0 deletions integrations/isolation/acq-kits/kits.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,23 @@
schemaVersion: "acq-kits-registry/v1"

kits:
opencode:
backends: [sbx, msb]
parity: |
First harness-agent kit in the family (declares the top-level `agent:`
block so acq's built-in agent-kit readiness gate can confirm this kit
genuinely provides the `opencode` agent token before enabling it).
Written entirely in the neutral vocabulary with no backend shortcut or
extras: a single install-phase script (files/home/opencode-install.sh)
fetches a pinned, SHA-256-verified OpenCode release binary (NOT npm, NOT
nixpkgs — see README.md) and installs it to /usr/local/bin at create
time, root, once. Fails soft (agent absent, sandbox stays up) on any
availability failure, but fails CLOSED (refuses to install) on a SHA-256
mismatch — see docs/decisions/fail-closed-on-integrity.md, which is a
deliberate divergence from every other kit in this family's uniform
fail-soft convention. Publishes NO ports (a TUI, not a web UI).
Replaces acq's own unpinned MSB fallback (npm install -g opencode-ai).

usai-provider:
backends: [sbx, msb]
parity: |
Expand Down
125 changes: 125 additions & 0 deletions integrations/isolation/acq-kits/opencode/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# opencode (acq mixin kit)

Installs [anomalyco/opencode](https://github.com/anomalyco/opencode) (MIT) —
the OpenCode terminal AI coding agent — via a **pinned, SHA-256-verified
release binary**, not npm. Replaces acq's own unpinned MSB fallback
(`npm install -g opencode-ai`, resolves to latest) with a deterministic,
version-pinned install.

This is the first **harness-agent kit** in the family: it declares the
top-level `agent:` block (`name: opencode`, `entrypoint: opencode`) so acq's
built-in agent-kit readiness gate can confirm this kit genuinely provides the
`opencode` agent token before enabling it for implicit agent selection. The
sibling quickstart repository implements that gate in `acq.backends/common.sh`
(`acq_validate_agent_builtin_kit_dir`). This kit is the reusable, declarative
replacement for acq's older backend-specific fallback installer.

opencode is a **TUI, not a web UI** (contrast the sibling `paseo` and
`openchamber` kits, which each publish a browser-UI port), so this kit
publishes **no ports**.

## Why a release-binary pin, not npm or nixpkgs

**Not npm** (`npm install -g opencode-ai`): this kit's chosen install method —
fetching the GitHub Release tarball directly — has no npm package metadata and
no native-module postinstall step to go through at all, because it never
touches npm. The `opencode-ai` npm package itself DOES have real package
metadata and its own `postinstall` script (which fetches the actual binary
separately, confirmed against the live npm registry) — this kit sidesteps that
postinstall step entirely rather than it not existing. That matters because
the postinstall step is a real, already-documented bug: GSA-TTS/agentic-coding-quickstart's
`docs/KNOWN_FAILURE_MODES.md` records that `opencode-ai`'s own npm
postinstall step intermittently fails fetching the real binary, which `acq`
already carries a bespoke `ensure_opencode_postinstall`
remediation for. This kit's install method has no postinstall step at all —
the release tarball already contains the real binary.

**Not nixpkgs**: the project's devenv sandbox image
(`integrations/isolation/images/devenv/`) pins nixpkgs at a fixed revision.
At that pinned revision, nixpkgs' own `opencode` package resolves to
`v0.3.112`, built from the pre-rename upstream repository (`sst/opencode`) —
roughly 70 releases behind the real current release this kit pins. Verified
directly (fetched the pinned revision's own `package.nix` from the real
nixpkgs repository) before choosing the release-binary approach instead.
Revisit nixpkgs only if/when the devenv image's pinned revision tracks the
real `anomalyco/opencode` repository at a current version — neither is true
today.

## Install method: GitHub Release asset, direct fetch + verify

Mirrors the goose-server kit's pattern (a single prebuilt binary tarball
needs no toolchain, so there is no create-vs-startup safety tension the way
there is for openchamber's native-module npm dependency — the install runs at
**create time**, as root, once):

1. Resolve the guest architecture (`uname -m`) to OpenCode's own asset-name
convention (`opencode-linux-x64.tar.gz` / `opencode-linux-arm64.tar.gz`).
2. Fetch the pinned release tarball from GitHub Releases.
3. Verify its SHA-256 against the pin **before** extracting.
4. Extract the single `opencode` binary and install it to `/usr/local/bin`.

**Deliberately not** the `opencode.ai/install` curl-pipe-to-shell script: it
resolves "latest" by default (an explicit `--version` flag is required to
pin at all), and its redirect/CDN chain is designed for interactive/CI use,
not a non-interactive, SHA-pinned create-time install. This kit fetches the
same underlying GitHub Release asset directly, with its own pin and
verification.

## Integrity posture: fails CLOSED on a hash mismatch

Every other kit in this family (`openchamber`, `pi-coding-agent`) treats a
verification failure as just another availability failure — warn and
degrade, because the thing being installed is an *optional capability*. This
kit is different: it installs the sandbox's agent harness itself. See
[`docs/decisions/fail-closed-on-integrity.md`](docs/decisions/fail-closed-on-integrity.md)
for the full reasoning. In short:

- **Availability failures** (no pin set, unsupported architecture, network
unreachable, extraction failed): fail soft — warn to stderr, exit 0, agent
absent this boot, sandbox stays up. Same shape as every sibling kit.
- **A SHA-256 mismatch**: fails CLOSED — refuses to extract or install the
mismatched bytes, with an explicit `SECURITY:`-prefixed warning. There is
no unpinned fallback path to degrade to (unlike the quickstart-side gate
this kit satisfies, whose own fallback *is* the unpinned npm path this kit
exists to retire) — falling back to it on a hash mismatch would silently
reintroduce the exact risk this kit closes.

## OpenCode v2

As of this kit's authoring, upstream has tagged `v2.0.0` through `v2.0.21`,
but none of them are published GitHub Releases, npm versions, or
Homebrew-formula versions — see
[`docs/decisions/opencode-v2-not-yet-released.md`](docs/decisions/opencode-v2-not-yet-released.md)
for the full verification. This kit pins the real, currently-released
`1.18.x` series. When v2 does ship, treat the migration as a deliberate,
reviewed change (the upstream diff is already on the order of several
thousand commits), not a routine pin bump.

## Egress allow-list

Deny-by-default. `github.com` (the initial release-asset request) and
`release-assets.githubusercontent.com` (the host GitHub's download redirect
actually resolves to for release assets — verified live against the real
pinned release before authoring this kit). Sibling kits (`openchamber`,
`prime-agent`) instead allow-list `objects.githubusercontent.com` for the
same purpose (release-asset downloads) — GitHub has used more than one
redirect hostname for release assets historically, and this kit's allowlist
covers the host actually observed for this specific release; a future GitHub
infrastructure change to the redirect target would need this allowlist
revisited.

## Version pin

`OPENCODE_VERSION` / `OPENCODE_SHA256_LINUX_X64` / `OPENCODE_SHA256_LINUX_ARM64`
are set by `spec.yaml` to a specific, reviewed release - never `latest`. Bump all
three together, as a deliberate reviewed change, after independently re-deriving
both hashes from the real release assets (download + `shasum -a 256`, not copied
from any third party).

## Backend parity

Written entirely in the neutral hybrid/v1 vocabulary (`caps` / `files` /
`commands` / `agent`), 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.
114 changes: 114 additions & 0 deletions integrations/isolation/acq-kits/opencode/TROUBLESHOOTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# Troubleshooting - opencode kit

Failure modes specific to the opencode kit. They assume you applied the kit to a
sandbox (see [README.md](README.md)).

## `opencode` is not installed

**Symptoms:** `command -v opencode` returns nothing inside the sandbox, or acq's
agent-kit readiness gate reports that the `opencode` agent token is unavailable.

**Cause:** this kit fails soft on availability problems so sandbox creation can
continue. The install script leaves `opencode` absent when it cannot safely fetch,
verify, extract, or install the pinned release binary.

**Fix:** inspect the create-time install output and look for lines prefixed with
`opencode-kit(install):`.

Common causes:

- `OPENCODE_VERSION` or one of the architecture-specific SHA-256 pins was not set
by `spec.yaml`.
- The sandbox architecture is not `x86_64`, `amd64`, `aarch64`, or `arm64`.
- `curl` is missing from the base image.
- The sandbox cannot reach `github.com` or `release-assets.githubusercontent.com`.
- The release tarball could not be extracted or did not contain an executable
`opencode` member.
- `/usr/local/bin` could not be created or written by the root install step.

Recreate the sandbox after fixing the underlying cause; the install step runs at
create time.

## `SECURITY: sha256 mismatch`

**Symptoms:** the install log contains `SECURITY: sha256 mismatch`, and
`opencode` is absent.

**Cause:** the downloaded archive's SHA-256 did not match the pin in `spec.yaml`.
This is a fail-closed integrity refusal, not a transient availability degrade.
The script refuses to extract the mismatched archive and has no unpinned fallback.

**Fix:** do not bypass the check. Confirm the intended OpenCode release, download
both Linux release assets from the official GitHub Release, re-derive their
SHA-256 values locally, update all three pins in `spec.yaml` together, and rerun
`scripts/verify` before recreating the sandbox.

## `SECURITY: archive member 'opencode' is not a regular file`

**Symptoms:** the install log contains `SECURITY: archive member 'opencode' is
not a regular file`, and `opencode` is absent.

**Cause:** the verified archive's `opencode` entry is not an ordinary file. The
script rejects hardlinks, symlinks, device files, and other non-regular tar
members before extraction because installing one could trust or copy something
other than the release binary.

**Fix:** treat this as a release-asset integrity problem. Do not install the
archive manually. Re-check the upstream release asset and update the kit only
after a human review confirms the expected archive layout.

## `SECURITY: extracted 'opencode' member is a symlink`

**Symptoms:** the install log contains `SECURITY: extracted 'opencode' member is
a symlink`, and `opencode` is absent.

**Cause:** the verified archive passed the pre-extraction tar listing check but
extracted to a symlink on this platform. The script keeps this post-extraction
check as a second guard before installing into `/usr/local/bin`.

**Fix:** treat this as a release-asset integrity problem. Do not install the
archive manually. Re-check the upstream release asset and update the kit only
after a human review confirms the expected archive layout.

## The kit works on one host architecture but not another

**Symptoms:** `opencode` installs on an x64 sandbox but not an arm64 sandbox, or
vice versa.

**Cause:** the kit pins separate SHA-256 values for
`opencode-linux-x64.tar.gz` and `opencode-linux-arm64.tar.gz`. A missing or stale
pin for the current guest architecture makes the installer refuse to install an
unverified binary.

**Fix:** update the version and both architecture hashes in `spec.yaml` together.
Then run the kit's verifier, preferably on both x64 and arm64-capable hosts when
available.

## The verifier reports a schema skip

**Symptoms:** `scripts/verify` prints `SKIP python3 with jsonschema+pyyaml not
available`.

**Cause:** the offline schema check needs Python with `jsonschema` and `pyyaml`.
The skip does not mean the kit is invalid; it means the local host is missing the
validator dependencies.

**Fix:** run the repository setup first, or install the validation dependencies in
your development environment, then rerun `scripts/verify`.

## Live sandbox verification is skipped

**Symptoms:** `scripts/verify` prints `RUN_ACQ=1 not set - skipped live acq
sandbox verification`.

**Cause:** live sandbox creation is opt-in because it needs a working acq install
and sandbox-capable host.

**Fix:** run the live check from a host where `acq` is installed and authenticated:

```bash
RUN_ACQ=1 integrations/isolation/acq-kits/opencode/scripts/verify
```

Use `KEEP=1` only when you intentionally want to leave the temporary sandbox in
place for inspection.
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# ADR: Fail closed on integrity, fail soft on availability

## Context

Every other kit in this family that fetches and verifies a pinned artifact
(openchamber's installer, the Nix installer in the devenv image) treats
verification failure as just another availability failure: log a warning,
skip the optional step, keep the sandbox alive. That convention exists because
in every one of those cases, the thing being installed is an *optional
capability* layered onto an already-functional sandbox — OpenChamber is a web
UI for a model the sandbox can still reach without it; a missing CA cert
degrades TLS trust but doesn't remove the agent.

This kit is different: it installs the sandbox's *agent harness itself*. If
this kit's install silently degrades the same way an optional-capability
kit's does, the practical result is a sandbox with no agent at all — the
single least-recoverable failure mode this whole agent-kit migration exists
to avoid, since the entire point of the kit is to replace an unpinned
fallback (`npm install -g opencode-ai`) that resolves to whatever is newest.

## Decision

Split "the download didn't work" from "the download worked but doesn't match
what we expect" into two different failure classes, with different handling:

- **Availability failures** (no pin set, unsupported architecture, network
unreachable, extraction failed, binary not executable after install): warn
loudly to stderr, exit 0. The agent is absent this boot; the sandbox stays
up. Identical in shape to every other kit's fail-soft convention.
- **Integrity failure** (the downloaded archive's SHA-256 does not match the
pin): warn loudly, with an explicit `SECURITY:` prefix distinguishing it
from an availability warning, and refuse to extract or install the
mismatched bytes. **Still exits 0** (a bad download must not crash sandbox
*creation*), but there is no fallback path to degrade to — unlike the
quickstart-side gate this kit satisfies, whose own fallback IS the unpinned
npm path this kit exists to retire. Falling back to it on a hash mismatch
would silently reintroduce the exact risk this kit closes, defeating the
pin's purpose at the one moment it matters.

## Consequences

- A corrupted or tampered download never results in a running, unverified
opencode binary — the kit either runs the exact pinned bytes or runs
nothing.
- The agent being absent after a hash mismatch is a user-visible gap (no
`opencode` on PATH), not a silent compromise. The stderr warning is written
with enough detail (`got X, want Y, at <version>`) to self-diagnose or
report.
- This intentionally diverges from the openchamber/pi-coding-agent kits'
"every failure path looks the same" convention. That divergence is the
point: those kits install something optional; this kit installs the thing
the sandbox exists to run.
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# ADR: OpenCode v2 is tagged but not yet released; this kit pins 1.18.x

## Context

OpenCode's upstream repository (`anomalyco/opencode`) carries git tags
`v2.0.0` through `v2.0.21` as of this kit's authoring. Taken at face value,
pinning to a `v2.x` tag would look like "the current version."

Verified directly before authoring this kit, across every real distribution
channel:

- No `v2.x` tag has a published GitHub Release object (`GET
/repos/anomalyco/opencode/releases/tags/v2.0.21` returns 404, as does every
other `v2.x` tag checked).
- `npm view opencode-ai dist-tags` reports `latest` as a `1.18.x` version; no
`2.x` version has ever been published to npm.
- The GoReleaser-generated Homebrew tap formula
(`anomalyco/homebrew-tap/opencode.rb`) still pins a `1.18.x` version.
- No `2.x` tag exists in the project's GHCR container image tags.
- The `v2.0.0` tag's own commit message is `fix(release): use V2 Docker
artifact paths`, for a previous, failed v2.0.0 release attempt (per that
commit's own PR description: "The first 2.0.0 attempt published some npm
packages before failing...").

So `v2.x` is real, in-progress upstream work, but is not yet consumable by any
install path a kit could pin to. The opencode.ai docs site shows a "New
OpenCode v2 is now available" banner, but every actual install command on
that same page (`npm install -g opencode-ai`, the install script, Homebrew,
Docker) still resolves to the current `1.18.x` release.

## Decision

Pin this kit to the real, currently-released `1.18.x` series (see spec.yaml
for the exact pinned version and hashes). Do not attempt to track or
pre-pin against any `v2.x` tag.

## Consequences

- When v2 does actually ship (a published GitHub Release + npm version +
Homebrew formula bump, not just a tag), bumping this kit's pin is NOT a
routine version increment. `git diff v1.18.34...v2.0.21` on the upstream
repo is already ~3,976 commits / 5,000+ files — large enough that GitHub's
own compare UI refuses to render it. Treat the v2 migration as its own
reviewed change: re-verify the asset naming convention, the CLI invocation
shape, and this kit's wrapper/entrypoint assumptions all still hold, rather
than assuming a pin bump is sufficient.
- This ADR should be revisited (and likely retired) once v2 has a real,
checkable release across at least npm and GitHub Releases.
Loading
Loading