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
16 changes: 10 additions & 6 deletions integrations/isolation/acq-kits/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,18 @@ The [`kits.yaml`](kits.yaml) registry is the human-readable parity summary

## Templates

Copy-and-rename starting points under `examples/`. They are **not kits `acq`
applies**: `hybrid/v1` has no parameters, so a template carries one live,
harmless value per extension point for you to replace, plus a `scripts/verify`
that asserts how the kit composes with the global layer. CI validates them like
kits (schema, `files[]` sources, env names, README) but they are exempt from —
and must stay out of — the `kits.yaml` registry. See
Copy-and-rename starting points under [`examples/`](examples/). They are **not
kits `acq` applies**: `hybrid/v1` has no parameters, so a template carries one
live, harmless value per extension point for you to replace, plus a
`scripts/verify` that asserts how the kit composes with the global layer. CI
validates them like kits (schema, `files[]` sources, env names, README) but
they are exempt from — and must stay out of — the `kits.yaml` registry. See
[`../docs/decisions/0005-kit-templates.md`](../docs/decisions/0005-kit-templates.md).

| Template | Purpose |
|----------|---------|
| [`examples/team-kit/`](examples/team-kit/) | A team's scope-layer mixin: conventions and agent settings through the OpenCode config tier, applied via `ACQ_EXTRA_KITS` after the global kits. Pattern and composition rules: [`../docs/scope-layers.md`](../docs/scope-layers.md). |

## What is a hybrid/v1 kit?

A kit is a directory with a `spec.yaml` (`schemaVersion: "hybrid/v1"`). A
Expand Down
158 changes: 158 additions & 0 deletions integrations/isolation/acq-kits/examples/team-kit/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
# team-kit (template for a team's acq mixin kit, `hybrid/v1`)

A **template, not a deployable kit**: the starting point for a team's own
scope-layer mixin, the layer between the global kits `acq` applies for
everyone and each repo's own agent config. `acq` never applies this directory;
you copy it into a repo your team owns, rename it, and grow it. The pattern,
the per-field composition rules, the authoring gotchas, the backend
differences, and the parser rules to check against your `acq` are in
`integrations/isolation/docs/scope-layers.md` in
[agentic-coding-patterns](https://github.com/GSA-TTS/agentic-coding-patterns)
(the pattern doc); the placement decision is ADR 0005 beside it, in
`docs/decisions/`.

> **Tested on msb only.** This template and its `scripts/verify` have not been
> tested on sbx and may not work there. Run `scripts/verify` on sbx before
> relying on it.

```text
global zscaler-ca-certificate · usai-provider · agentic-coding-playbook · git-ssh-sign (acq, everyone)
agent the agent's own kit (opencode for `acq run opencode`) (acq, per agent)
team your copy of this kit (ACQ_EXTRA_KITS)
personal a teammate's own kit (ACQ_EXTRA_KITS, later)
repo the repo's AGENTS.md / .opencode/ (read by the agent)
```

## Use

1. Copy this directory into your team's repo and **rename both the directory
and `name:` in `spec.yaml`** (kebab-case; keep them equal). Update
`displayName:` and `description:`.
2. Replace every value marked `REPLACE` in `spec.yaml`, and rewrite
`files/home/team-config/team-conventions.md` for your team. Each extension
point ships one live, harmless value so the schema and `scripts/verify`
exercise it; leave a value in place only if you actually want it.
3. Apply it after the global kits. Each teammate sources the kit's `env.sh`
from their shell rc, then starts sandboxes as usual:

```bash
# shell rc (bash or zsh)
export TEAM_KIT=/path/to/your-team-repo/acq-kits/your-team
. "$TEAM_KIT/env.sh"
```

```bash
acq run opencode /path/to/project
```

`env.sh` puts the team kit first in `ACQ_EXTRA_KITS`, and sets `ACQ_IMAGE`
if you enable it. It is host-side: keep it out of `files[]`. How it works,
and how it interacts with `acq configure`: "Host-side defaults" in the
pattern doc.

4. Validate on every change, and run the live check when the spec or a payload
changes:

```bash
acq kit validate /path/to/your-team-kit
/path/to/your-team-kit/scripts/verify
```

Teammates stack their own kits *after* yours by listing them in
`PERSONAL_KITS` before sourcing `env.sh` (a catalog kit's `git+https` ref
goes here too):

```bash
PERSONAL_KITS="/path/to/personal-kit"
```

Later kits win wherever a field is last-wins (`environment`, `files[]` by
path). Say in this README which of your settings a personal kit may override.

## What it carries (as shipped)

| Extension point | Live value | Replace with |
|-----------------|------------|--------------|
| `caps.network.allow` | `example.org` (an IANA-reserved example domain the global layer does not allow, so `scripts/verify` can observe it) | your team's VCS, package-mirror, and MCP hosts |
| `files[]` | `team-config/opencode.jsonc`, `team-config/team-conventions.md`, a non-empty `.agents/skills/.gitkeep` placeholder (msb rejects empty files) | your conventions, settings, and skills (one `files[]` record per file) |
| `commands[]` | `git config --global push.autoSetupRemote true` at startup | your idempotent per-sandbox setup steps |
| `environment` | `OPENCODE_CONFIG` → the team `opencode.jsonc` | more non-secret settings; never a credential |

The OpenCode wiring gives the settings tier global → team → repo: the global
`usai-provider` kit merges into OpenCode's global config path and leaves
`OPENCODE_CONFIG` free for this layer. The rest of the kit is agent-agnostic.
The conventions file reaches the agent through OpenCode's `instructions`
field, which only OpenCode 1.x loads; `scripts/verify` fails when sandbox
shells run 2.x (see "Check against your acq" in the pattern doc).

## What it does not carry, on purpose

- **Tools and toolchains.** Bake them into the image and point `ACQ_IMAGE` at
it; the published devenv image in this repo is the precedent.
- **Secrets.** `acq secret set -g <service> --host <host> --env <VAR>` on the
host; the sandbox only ever sees a placeholder.
- **Whole shared files** (`~/.bashrc`, `~/.gitconfig`) or **binary files**.
- **Anything the global layer already owns.**

## Writing a skill

A skill is a directory under `files/home/.agents/skills/<name>/` holding a
`SKILL.md` with two frontmatter fields, `name` and `description`, followed by
the procedure. The agent picks a skill by matching its description to the
request.

- **Pick a name the global playbook kit does not use.** The playbook symlinks
its own skills into the same directory at every start, so a same-named team
skill collides with one of them (use `team-code-review`, not
`code-review`).
- **Put the trigger phrases in the description**, worded the way a teammate
would ask, plus one sentence on what the skill produces.
- **Write only the delta:** team conventions, sandbox constraints, the output
contract. Generic advice on how to do the task is padding.
- **Say what the agent must never do on its own** (post, approve, push). The
team `opencode.jsonc` can gate the commands; the skill is where the agent
learns the rule.
- **Keep it small.** Every session loads the description, so keep it under
1024 characters; keep the body under 500 lines and move reference material
into sibling files the body points to.
- **Register every file** in the skill directory as a `files[]` record.
- **Check that the description wins.** Several playbook skills cover nearby
tasks. In a sandbox, ask for the task three ways, one without naming the
skill, and confirm yours loads. Repeat when the playbook pin changes.

## Verifying

```bash
./scripts/verify # needs acq on PATH (or ACQ_DIR) and a sandbox-capable host
KEEP=1 ./scripts/verify # keep the sandbox and work dir for inspection
```

It validates the spec, then creates a throwaway sandbox through `acq`, which
applies the pinned built-in bundle plus, via `ACQ_EXTRA_KITS`, a generated
**lower fixture kit** and then this kit. It asserts that the global layer is
intact, that every live value above landed, and that the composition rules from
the pattern doc hold against the fixture: `environment` last-wins, `files[]`
last-wins by path, `commands[]` append in order, `caps.network.allow` union.
Run it on each backend your team uses; select the backend with `ACQ_BACKEND`.
The values at the top of `scripts/verify` (`TEAM_CONFIG`, `TEAM_CONVENTIONS`,
`TEAM_EGRESS_HOST`) mirror `spec.yaml`: update them whenever you change the
matching path or host. If you replace the git startup step, also update the
startup poll and the `commands[]` check, which both read its git key. Extend its clearly marked **TEAM-SPECIFIC** section as your kit
grows, one assertion per mechanism you add.

## Backend parity

The kit uses no backend-specific features: it is declarative only (`caps`,
`files[]`, `commands[]`, `environment`), with no `backend_shortcuts` or
`backend_extras`, and `scripts/verify` uses only `acq` verbs. The backends
still behave differently (who runs a command with no `user:`, `agentContext`,
how kits re-apply, unlisted files, SSH egress, secret rotation); "Backend
differences" in the pattern doc is the list. Keep this section in your copy
and add anything backend-specific your kit introduces.

## Troubleshooting

See [`TROUBLESHOOTING.md`](TROUBLESHOOTING.md). Record your team's non-obvious
choices as ADRs next to the kit, in [`docs/decisions/`](docs/decisions/). The
sample there records the template's own choice of `OPENCODE_CONFIG`; keep it,
rewrite it, or replace it with your first real decision.
158 changes: 158 additions & 0 deletions integrations/isolation/acq-kits/examples/team-kit/TROUBLESHOOTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
# Troubleshooting — team kit (template)

Failure modes any team's copy inherits. Keep this file in your copy and grow it
with your team's own entries. Every command is an `acq` command. The rules
behind these entries live in the pattern doc (`scope-layers.md`, see the
README); each entry names the section.

## Kit content missing (`OPENCODE_CONFIG` empty, files absent)

Almost always: the shell that *created* the sandbox did not source `env.sh`
(or `TEAM_KIT` pointed at the wrong path), so only the global kits applied.
Check:

```bash
acq exec <sandbox> -- printenv OPENCODE_CONFIG # empty => the kit did not apply
echo "$ACQ_EXTRA_KITS" # on the host: the team kit should come first
```

Fix the rc, then recreate:

```bash
acq rm <sandbox> && acq run opencode /path/to/project
```

Re-running `acq run` alone is not enough: `acq` records a sandbox's extra kits
at creation and reuses that record on reattach. See "Recreating a sandbox" in
the pattern doc.

## Create fails: the backend rejects the kit / unsupported `schemaVersion`

The kit is neutral `hybrid/v1`, which no backend parses natively; `acq`
translates it. If the raw spec reaches the backend (parse errors naming your
spec's fields), the `agentic-coding-quickstart` checkout predates acq's
kit-translate layer. Update it with `git pull`. A bare backend command that
bypasses `acq` fails for the same reason.

## Create fails fetching a global kit over SSH

The create output shows a `git@github.com:` URL and `Permission denied
(publickey)` for one of the pinned kits. A host `url.*.insteadOf` rewrite (for
example `url.git@github.com:.insteadOf https://github.com/`) sends acq's
anonymous kit fetch over SSH, which fails whenever your SSH key is not
available. Check for one:

```bash
git config --get-regexp 'url\..*insteadOf'
```

Make the key available (load it into your agent) and retry the create.

## A needed host is blocked

On msb, a blocked fetch fails at acq's egress proxy and `acq` prints a diagnosis
naming the host. Add it to `caps.network.allow` and recreate the sandbox:
egress is fixed at creation on every backend. Under org governance, org network
rules override kit rules; a host that stays blocked despite the kit entry must
be allowed at the org level.

## A team setting has the wrong value, or is missing

A kit applied *after* yours (a personal kit) can shadow a team value
silently (see the composition table in the pattern doc). Check what the
sandbox actually has, then look for the same variable name or file path in the
kits listed after yours:

```bash
acq exec <sandbox> -- printenv OPENCODE_CONFIG
acq kit list # the pinned kits plus THIS shell's ACQ_EXTRA_KITS
```

`acq kit list` shows neither `--kit` refs nor the extras recorded for an
existing sandbox, so compare against the `ACQ_EXTRA_KITS` the sandbox was
created with. Move the override, or document it as an allowed personal
override.

## The agent ignores the team conventions

`opencode debug config` lists `team-conventions.md` under `instructions`, but
the agent does not follow it or cannot quote it. Check which OpenCode the
sandbox runs:

```bash
acq exec <sandbox> -- opencode --version
```

OpenCode 2.x accepts `instructions` but never loads the files it lists. See
"Check against your acq" in the pattern doc.

## A daemon still uses the old team settings

A background process that a startup step launched (a web UI serving agent
sessions, for example) read its configuration when it started, so kit changes
that reached the sandbox since then do not reach it. Restart the sandbox:

```bash
acq restart <sandbox>
```

## A startup command did nothing, and validate said OK

`acq kit validate` passes several spec shapes that run nothing or run the
wrong thing: a comment after `phase:` or `user:` (the command is skipped with
only a warning in the create output), a comment after an argv item (it becomes
part of the argument), and flow-style argv (`command: [a, b]`, parsed as
empty). See "Check against your acq" in the pattern doc for the exact rules.

## Files landed, but the env var and startup effects are missing (msb)

One of the kit's `files[]` drops failed, which stops the rest of that kit's
apply on msb. The create output names the file ("could not place kit file at
..."); the usual cause is an empty payload. See "Check against your acq" in the
pattern doc.

## A shipped file is present on sbx but missing on msb

The file is under `files/` but has no `files[]` record in `spec.yaml`. Add the
record with the in-guest absolute path. See "Backend differences" in the
pattern doc.

## git push/fetch hangs or is refused on an SSH remote (msb)

The remote is `git@host:…` or `ssh://git@host/…`, and msb does not carry SSH
to allowed hosts (see "Backend differences"). Adding `host:22` to the allow
list does not help. A `--clone` workspace inherits the host checkout's SSH
origin, so this is the default case for a team VCS and for GitHub. Check what
git will actually contact:

```bash
acq exec <sandbox> -- git -C /path/in/guest/to/repo ls-remote --get-url origin
```

Fix: add the startup step from "Git over HTTPS" in the pattern doc.

## OpenCode: `The socket connection was closed unexpectedly` (msb)

Every request in one session fails the same way, on any model, and reopening
the sandbox does not help, while `curl` to the model endpoint from the same
sandbox succeeds. The session transcript contains a secret placeholder,
usually from a command that printed the environment (`env`, `printenv`), and
msb's proxy fails closed on it (see "Backend differences").

Fix: start a new session; the affected one cannot be sent as-is. Prevent it by
keeping "never print the environment" in the team conventions file.

## Rotating a team token breaks existing sandboxes

The new secret got a new placeholder; existing sandboxes still hold the old
one. Rotate with the same placeholder: see "Backend differences" in the
pattern doc for the command on each backend.

## Inspecting startup state

Startup-command output only prints during `acq run` or `acq create`. Inspect
later with a probe, and recreate to watch the output again:

```bash
acq exec <sandbox> -- sh -c 'git config --global push.autoSetupRemote'
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Decision: Deliver team agent settings through `OPENCODE_CONFIG`

**Status:** accepted (sample: keep, rewrite, or replace it in your copy)

## Context

The team needs settings every teammate's agent shares: team instructions,
permission rules for team tools. OpenCode reads config from three places, in
order: its global config path, the file `OPENCODE_CONFIG` names, and the
repo's own config. Later sources override earlier ones key by key.

The global `usai-provider` kit merges its provider settings into the global
config path at every start, and leaves `OPENCODE_CONFIG` unset. Writing the
team's settings to the global path as well would mean merging with that kit,
or overwriting it.

## Decision

Ship the team's settings as `files/home/team-config/opencode.jsonc` and set
`OPENCODE_CONFIG` to its in-guest path in `environment`. The team kit owns
that variable; a personal kit does not override it.

## Consequences

- Settings tier global → team → repo, with no merge script in this kit.
- `OPENCODE_CONFIG` is single-valued and last-wins across kits, so any later
kit that sets it hides the team settings without a warning.
- The conventions reach the agent through OpenCode's `instructions` field,
which OpenCode 2.x ignores. On an image whose sandbox shells run 2.x, the
team settings still apply but the conventions do not load; `scripts/verify`
fails there.
Loading
Loading