You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A locked set of decisions — a handoff spec an implementation effort can start from with nothing left to argue about — defining a radically simpler Actana Control:
Two container images. Panel on a distroless runtime; Core a real daemon container that actually runs agent CLIs.
One installer.curl … | sh turns a Linux machine into a Core. No Panel outside Docker.
Three CI workflows. ci / release / housekeeping, replacing nine.
One vocabulary. Core everywhere; "Harness" retired.
A task-shaped docs tree and a thin repo root.
Protected main. No direct commits, by anyone.
This map produces decisions, not code. Prototypes are throwaway evidence; the deliverable is written answers.
Notes
Domain. Actana Control is a self-hosted control plane for agentic coding. A Panel (web service, one deployment) drives N Cores (machines running a daemon that spawns PTY sessions running Claude Code / Codex / Cursor CLI against git repos). A machine becomes a Core via the installer; the Panel pairs with it over core-link.
Skills every session should consult./grilling and /domain-modeling by default; /research for AFK research tickets; /prototype for prototype tickets.
Tracker gotcha — read this before any gh call. No git remote in this clone points at actana/control. origin is AgentSystemLabs/mission-control (the read-only upstream fork parent), so bare gh issue … targets the wrong repo. Always pass --repo actana/control.
Frame locked during charting — settled, do not re-litigate:
Destination is decisions, then hand off. No implementation on this map beyond throwaway prototypes.
Core image = a real daemon container that ships git, Node and agent CLIs. The vulnerability count is fixed by changing the base and how packages arrive — not by stripping the toolchain. The toolchain is the product.
⚠️Amended by Core image: pick the base and what ships inside it #8 — do not cite the struck half as settled. Harnesses (agent CLIs) are not baked: ~1.15 GB of a ~1.4 GB image, ~1 finding, and four vendors' redistribution licences uncleared. They install at runtime into the persistent home, so they survive image upgrades and self-update in place. The rest of the item stands, but state the mechanism correctly: the base did change, and the toolchain did ship — but what moves 1248 → ~38 is the package set. build-essential → linux-libc-dev is 1200 of 1328 OS findings. (ADR 0016 D6, D9)
Panel runtime = distroless, with a normal full-Node build stage. Build-stage CVEs don't ship.
Full rename Harness → Core, end to end: package dirs, npm names, workflow names, release assets, docs.
CI collapses to three workflows — ci / release / housekeeping.
Core is Linux x64 + arm64 only. macOS Core targets are dropped.
Docs: thin root, task-shaped docs/. Root keeps README + the community files GitHub expects; everything else moves under docs/ organised by what the reader is trying to do.
Docker Hub is the primary registry, GHCR a mirror. Per-image descriptions on both.
main is protected with no bypass actors — PR + green CI required, binding on admins. Every implementation session after this map must work through PRs.
.scratch/, docs/specs/, docs/tickets/ get deleted; git history is the archive. Anything load-bearing is promoted first.
⚠️The struck premise is false and nothing may be deleted on the strength of it.actana/control is one squashed commit — c2b8d0d8 chore: initial commit. There is no history on it. The archive is this working clone and the azure remote. The sharpest case is repo-template/: untracked, present only in this clone, in no git history anywhere — deleting it destroys it, so it moves out first (#57). The promote-first half of the item stands and is enforced by #58's dependency on #53–#57. (ADR 0016 D46)
Standing tension to respect. Distroless has no shell — fine for the Panel, fatal for a Core, whose whole job is spawning shells. Panel and Core will not share a base image.
Core image: measure the candidate bases #7 — Core base measured. Every candidate scanned on one scanner (Trivy 0.73.0, DB 2026-08-03) on a distinct-CVE basis, with the glibc/musl and shell gates measured rather than argued. Reframed Core image: pick the base and what ships inside it #8: build-essential → linux-libc-dev is 1200 of 1328 OS findings, so the package set is the high-leverage decision, not the base. (decision)
Core image: pick the base and what ships inside it #8 — Core image: ubuntu:24.04, digest-pinned.apt-get upgrade in the same layer, native-addon toolchain in with its ~1200 linux-libc-dev findings suppressed and justified, Harnesses not baked, Node 24 from the nodejs.org tarball sha256-verified, weekly rebuild gated on fixable CRITICAL/HIGH — 1248 → ~38 distinct CVEs, 751 → ~190 MB. Overturns frame item 2's baked-CLIs half. (decision)
Core as a container: process model, config, pairing, state #9 — Core as a container. The image is the install (actana setup bypassed entirely), tini as PID 1, one required env var (ACTANA_PUBLIC_HOST), first-run identity mint persisted into a single /home/core volume, host changes re-issue the server cert from the kept CA instead of re-minting, deploy/dev/ deleted, one reference compose with no TLS terminator. (decision)
Panel on distroless: prove it works and measure the delta #10 — Distroless Panel works. Built and run: 192 → 14 CVEs, zero CRITICAL and zero HIGH from the OS, reproducible in one command. Also settled the rootless posture (numeric USER 65532:65532 on the default tag) and found three failure modes that fail quietly — the discarded staged chown on /data being the one that would ship a broken deployment. (decision)
Harness to Core: rename scope and the migration it forces #12 — Rename decided; it is two renames, not one. Machine-Harness → Core; the agentic CLI → Harness; "Agent" retired. 233 files, one sweep. Compatibility question is empty — zero published releases, so no shim and no deprecation window. Resolves "fate of packages/shared": it survives as a third package, because it holds the Panel↔Core wire contract. (decision)
Release: what a tag produces and where install.sh lives #13 — Release shape. A tag publishes three assets and two multi-arch images; install.sh stays on raw and is not an asset; the version resets to 0.1.0 and the 102 inherited local tags are deleted; one Docker Hub PAT serves both jobs, and a missing credential fails on actana/control while still skipping on forks. Unblocked the supply-chain item. (decision)
Tests: the minimum set that still gives confidence #14 — Test set.26 PR jobs → 13, ~5 min wall clock and ~89 → ~17 billed minute-equivalents; five checks deleted, two installer e2es merged into one, the Harness-install e2e demoted to a weekly vendor canary, dependency-audit gated on --prod. Diagnosis correction: nothing in CI is slow — 72% of the bill was three macOS legs. (decision)
The handoff spec itself.Closed — ADR 0016, at docs/adr/0016-the-0-1-0-shape.md. An ADR rather than a docs/ page because it is permanent, diffable and amended by PR; the tracker keeps the evidence and the clauses link out to it. Structure: locked clauses in eight groups, the contradictions table, the closed-open-items table, what is genuinely still open, and sequencing.
Rootless / non-root posture for the Panel image.Closed (D21, D22) — the :nonroot tag is not forced: every distroless variant already ships nonroot:x:65532:65532, so numeric USER 65532:65532 on the default tag drops privilege and keeps the posture stated in our Dockerfile. Numeric is load-bearing — Kubernetes' runAsNonRoot cannot resolve a username. /data ownership comes from COPY --chown, never a staged chown, which is silently discarded.
Supply-chain posture.Closed as out of scope for 0.1.0, with Supply chain: sign the release, publish an SBOM and provenance #24 filed and both options priced (D29, §H). Integrity is SHA256SUMS over the same channel as the tarball. GitHub provenance attestations cost ~+30s/job but install.sh cannot verify them; full cosign + SBOM costs ~+2–3 min and takes assets from 3 to 7 — which is itself a breaking change to the installer contract. The deferral is cheap precisely because that contract is three names, so a signature is a fourth asset.
Fate of packages/shared.Closed (D3) — it survives as a third package. It holds the Panel↔Core wire contract, so folding it either way would make one image depend on the other and break the two-image split. Fix the asymmetry while renaming: Core imports it by relative path in 45 places today.
Upgrade path for existing deployments.Closed, and it is empty (§H). Zero published releases and no Core tarball ever published anywhere, so there is no pinned download URL to break and no Panel holding paired Core records from a published build. The only migration is developer machines: actana setup removes a legacy actana-harness.service, or a re-run leaves two daemons on one socket (actana setup removes a legacy actana-harness.service #36, ~5 lines). Paths, schema and pairing are untouched — re-pairing is not required.
Dependency-audit policy.Closed (D37) — three populations, three owners: shipped npm packages via pnpm audit --prod --audit-level high on PR; the dev tree weekly, opening an issue; the OS layer and shipped node_modules via Trivy on the image build, failing on fixable CRITICAL/HIGH only. pnpm audit cannot see the base image and Trivy has no dev/prod notion, so neither substitutes for the other. The allowlist is one .trivyignore entry for linux-libc-dev with its justification beside it, and nothing else is suppressed anywhere.
Out of scope
Panel wired to multiple Cores (original item 4) — already implemented.CoreListResponse { cores }, coreId threaded through panel-link/core-link, packages/panel/src/server/__tests__/cores-api.test.ts, per-core fleet view. Verified during charting; no decision to make.
Windows Core support. Windows operators run the Panel and host Cores on Linux (or WSL, which is linux-*).
Product, UI and visual design work (PRODUCT.md, designs/). This effort is distribution, CI, security and docs.
Electron / desktop remnants (dist-electron*, dist-electron-out/, TERMINAL_FOCUS_BUG.md, shift-enter-bug-notes.md). Pre-existing cruft from the pre-Panel era; a separate cleanup, not a decision on this route.
Destination
A locked set of decisions — a handoff spec an implementation effort can start from with nothing left to argue about — defining a radically simpler Actana Control:
curl … | shturns a Linux machine into a Core. No Panel outside Docker.main. No direct commits, by anyone.This map produces decisions, not code. Prototypes are throwaway evidence; the deliverable is written answers.
Notes
Domain. Actana Control is a self-hosted control plane for agentic coding. A Panel (web service, one deployment) drives N Cores (machines running a daemon that spawns PTY sessions running Claude Code / Codex / Cursor CLI against git repos). A machine becomes a Core via the installer; the Panel pairs with it over
core-link.Skills every session should consult.
/grillingand/domain-modelingby default;/researchfor AFK research tickets;/prototypefor prototype tickets.Tracker gotcha — read this before any
ghcall. No git remote in this clone points atactana/control.originisAgentSystemLabs/mission-control(the read-only upstream fork parent), so baregh issue …targets the wrong repo. Always pass--repo actana/control.Frame locked during charting — settled, do not re-litigate:
ci/release/housekeeping.docs/. Root keeps README + the community files GitHub expects; everything else moves underdocs/organised by what the reader is trying to do.mainis protected with no bypass actors — PR + green CI required, binding on admins. Every implementation session after this map must work through PRs..scratch/,docs/specs/,docs/tickets/get deleted;git history is the archive. Anything load-bearing is promoted first.Standing tension to respect. Distroless has no shell — fine for the Panel, fatal for a Core, whose whole job is spawning shells. Panel and Core will not share a base image.
Handed off. The locked decisions are ADR 0016 — The 0.1.0 shape. The work they imply is #23 — Build the 0.1.0 shape: 32 tickets in 5 phases, plus 6 filed-but-unscheduled. This map keeps only #16 and #17.
Decisions so far
build-essential→linux-libc-devis 1200 of 1328 OS findings, so the package set is the high-leverage decision, not the base. (decision)ubuntu:24.04, digest-pinned.apt-get upgradein the same layer, native-addon toolchain in with its ~1200linux-libc-devfindings suppressed and justified, Harnesses not baked, Node 24 from the nodejs.org tarball sha256-verified, weekly rebuild gated on fixable CRITICAL/HIGH — 1248 → ~38 distinct CVEs, 751 → ~190 MB. Overturns frame item 2's baked-CLIs half. (decision)actana setupbypassed entirely),tinias PID 1, one required env var (ACTANA_PUBLIC_HOST), first-run identity mint persisted into a single/home/corevolume, host changes re-issue the server cert from the kept CA instead of re-minting,deploy/dev/deleted, one reference compose with no TLS terminator. (decision)USER 65532:65532on the default tag) and found three failure modes that fail quietly — the discarded stagedchownon/databeing the one that would ship a broken deployment. (decision)postcssand@babel/corereach the runtime image through the Panel's production graph, so the advisories are real. Fix is a lockfile-only upgrade. Also found a whole build toolchain shipping as dead weight — filed as @tanstack/react-start ships a build toolchain the runtime never imports #25. (decision)Harness→ Core; the agentic CLI → Harness; "Agent" retired. 233 files, one sweep. Compatibility question is empty — zero published releases, so no shim and no deprecation window. Resolves "fate ofpackages/shared": it survives as a third package, because it holds the Panel↔Core wire contract. (decision)install.shstays onrawand is not an asset; the version resets to0.1.0and the 102 inherited local tags are deleted; one Docker Hub PAT serves both jobs, and a missing credential fails onactana/controlwhile still skipping on forks. Unblocked the supply-chain item. (decision)dependency-auditgated on--prod. Diagnosis correction: nothing in CI is slow — 72% of the bill was three macOS legs. (decision)docs/grouped by reader task, a 58-line README, a disposition for every existing file, and it caught the ADR 0013 collision between Harness to Core: rename scope and the migration it forces #12 and Triage .scratch, docs/specs, docs/tickets before deleting them #17 — resolved 0013/0014/0015. Follow-up closed theCONTEXT.mddiscovery risk with a 3-line rootCONTEXT-MAP.md, because the skill would otherwise silently recreate a competing glossary. (decision, follow-up)Cross-check. #8, #9 and #15 landed blind to each other — seven contradictions and three gaps, all resolved in ADR 0016 §"Contradictions and gaps".
Not yet specified
All six closed. The record is ADR 0016 — The 0.1.0 shape; §H is the item-by-item table.
The handoff spec itself.Closed — ADR 0016, atdocs/adr/0016-the-0-1-0-shape.md. An ADR rather than adocs/page because it is permanent, diffable and amended by PR; the tracker keeps the evidence and the clauses link out to it. Structure: locked clauses in eight groups, the contradictions table, the closed-open-items table, what is genuinely still open, and sequencing.Rootless / non-root posture for the Panel image.Closed (D21, D22) — the:nonroottag is not forced: every distroless variant already shipsnonroot:x:65532:65532, so numericUSER 65532:65532on the default tag drops privilege and keeps the posture stated in our Dockerfile. Numeric is load-bearing — Kubernetes'runAsNonRootcannot resolve a username./dataownership comes fromCOPY --chown, never a stagedchown, which is silently discarded.Supply-chain posture.Closed as out of scope for 0.1.0, with Supply chain: sign the release, publish an SBOM and provenance #24 filed and both options priced (D29, §H). Integrity isSHA256SUMSover the same channel as the tarball. GitHub provenance attestations cost ~+30s/job butinstall.shcannot verify them; full cosign + SBOM costs ~+2–3 min and takes assets from 3 to 7 — which is itself a breaking change to the installer contract. The deferral is cheap precisely because that contract is three names, so a signature is a fourth asset.Fate ofClosed (D3) — it survives as a third package. It holds the Panel↔Core wire contract, so folding it either way would make one image depend on the other and break the two-image split. Fix the asymmetry while renaming: Core imports it by relative path in 45 places today.packages/shared.Upgrade path for existing deployments.Closed, and it is empty (§H). Zero published releases and no Core tarball ever published anywhere, so there is no pinned download URL to break and no Panel holding paired Core records from a published build. The only migration is developer machines:actana setupremoves a legacyactana-harness.service, or a re-run leaves two daemons on one socket (actana setup removes a legacy actana-harness.service #36, ~5 lines). Paths, schema and pairing are untouched — re-pairing is not required.Dependency-audit policy.Closed (D37) — three populations, three owners: shipped npm packages viapnpm audit --prod --audit-level highon PR; the dev tree weekly, opening an issue; the OS layer and shippednode_modulesvia Trivy on the image build, failing on fixable CRITICAL/HIGH only.pnpm auditcannot see the base image and Trivy has no dev/prod notion, so neither substitutes for the other. The allowlist is one.trivyignoreentry forlinux-libc-devwith its justification beside it, and nothing else is suppressed anywhere.Out of scope
CoreListResponse { cores },coreIdthreaded throughpanel-link/core-link,packages/panel/src/server/__tests__/cores-api.test.ts, per-core fleet view. Verified during charting; no decision to make.linux-*).PRODUCT.md,designs/). This effort is distribution, CI, security and docs.dist-electron*,dist-electron-out/,TERMINAL_FOCUS_BUG.md,shift-enter-bug-notes.md). Pre-existing cruft from the pre-Panel era; a separate cleanup, not a decision on this route.