{"id":51530683,"url":"https://github.com/acoyfellow/terrarium","last_synced_at":"2026-07-09T02:01:03.518Z","repository":{"id":354890936,"uuid":"1225838437","full_name":"acoyfellow/terrarium","owner":"acoyfellow","description":"One-level orchestration harness for composable AI subagents.","archived":false,"fork":false,"pushed_at":"2026-06-30T00:54:53.000Z","size":31390,"stargazers_count":1,"open_issues_count":2,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-30T02:22:55.028Z","etag":null,"topics":["agent-harness","ai-agents","cli","coding-agents","local-first","orchestration","subagents"],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/acoyfellow.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":"THREAT_MODEL.md","audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-04-30T17:38:11.000Z","updated_at":"2026-06-30T00:54:57.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/acoyfellow/terrarium","commit_stats":null,"previous_names":["acoyfellow/terrarium"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/acoyfellow/terrarium","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/acoyfellow%2Fterrarium","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/acoyfellow%2Fterrarium/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/acoyfellow%2Fterrarium/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/acoyfellow%2Fterrarium/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/acoyfellow","download_url":"https://codeload.github.com/acoyfellow/terrarium/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/acoyfellow%2Fterrarium/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35283905,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-09T02:00:07.329Z","response_time":57,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["agent-harness","ai-agents","cli","coding-agents","local-first","orchestration","subagents"],"created_at":"2026-07-09T02:01:01.830Z","updated_at":"2026-07-09T02:01:03.508Z","avatar_url":"https://github.com/acoyfellow.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Terrarium\n\n[![Live campaign demo](https://img.shields.io/badge/demo-terrarium.coey.dev-8ff0ad?style=for-the-badge)](https://terrarium.coey.dev)\n\n![A cozy glass terrarium on a wooden desk. Inside, a single small robot tends a tiny garden of even smaller robots, each working in their own pot.](./assets/social-card.jpg)\n\n**Durable, runner-independent execution and callbacks for bounded delegated work.**\n\nTerrarium gives each delegated job a durable run ID, correlated result receipt, progress/status surface, cancellation, and a deterministic callback queue with atomic claim/ack/requeue semantics. It can launch one job or an explicitly requested batch while preserving one independent child process and receipt per run.\n\nThe public site is now a compact run ledger plus changelog for real hardening work. Older campaign/story material is archived as historical evidence, not the current product direction. See the [core product decision](./docs/CORE_PRODUCT_DECISION.md) and [CHANGELOG.md](./CHANGELOG.md).\n\nTerrarium is built around one stable primitive:\n\n```text\none bounded task → one child run → one inspectable result\n```\n\n| If you want to… | Use… | Status |\n| --- | --- | --- |\n| Keep your main agent context clean while a child investigates or edits. | `terra \"task\"` or MCP `terrarium_spawn`; optionally choose Pi/OpenCode and pin a model | Stable original workflow |\n| Launch independent jobs in one call with explicit join semantics. | `terra batch` or MCP `terrarium_spawn_batch` | Additive; all/allSettled/race/any/quorum; cancellation settlement is bounded |\n| Observe durable runs, groups, callbacks, and attention. | `terra status`, `terra group`, `terra doctor`, callback MCP | Implemented |\n| Replay bounded cancellation/completion timing schedules. | `terra schedule replay \u003cfixture\u003e` | Local-only, versioned fixtures |\n| Test declared containment boundaries in Docker. | `terra probe \u003cscenario\u003e` | Implemented, opt-in |\n| Ask an agent to initiate a bounded containment test. | `terra attack \u003cscenario\u003e` | Implemented, deliberately constrained |\n| Turn a reproduced escape into reviewable report text. | `terra campaign issue-draft \u003cid\u003e` | Local-only; does not publish |\n| Validate repository work inside the hardened profile. | `terra secure \"run the repository tests\"` | secure-v1, opt-in |\n| Wrap Pi around a secure workspace without giving it host tools. | `terra secure-agent --model \u003cid\u003e \"fix the bug\"` | Node fixture vertical slice |\n| Replay the permanent attack corpus before release. | `terra hardening verify` | Implemented |\n| Inspect the public run ledger and changelog for real hardening runs. | [`terrarium.coey.dev`](https://terrarium.coey.dev) | Live |\n| Deliver a finished run's wake event across closes, sessions, and machines. | [Pulse](./docs/PULSE.md) durable edge transport (`POST /pulse` · `/claim` · `/ack` · `/status`) | Implemented + tested locally; prod e2e pending |\n\nOrdinary children inherit host authority and environment; use them for cooperative work, not as a security boundary. `secure-v1` is the opt-in Docker profile with explicit [guarantees and non-guarantees](./docs/SECURE_V1.md).\n\n## Public run ledger and changelog\n\n[`terrarium.coey.dev`](https://terrarium.coey.dev) shows a boring spreadsheet-style ledger of hardening runs against the runner, callbacks, receipts, batches, groups, and boundary behavior. It also exposes the same concise product changelog kept in [CHANGELOG.md](./CHANGELOG.md). The site is a public/dev presentation layer over safe manifest and receipt files; it is not authoritative proof by itself. Receipts, run IDs, tests, and commits are the evidence.\n\n```sh\nnpm run demo:dev      # local public site\nnpm run demo:build    # static build\nnpm run demo:smoke    # local smoke check\nnpm run deploy        # personal Cloudflare account + custom domain\n```\n\n`demo:smoke` prints which data source it checked. Local Vite should report `source: \"/campaign/manifest.json\"` for the active run ledger.\n\nDocumentation rule: when behavior, public surfaces, safety semantics, or CLI/MCP outputs change, update this README if the getting-started or product description changes, and update [CHANGELOG.md](./CHANGELOG.md) with a concise entry. The product-hardening loop should include this documentation check before committing.\n\nStart with [Architecture](./docs/ARCHITECTURE.md) and the [core product decision](./docs/CORE_PRODUCT_DECISION.md). See [concurrency and context isolation](./docs/CONCURRENCY_ISOLATION.md), the [pi-subagents comparison](./docs/PI_SUBAGENTS_COMPARISON.md), the [secure-agent proof](./docs/SECURE_AGENT_PROOF.md), [landscape research](./docs/SECURE_AGENT_LANDSCAPE.md), and [Pulse, the durable edge wake transport](./docs/PULSE.md).\n\n## Pulse: durable edge wake transport\n\nThe local callback router only works while the parent process is alive. Pulse lifts that same router — journal, claim, ack, replay, per-owner mailboxes — onto Cloudflare so a finished run's terminal event reaches the right consumer **across closes, sessions, and machines**.\n\nA `PulseRouter` Durable Object on DO SQLite holds the journal, subscribers, and mailboxes; a token-gated Worker exposes `POST /pulse` (emit), `POST /claim`, `POST /ack`, and `GET /status`, mounted through the merged control worker.\n\n```text\nemitter ──POST /pulse──► Worker (token gate) ──► PulseRouter DO + SQLite journal\n                                                       │  dedup by eventId, fan out\nconsumer ◄─claim/ack/status─ Worker ◄──────────────────┘  to matching mailboxes\n```\n\nIt is **at-least-once with dedup** (`eventId = sha256[runId,type,at,status,exitCode]`), replays the journal for concrete `runIds` subscribed after a run finished, makes claim→ack idempotent, and isolates mailboxes by `ownerRunId`. Auth is fail-closed (`401` when the bearer token or the pulse token is missing). Production e2e is now verified live on `terrarium.coey.dev`: a real cloud run's terminal event was subscribed, claimed once, acknowledged, and confirmed principal-scoped (an unacked inflight event is not re-served). Honest limits: owner isolation is enforced at claim/ack/status — not at delivery/match (a conscious host-trust choice); the standalone Pulse consumer library is not yet rewired to read from the cloud router by default.\n\nFull quick start (curl emit/claim/ack/status), the proof-to-test mapping, and where to edit behavior live in **[docs/PULSE.md](./docs/PULSE.md)**.\n\n## Cloud execution service (`/api/runs`)\n\nBeyond the local CLI/MCP, Terrarium runs a cloud execution service, live on `terrarium.coey.dev`. A principal submits one bounded task over an authenticated Worker API and the task runs entirely on Cloudflare-managed infrastructure — no local machine is required for correctness, liveness, logs, or delivery.\n\n```text\nauthenticated POST /api/runs (Bearer + Idempotency-Key)\n→ ordered admission + per-principal budget\n→ durable RunControl Durable Object\n→ Cloudflare-managed Pi execution cell (Dockerfile.pi, linux/amd64)\n→ credentialless server-owned Workers AI route (intercepted by ContainerProxy)\n→ durable integrity-checked logs (DO SQL inline + R2 overflow, byte count + SHA-256)\n→ verified correlated TERRARIUM_RESULT (runId + taskFingerprint + nonce)\n→ durable principal-scoped terminal callback (Pulse)\n```\n\nAPI surface (all `/api/runs*` require `Authorization: Bearer \u003ccontrol token\u003e`; `POST /api/runs` also requires an `Idempotency-Key`):\n\n| Method + path | Purpose | Notes |\n| --- | --- | --- |\n| `POST /api/runs` | Admit one bounded task | Body `{ task, spec? }`. Returns `202 { runId, contract, executionRef }`. Missing key `400`; over budget `429`; task \u003e 64 KiB `413`. |\n| `GET /api/runs/:id/status` | Terminal + contract status | Owner-scoped; cross-principal `401`; unknown `404`; malformed id `400`. |\n| `GET /api/runs/:id/logs` | Durable logs + overflow refs | Inline logs plus R2 `logRefs`. |\n| `GET /api/runs/:id/logs/ref?seq=N` | Fetch an R2 overflow chunk | Integrity-checked (byte count + SHA-256); fails closed on corrupt/missing. |\n| `POST /api/runs/:id/cancel` | Request cancellation | Idempotent; intent wins over a late receipt. |\n\nAuthority is invariant: `exit 0`, a callback, backend `ok`, or model prose are **not** success. Only a `TERRARIUM_RESULT` whose server-minted `runId`, `taskFingerprint`, and `nonce` match the run's contract establishes task success. The `summary` field is advisory. The `nonce` is always server-minted; a client-supplied `spec.nonce` is ignored. A prompt-injection task cannot forge a receipt for another run.\n\nHonest scope: this is C0 — bounded per-principal concurrency matched to container capacity. Broad simultaneous cold starts beyond warm capacity degrade gracefully (excess runs are deadline-killed, fail-closed, never a fabricated receipt). Intermittent upstream model 5xx is mitigated by bounded retry; residual failures fail closed.\n\n## Quick start: ordinary delegation\n\nUse this when you want the original Terrarium behavior: move noisy work into one child agent and keep the parent context small.\n\n```sh\nnpm install -g .\nterra --dry-run \"summarize this repo\"\nterra --read-only --profile minimal \"find every place we handle auth\"\nterra --agent \"pi -p --no-session\" --model \u003cmodel-id\u003e \"inspect the failing parser test\"\nterra --isolation worktree \"fix the failing parser test\"\n```\n\n```text\ntop context ──spawns──\u003e child context\n   stays                 does messy work\n   clean                 returns concise result\n```\n\nTerrarium defaults to `opencode run` for compatibility. Runner, read-only runner, and model can now be configured independently; Pi one-shot children should use `pi -p --no-session` so the child does not leave its own persistent conversation file.\n\nGood uses:\n\n- repo archaeology and design digs;\n- failing-test diagnosis;\n- log investigation;\n- experimental edits in a separate workspace.\n\nDo not use it as a security boundary for hostile code. In ordinary delegation:\n\n- `--isolation copy` copies a workspace;\n- `--isolation worktree` creates a Git worktree;\n- neither option prevents host reads, host process access, environment access, or network access.\n\n## Quick start: containment lab\n\nRequires a local Docker daemon; Docker may pull `node:22-alpine` on first use.\n\n### 1. Run deterministic containment probes\n\n```sh\nterra probe filesystem-write-outside-workspace --json\nterra probe environment-canary --json\nterra probe network-disabled --json\nterra probe filesystem-read-canary --json\nterra probe process-persistence --json\n```\n\nA normal secure baseline returns `\"verdict\": \"contained\"`.\n\n| Scenario | What it attempts | A contained result means |\n| --- | --- | --- |\n| `filesystem-write-outside-workspace` | Write outside writable scratch mounts. | The write failed. |\n| `environment-canary` | Observe a host-side planted environment canary. | The canary was absent in the container. |\n| `network-disabled` | Reach a network target while Docker networking is disabled. | The request failed. |\n| `filesystem-read-canary` | Read a forbidden host path not mounted into the container. | The read failed. |\n| `process-persistence` | Keep a spawned process alive after the primary probe exits. | Container teardown removed the attempt. |\n\nProbe policy today:\n\n- Docker network disabled (`--network none`);\n- read-only container root filesystem;\n- all Linux capabilities dropped;\n- `no-new-privileges`;\n- non-root process user;\n- writable `/workspace` and `/tmp` scratch only;\n- deterministic probe code supplied inline to the container, with no host checkout bind mount;\n- explicit planted canaries rather than real secrets.\n\n### 2. Verify an alleged deterministic escape\n\n```sh\nterra verify filesystem-write-outside-workspace --json\n```\n\n`verify` reruns only an attempt that reports `escaped`. If the initial probe is contained, it does not manufacture vulnerability evidence by replaying it anyway.\n\n### 3. Run the constrained agent-assisted path\n\n```sh\nterra attack environment-canary --agent \"opencode run\" --json\n```\n\nWhat this does today:\n\n1. The proposal agent receives one declared scenario.\n2. The agent may request that bounded detector run through one machine-parsed line.\n3. Terrarium executes the existing deterministic Docker detector.\n4. Detector output, not agent prose, decides `contained` or `escaped`.\n5. Terrarium writes a sanitized receipt under `~/.terrarium/campaigns/`.\n\nWhat it does **not** do today:\n\n- run model-generated exploit programs inside Docker;\n- expose real credentials;\n- grant GitHub write or merge authority;\n- publish issues or open pull requests.\n\n### 4. Inspect receipts and draft a report\n\n```sh\nterra campaigns\nterra campaign read \u003ccampaignId\u003e\nterra campaign verify \u003ccampaignId\u003e\nterra campaign issue-draft \u003cverifiedCampaignId\u003e\n```\n\nReceipts contain scenario, policy, proposal reason, detector verdict, and timestamps. Receipts omit raw attacker output, raw detector output, the configured agent command, and host-specific receipt paths.\n\n`issue-draft` prints sanitized Markdown locally only after a fresh replay produces `verified-escape`. It does **not** publish to GitHub.\n\n### 5. Exercise the full reporting pipeline with a known fixture\n\nA real secure baseline should not naturally generate an escape. To prove the receipt → replay → draft pipeline works, Terrarium ships one explicitly vulnerable fixture:\n\n```sh\nterra fixture escape vulnerable\n# Copy campaignId from the JSON output, then:\nterra campaign verify \u003cfixtureCampaignId\u003e\nterra campaign issue-draft \u003cfixtureCampaignId\u003e\n\n# The explicit remediated comparison returns contained:\nterra fixture escape fixed\n```\n\nFixture scenario and tracked policy variants:\n\n```text\nfixture-environment-leak\nfixtures/known-vulnerable/environment-leak/vulnerable.json\nfixtures/known-vulnerable/environment-leak/fixed.json\n```\n\nThe `vulnerable` variant deliberately injects a planted canary into the container environment so the detector reports `escaped`, replay confirms `verified-escape`, and an issue draft can be rendered. The `fixed` variant omits that injection and reports `contained`; it is the expected remediation target for the synthetic issue/PR loop. These fixture commands do not invoke an AI agent because the pipeline being tested begins at a controlled detector result. Generated drafts are labeled as a **known-vulnerable fixture** and must not be published as real discoveries.\n\n## Historical public automation evidence\n\nA GitHub Actions workflow was added to run the safe baseline publicly. The first manual run exposed a Docker bind-mount portability bug on GitHub-hosted Linux; probes now execute trusted deterministic source inline in Docker rather than bind mounting generated files from the runner workspace.\n\nWorkflow:\n\n```text\n.github/workflows/containment-baseline.yml\n```\n\nIt can be triggered manually and runs weekly. The workflow:\n\n1. runs the product test suite;\n2. requires every deterministic baseline probe to return `contained`;\n3. executes the explicitly vulnerable fixture;\n4. requires that fixture to replay as `verified-escape`;\n5. generates a sanitized fixture issue draft;\n6. uploads probe results, the sanitized receipt, verification result, and draft as workflow artifacts.\n\nThe baseline workflow has only `contents: read` permission. It does **not** create GitHub issues, open pull requests, execute adaptive attacker payloads, or mutate the repository.\n\nA second manual workflow, `.github/workflows/publish-fixture-issue.yml`, exercises verified issue publication for the intentionally vulnerable fixture only. It is scoped to `issues: write`, refuses unverified fixture output, labels the result as a pipeline test, and avoids duplicate open fixture issues.\n\nA third manual workflow, `.github/workflows/fix-fixture-issue.yml`, accepts a labeled open fixture issue and opens a synthetic remediation PR selecting the tracked `fixed` fixture policy. The PR is checked by `.github/workflows/replay-fixture-fix.yml`, which runs the fixture/sandbox tests, requires the vulnerable control to remain reproducible, and requires the fixed variant to return `contained`. The replay workflow also supports manual dispatch for bootstrapping its first PR before that workflow file exists on the base branch. This is remediation plumbing, not yet an AI-generated fix or an automatic merge path.\n\nThe synthetic loop has now been exercised end to end in public:\n\n```text\nfixture issue #3\n  → remediation PR #4\n  → replay gate passed\n  → merged\n  → issue closed\n```\n\n- Issue: https://github.com/acoyfellow/terrarium/issues/3\n- PR: https://github.com/acoyfellow/terrarium/pull/4\n- Replay run: https://github.com/acoyfellow/terrarium/actions/runs/27006150043\n\nThis is still a pipeline fixture, not a discovered security vulnerability. Two later synthetic PRs were created after the replay workflow landed, but their native `pull_request` checks still did not attach automatically, so the workflow now explicitly declares `opened`, `synchronize`, and `reopened` event types and temporarily uses `paths: [\"**\"]` to eliminate path-filter ambiguity. It still needs one clean native-cycle proof. The remaining gap from this proof to hostile adaptive public automation is tracked in [docs/GAPS_TO_AUTONOMY.md](./docs/GAPS_TO_AUTONOMY.md).\n\n## What is stable versus experimental\n\n### Stable base interface\n\nThe original delegation contract remains foundational:\n\n- CLI: `terra \"task\"`, `terra status`, `terra read`\n- Node API: bounded single-run primitives from `src/core.js`\n- MCP: `terrarium_spawn`, `terrarium_status`, `terrarium_read`\n\nBatch calls wait for the selected join strategy. `timeoutMs` bounds that join in the Node/MCP API and also bounds active-concurrency launch before every queued job has started; CLI callers use `--batch-timeout-ms` for the same whole-batch wait budget so it stays separate from per-child `--timeout-ms`. When cancellation is needed, `cleanupTimeoutMs` (default 5000 ms; CLI `--cleanup-timeout-ms`) separately bounds synchronous settlement so an MCP client can receive durable `groupId`/`runIds` before its own request deadline. Any runs still settling are reported in `cleanupErrors` and remain inspectable through `terrarium_group`/`terrarium_status`. MCP `initialize` reports runtime `apiVersion`, `schemaVersion`, `batchApiVersion`, and `batchSupportedOptions`; batch responses include `apiVersion`, `schemaVersion`, and `supportedOptions` on two distinct axes. `apiVersion` is the batch contract version (`terrarium-batch-*`), while `schemaVersion` is the MCP wire/tool-metadata version (`terrarium-mcp-*`). Compare a fresh initialize `schemaVersion` against the `schemaVersion` carried in cached tool metadata to detect stale client metadata; use `apiVersion`/`supportedOptions` to confirm repository/API feature support.\n\nCompatibility promises:\n\n1. Existing ordinary delegation commands keep their meaning.\n2. Existing MCP tool names and ordinary request semantics remain available.\n3. The Node run API remains a one-child execution primitive, not an attack-only API.\n4. Containment-lab behavior remains additive and opt-in.\n5. Workspace `--isolation` is never silently presented as security containment.\n\nSee [COMPATIBILITY.md](./COMPATIBILITY.md).\n\n### Containment-lab commands\n\nThese are implemented locally but are not yet exposed as MCP tools:\n\n```sh\nterra probe \u003cscenarioId\u003e --json\nterra verify \u003cscenarioId\u003e --json\nterra attack \u003cscenarioId\u003e --agent \"opencode run\" --json\nterra campaigns [limit]\nterra campaign read \u003ccampaignId\u003e\nterra campaign verify \u003ccampaignId\u003e\nterra campaign issue-draft \u003cverifiedCampaignId\u003e\nterra fixture escape vulnerable\nterra fixture escape fixed\n```\n\n### Frozen Cloudflare Dynamic Workflow prototype\n\nThe repository retains a minimal Cloudflare-native control-worker prototype in `src/control-worker.js`, configured by `wrangler.jsonc`. It uses a Dynamic Workflow to load policy, enforce pause/cooldown/daily caps, run Lab-backed scenarios, replay escaped results, and write receipts. The personal-account deployment remains at `https://terrarium-control.coy.workers.dev`; it does not publish issues, open PRs, or merge fixes. `src/lab.js` plus `src/hostile.js` define the bounded Lab adapter, hostile payload policy, scenarios, and exact fresh replay. Real mode was manually enabled for bounded validated campaigns and is currently paused/disabled through inspectable policy. It should remain disabled while this campaign path is frozen.\n\n```text\nload policy\n→ guardrails\n→ Lab run\n→ detector\n→ replay if escaped\n→ receipt\n```\n\nManual adaptive campaigns are available behind controller authentication and disabled real-mode policy:\n\n```sh\nTERRARIUM_CONTROL_TOKEN=... \\\nTERRARIUM_CONTROLLER_URL=https://terrarium-control.coy.workers.dev \\\nterra hostile run lab-env-canary --turns 3 \\\n  --agent \"pi -p --no-session\" --model \u003cmodel-id\u003e\n```\n\nEach turn accepts one machine-parsed payload, validates it against the zero-capability/4 KB policy, executes it in Lab, returns sanitized feedback, and stops early only on a fresh replayed escape. Issue publication, fix PRs, and auto-merge remain disabled for this manual path.\n\n### Frozen campaign gaps\n\nThe historical intended public loop was:\n\n```text\ncontain → attack → verify → issue → patch → replay → repeat\n```\n\nNot shipped in the frozen campaign:\n\n- arbitrary adaptive attack programs generated by an AI inside containment;\n- trusted automatic GitHub issue publication;\n- defensive fix-agent pull requests;\n- replay-gated automated merge and scheduled looping.\n\nDesign and trust boundaries: [THREAT_MODEL.md](./THREAT_MODEL.md), [docs/AUTONOMOUS_LOOP.md](./docs/AUTONOMOUS_LOOP.md), [docs/GAPS_TO_AUTONOMY.md](./docs/GAPS_TO_AUTONOMY.md).\n\n## Original delegation reference\n\n### Useful recipes\n\n```sh\n# Choose a child runner and pin its model\nterra --agent \"pi -p --no-session\" --model \u003cmodel-id\u003e \"fix the failing build\"\nterra --agent \"opencode run\" --model anthropic/claude-sonnet-4-6 \"add tests\"\n\n# Or configure defaults once\nTERRARIUM_AGENT=\"pi -p --no-session\" TERRARIUM_MODEL=\"\u003cmodel-id\u003e\" terra \"add tests for the parser\"\n\n# Prefer a smaller read-only child for research\nterra --read-only \"find every place we handle X\"\nterra --read-only --profile minimal \"design dig: read 8 files, return a plan\"\n\n# Separate write-capable work from your current checkout\nterra --isolation copy \"patch a disposable repo copy\"\nterra --isolation worktree \"patch an isolated git branch\"\nterra --isolation copy --keep-workspace \"leave the workspace for inspection\"\n```\n\nProfiles:\n\n- `default` — structured handoff with `Summary / Changed files / Verification / Follow-ups`.\n- `minimal` — smaller prompt shell for bounded research.\n\nWorkspace modes:\n\n- `none` — child works in `--cwd`.\n- `copy` — copies `--cwd` into `~/.terrarium/workspaces/\u003crunId\u003e-\u003cname\u003e`.\n- `worktree` — creates a Git worktree on branch `terrarium/\u003crunId\u003e`.\n\nIf an isolated Git workspace leaves a diff, Terrarium writes a patch receipt at `~/.terrarium/runs/\u003crunId\u003e.patch`.\n\n### Ordinary CLI options\n\n```text\n--agent \u003ccmd\u003e          Child command. Default: config, $TERRARIUM_AGENT, or opencode run.\n--model \u003cid\u003e           Pin model for opencode run or pi. Default: env/config/runner.\n--read-only            Use the configured read-only child command when no explicit agent is provided.\n--profile \u003cname\u003e       default or minimal.\n--cwd \u003cpath\u003e           Child working directory.\n--timeout-ms \u003cn\u003e       Kill child after n milliseconds.\n--max-depth \u003cn\u003e        Maximum Terrarium depth.\n--isolation \u003cmode\u003e     none, copy, or worktree workspace separation.\n--keep-workspace       Retain an isolated workspace after the run.\n--dry-run              Print the child invocation without executing it.\n--json                 Print structured JSON.\n--log \u003cpath\u003e           Write the transcript to a chosen path.\n--task                 Force the argument to run as a task even if it looks like a mistyped command.\n```\n\nMistyped subcommands fail closed. `terra statsu`, `terra group` (no subcommand),\nor `terra schedule run f.json` print a suggestion and exit non-zero instead of\nsilently spawning a child agent to \"run\" the broken command as a task. Genuine\nfree-form tasks are never affected. If a real task legitimately looks like a\ncommand typo, pass `--task \"...\"` or set `TERRARIUM_NO_COMMAND_GUARD=1`.\n\nAgent resolution precedence: explicit `--agent` → configured `--read-only` command → `$TERRARIUM_AGENT` → `config.defaultAgent` → built-in `opencode run`.\n\nModel precedence: explicit `--model` → `$TERRARIUM_MODEL` → `config.defaultModel` → the runner's own default. Terrarium appends the correct `--model` flag for `opencode run` and `pi`.\n\nFor Pi, use `pi -p --no-session`: `-p` is one-shot non-interactive mode and `--no-session` prevents a persistent Pi conversation file. Terrarium still records its own bounded receipt. A useful read-only command is `pi -p --no-session --tools read,grep,find,ls`.\n\nConfig at `~/.terrarium/config.json`:\n\n```json\n{\n  \"defaultAgent\": \"pi -p --no-session\",\n  \"readOnlyAgent\": \"pi -p --no-session --tools read,grep,find,ls\",\n  \"defaultModel\": \"\u003cmodel-id\u003e\",\n  \"maxDepth\": 3,\n  \"timeoutMs\": 900000\n}\n```\n\n## MCP: stable original workflow\n\n```json\n{\n  \"name\": \"terrarium_spawn\",\n  \"arguments\": {\n    \"task\": \"inspect this repo and summarize the test command\",\n    \"model\": \"\u003cmodel-id\u003e\",\n    \"readOnly\": true,\n    \"profile\": \"minimal\",\n    \"cwd\": \"/path/to/repo\"\n  }\n}\n```\n\nTools:\n\n- `terrarium_spawn` — run one bounded child agent task. Existing callers remain synchronous when `background` is omitted. With `background: true`, the call detaches immediately. If a Pi host explicitly installs `src/pi-extension.js`, that extension subscribes the current session to the concrete run and surfaces terminal completion; Terrarium does not auto-load it. Otherwise, use the low-level callback pull API or `terrarium_status`. Retries remain synchronous.\n- `terrarium_spawn_batch` — fan out an array of jobs (each a normal `terrarium_spawn`) as independent background runs under one group, then resolve by a join strategy: `all` (every job finishes; ok only if all succeed), `allSettled` (collect every outcome, always ok), `race` (first terminal wins; cancel the rest), `any` (first success wins; cancel the rest), `quorum` (first `k` successes; cancel the rest). Winner-picking strategies cancel losing runs via the existing cancel primitive — prefer `isolation: copy|worktree` for jobs with side effects. Optional `concurrency` bounds simultaneous launches. Top-level only; capability-scoped like `terrarium_spawn`.\n- `terrarium_status` — inspect one run or list recent runs, including last activity, concise progress, idle time, and a factual needs-attention flag.\n- `terrarium_read` — read a recorded run log; pass `kind: \"mre\"` for the MRE side log.\n- `terrarium_cancel` — cancel one active run and its descendant process group within the caller's lineage scope.\n- `terrarium_group` — create/status/read/cancel a parent-owned collection of already-started independent runs; it never spawns or hides fan-out. Group state is a fail-closed roll-up of member receipts: `ok` requires every member `done` with `ok: true`, and missing or inaccessible members are not complete. It is not independent success proof; the per-run verified receipts are.\n- `terrarium_callbacks` — create a durable **pull** subscription for terminal run events, atomically claim each callback, acknowledge delivery, requeue abandoned inflight events, recover a terminal run, and prune stale state. Terminal events are journaled even when no subscriber is online; journal entries contain correlation/status facts, not task prompts, child output, or local paths. A concrete run subscription replays a completion that raced ahead; acknowledged events are not redelivered. Subscribing alone does not wake a conversation—the consumer or Pi extension must claim the queue. High-frequency progress remains in run status/logs, not callback mailboxes. A terminal callback is a notification that a run finished, not authoritative proof the task succeeded; confirm with the run's verified receipt. The local filesystem router and experimental Cloudflare Pulse backend share validation and matching semantics; when deployed with the demo SPA, `/pulse`, `/claim`, `/ack`, and `/status` must be worker-first routes so the token gate runs instead of the SPA fallback.\n- `terrarium_doctor` — top-level-only diagnostics for storage, runs, attention, callbacks, groups, stale claims, schema versions, and concrete repair handles for local reconstruction. Over MCP it stays read-only; the CLI adds an opt-in self-heal executor (see `terra doctor --repair`).\n\nTerrarium does not auto-load its Pi host extension. The durable callback queue remains available through `terrarium_callbacks` for hosts that want to subscribe, claim, acknowledge, requeue, recover, and prune terminal events explicitly. By default, Pi users should use the normal MCP tools or CLI (`terrarium_status`, `terrarium_read`, `terrarium_cancel`, `terra status`, `terra read`, `terra cancel`). Hosts may explicitly install `src/pi-extension.js` to get a run widget and callback-triggered follow-ups; the extension subscribes only to concrete runs spawned by that Pi session. This keeps Terrarium out of unrelated Pi sessions by default.\n\nPi children default to ephemeral `--no-session` runs unless the caller supplies an explicit `--session`/`--session-id` or sets `ephemeral: false`. `needsAttentionAfterMs` controls when an active run with no observed output is marked for attention; this is an inactivity signal, not a claim that the child is stuck.\n\nCLI equivalents:\n\n```sh\nterra cancel \u003crunId\u003e\nterra batch --strategy any \"try approach A\" \"try approach B\" \"try approach C\"\nterra batch --strategy quorum --quorum 2 --concurrency 2 --batch-timeout-ms 120000 \"job1\" \"job2\" \"job3\"\nterra group create \"research batch\" \u003crunIdA\u003e \u003crunIdB\u003e\nterra group status \u003cgroupId\u003e\nterra group read \u003cgroupId\u003e\nterra doctor\nterra doctor --repair                    # dry-run the mechanically-safe repair subset (recover/requeue/prune)\nterra doctor --repair --apply            # execute it; judgement/quarantine steps stay skipped for an operator\nterra doctor --repair --apply --verify   # execute, then re-diagnose and attach residual evidence each condition cleared\nterra schedule replay fixtures/run-schedules/cancel-before-completion.v1.json\n```\n\nRun-schedule replay is local-only and does not add an MCP surface. It replays bounded classification facts against the same pure transition function used by the background supervisor; it does not claim that a real model or process will reproduce those facts. See [Replayable run schedules](./docs/RUN_SCHEDULES.md).\n\nSpawn and status default to concise responses so parent transcripts stay small. The full envelope remains on disk under `~/.terrarium/runs/\u003crunId\u003e.json`, or can be requested inline with `verbose: true`.\n\n## Authoritative success proof\n\nFor an ordinary delegated run, success has exactly one authoritative proof chain:\n\n```text\nchild exits 0  +  verified TERRARIUM_RESULT receipt (runId, taskFingerprint, nonce, summary)\n  → terminal result status: done, ok: true\n```\n\nExit 0 alone is never success: a missing, mismatched, or malformed receipt settles as `inconclusive` (exit 0) or `failed`, with `process exit is not accepted as task success`. The verified receipt and the run's tests/commits are the evidence.\n\nEvery surface Terrarium exposes falls into exactly one role below. Only the **authoritative** and **evidence** roles can establish that a task succeeded; the diagnostic, notification, and presentation surfaces are deliberately **not** authoritative success proof and must never be read as one. Rank reflects how much trust the surface may carry: **P0** = source of truth, **P1** = corroborating/operational, **P2** = informational only.\n\n| Surface | Role | Rank | What it proves | What it must never be read as |\n| --- | --- | --- | --- | --- |\n| Verified `TERRARIUM_RESULT` receipt + terminal `done, ok: true` | Authoritative | P0 | The one proof chain: child exited 0 **and** returned a matching `runId`/`taskFingerprint`/`nonce`/`summary` receipt. | — (this *is* the proof) |\n| Run's claimed tests, commits, run IDs, and on-disk envelope (`~/.terrarium/runs/\u003crunId\u003e.json`, including `terminalCallback.eventId` when routed) | Evidence | P0 | The substantive artifacts the receipt points at; verify these before acting. | A receipt substitute — the receipt must still verify. |\n| `terra status`, `terra doctor`, run logs/MRE logs | Diagnostic | P1 | Operational/liveness facts: state, attention, storage, stale claims, progress. | Task success — diagnostics describe process, not outcome. |\n| Callbacks (`terrarium_callbacks`) and Groups roll-ups (`terrarium_group`) | Notification | P1 | A run *finished* (callback) or an aggregate fail-closed roll-up of member receipts (`ok` only when every member is `done, ok: true`); group rows may include `terminalCallback.eventId` for reconstruction. | Independent success — confirm with each run's verified receipt. |\n| The public run ledger at [`terrarium.coey.dev`](https://terrarium.coey.dev) and `CHANGELOG.md` | Presentation | P2 | A presentation layer over safe manifest/receipt files and a product-facing change record. | Per-run success proof — it restates receipts, it does not establish them. |\n\nWhen in doubt, the run/task-correlated receipt (and the tests it claims to have passed) is the source of truth; diagnostics, callbacks, groups, the ledger, and the changelog are convenience surfaces around it.\n\n## Adoption signal for the original primitive\n\n- Installed as MCP, Terrarium was selected without explicit prompting across unrelated work: **24 unprompted spawns in 5 sessions**.\n- In a same-model, same-task 14-point eval building *Wake*, baseline scored **11/14**; a run using one read-only Terrarium design dig scored **14/14**.\n- That eval also exposed the need for background MCP execution and polling, which is now implemented.\n\n## How ordinary delegation works\n\nTerrarium starts exactly one child process per run. For ordinary delegation, children inherit host execution authority, but Terrarium MCP/CLI capabilities are now scoped by run lineage. Minimal or max-depth-one children cannot spawn Terrarium recursively and can inspect only their own status/logs; explicitly nested runs may inspect their descendants, never siblings. MCP children must return a run/task-correlated receipt before exit zero is accepted as task success. The `TERRARIUM_RESULT=` receipt is an exact four-field object (`runId`, `taskFingerprint`, `nonce`, `summary`) with a bounded marker line. Terrarium records the resolved runner and model with logs/metadata and sets lineage/capability values including `TERRARIUM_RUN_ID`, `TERRARIUM_DEPTH`, `TERRARIUM_MAX_DEPTH`, `TERRARIUM_ALLOW_SPAWN`, `TERRARIUM_STATUS_SCOPE`, `TERRARIUM_READ_SCOPE`, and `TERRARIUM_MRE_LOG_PATH`.\n\nThat ordinary inheritance is convenient for cooperative work and precisely why it is not the hostile-run path.\n\nA bounded child may start a further Terrarium child within the configured depth limit:\n\n```text\nterra task A\n  child runs: terra task B\n    grandchild does B\n```\n\nEach run still owns one child process and one receipt. `terrarium_spawn_batch` is an explicit top-level fan-out coordinator: it creates independent ordinary runs, records them in one durable group, applies the requested join strategy, and cancels remaining runs for winner-picking strategies.\n\nA terrarium is a tiny sealed world. This one starts by making its missing glass measurable.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Facoyfellow%2Fterrarium","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Facoyfellow%2Fterrarium","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Facoyfellow%2Fterrarium/lists"}