{
  "markdown": "<p align=\"center\">\n  <img src=\".github/README/banner.png\" alt=\"Banner\" />\n</p>\n\n#### harnessed — Composable harness stacks (Claude Code / omp / opencode / antigravity / codex + an MCP hub + optional shared services)\n\n> [!WARNING]\n> **⚠️ ALPHA SOFTWARE — not production-ready.** harnessed is under active development and the field\n> of agentic AI security is very young. Expect breaking changes, rough edges, and incomplete\n> features. **Use at your own risk.**\n>\n> **Container runtimes — all WIP:**\n>\n> | Runtime | Status | Notes |\n> | --- | --- | --- |\n> | **podman** (rootless) | 🧪 **in testing** | The reference runtime (pods). Most complete path — verify your host with `HARNESSED_PODMAN=1 uv run pytest tests/test_recipes_integration.py`. |\n> | **Docker** | ⏳ **pending** | A shared-network-namespace path exists (`--network container:`) but is **not yet verified**. Egress firewall needs rootless `NET_ADMIN` (best-effort — `--no-firewall` to skip). Shared **service sidecars** are not wired (no `host.containers.internal`). |\n> | **Apple `container`** | ⏳ **pending** | Tracked follow-up. One VM/IP per container, no shared netns — needs a different networking story. |\n>\n> Runtime differences (pod vs shared-netns) are handled inside the host CLI (`src/harnessed/launcher.py`). See [troubleshooting](docs/guides/troubleshooting.md).\n\nYou can read my [announcement here](https://mikesshinyobjects.tech/posts/2026/2026-03-20-code-container-isolating-ai-harnesses/)\n\n> Forked from [kevinMEH/code-container](https://github.com/kevinMEH/code-container) and extended for rootless Podman, hardware authentication (YubiKey, 1Password), Claude Code auth, composable harness stacks, and alternative AI providers.\n\n---\n\n`harnessed` is **one executable** that launches **composable harness stacks** — each a\npodman pod running an AI coding harness (`claude`, `omp`, `opencode`, `antigravity`, or `codex`) plus\nan MCP hub (hatago) plus optional\nshared services (hindsight, openbrain, …). You compose a named stack (one harness + chosen recipes)\nand launch an authenticated instance that exposes **exactly** the skills/commands/MCP/\nservices it declares — nothing from the host config — reproducibly, with **podman as the only host\ndependency**.\n\nIt is for developers who want to compose and trial harness configurations — different\nskill/plugin/MCP/memory combinations — in clean, reproducible, throwaway-or-persistent environments\nwithout dragging every host default into the container or polluting `~`.\n\n> The full architecture and design rationale live in **[docs/harnessed-design.md](docs/harnessed-design.md)**\n> (the *why*). This README is the *how*: install, build, and run.\n\n## Isolated mode\n\nEvery stack runs in **isolated mode**: auth seeded from host credentials, config layer (skills/commands/hooks/MCP) sourced exclusively from an assembled stack profile — **nothing from host config** leaks in. The harness container + hatago MCP hub run as a podman pod. See the [design rationale](docs/harnessed-design.md).\n\n## Install\n\n`harnessed` is a **host Python CLI** (Python ≥ 3.12) that drives podman directly — there is no tool\ncontainer. You need two host dependencies: **podman** (rootless; the reference runtime — Docker\nsupport is pending, see the runtime table above) and **[uv](https://docs.astral.sh/uv/)** (or pipx)\nto install the CLI.\n\n**Recommended — the checked-in installer.** [`install.sh`](install.sh) detects podman + uv (it never\ninstalls podman for you — that is privileged and distro-specific), then installs the CLI from a\n**pinned git tag**:\n\n```bash\ngit clone https://github.com/drmikecrowe/harnessed.git\ncd harnessed && ./install.sh          # --install-uv to also install uv; --uninstall to remove\n```\n\nNo-clone path — download, **review**, then run (the script is auditable; read it before you trust it):\n\n```bash\ncurl -fsSL https://raw.githubusercontent.com/drmikecrowe/harnessed/v0.1.0/install.sh -o install.sh\nless install.sh && sh install.sh\n# fast-and-loose one-liner (pinned to a tag, never a branch — we recommend reading it first):\n#   curl -fsSL https://raw.githubusercontent.com/drmikecrowe/harnessed/v0.1.0/install.sh | sh\n```\n\n> The `v0.1.0` tag above is a placeholder — **no release tag has been cut yet**. Until one exists,\n> use the manual step below or run `install.sh` against a local checkout.\n\n**Manual** (what the installer automates):\n\n```bash\ngit clone https://github.com/drmikecrowe/harnessed.git\nuv tool install ./harnessed          # or: pipx install ./harnessed\n```\n\nThis puts `harnessed` in `~/.local/bin`. Make sure it is on your PATH; `uv tool update-shell` sets it up.\nTo uninstall: `./install.sh --uninstall` (or `uv tool uninstall harnessed` / `pipx uninstall harnessed`).\nYour `~/.config/harnessed` and `$XDG_DATA_HOME/harnessed` data are preserved.\n\n> **Working on the CLI itself?** Use an editable dev env instead — see [CONTRIBUTING.md](CONTRIBUTING.md)\n> (`uv sync --extra dev` + `export PATH=\"$PWD/.venv/bin:$PATH\"`).\n>\n> **Linux** — tested on Manjaro. It should work on any systemd distro, but this is not tested. macOS/WSL: untested.\n\n## First-run build\n\nImages are built on the host with `podman build` the first time they are needed:\n\n- **`harnessed-base`**: fat toolchain image (mise, node@24, python, pnpm; no harness CLI).\n- **`harnessed-<harness>-<stack>`**: the derived stack image, built by `harnessed build <stack> <harness>` — FROM `harnessed-base`, then the stack's recipe Dockerfiles concatenated, then **the agent CLI installed last**, then the supply-chain scan.\n- **`harnessed-<agent>`**: FROM `harnessed-base` + the agent CLI (`harnessed-claude`, `harnessed-omp`, …). No longer the derived image's parent — it is the fallback image a run uses for a stack with no derived image yet.\n\n**Why the agent installs last.** Agent CLI pins churn far faster than recipes. When the agent image was the derived image's `FROM` parent, every agent bump changed the parent's id and so invalidated *every recipe layer of every stack* on that harness. With the agent on top, an agent bump rebuilds only the agent layer and the scan — the expensive recipe layers stay cached. It also makes the recipe layers harness-independent (they hang off `harnessed-base` with identical instructions), so a stack declaring `harnesses: [claude, omp]` builds its recipe layers once and both harnesses share them.\n\nA recipe that branches on `${HARNESS}` in a `RUN` necessarily splits that cache from its own layer onward, so keep such recipes late in a stack's `recipes:` list.\n\nSupporting image (not part of the base→agent→stack lineage):\n\n- **`hatago`** — the MCP hub (aggregates a stack's MCP servers behind one HTTP endpoint; light `pnpm dlx`/`uvx` servers baked in).\n\nAssembly runs **host-native in-process** (no tool container) — the host CLI emits the profile and\nthe `Dockerfile.harnessed-<stack>`, then drives `podman build`.\n\n```bash\nharnessed build                    # rebuild the shared images, then reconcile every stale stack\nharnessed build -j1                # ... one stack at a time (default: half the cores, capped at 4)\nharnessed build <stack>            # build every harness in the stack's `harnesses:` list\nharnessed build <stack> <harness>  # assemble one stack for a harness: emit profile + build images (+ supply-chain scan)\n```\n\nA bare `harnessed build` rebuilds the shared images once, then builds every stale stack\n**concurrently** (`--jobs`/`-j`). Each build's output is prefixed with its own coloured\n`stack(harness)` tag so the interleaved podman logs stay readable, and one stack failing does not\ncancel the others — the failures are reported together at the end.\n\nA stack may declare which harnesses it is built for:\n\n```yaml\nname: my-stack\nrecipes: [superpowers, serena]\nharnesses: [claude, omp]   # build-time only — the harness is still a run-time argument\n```\n\n`harnessed build <stack>` then fans out to every name in that list, and bare `harnessed build`\nincludes those `<stack> <harness>` pairs in its sweep — so a freshly-authored stack is provisioned\nwithout naming it. A stack that declares no `harnesses:` is unchanged: `build <stack>` still\nrequires an explicit harness, and a bare `build` only reconciles it once it has been built at\nleast once.\n\nBare `harnessed build` rebuilds the shared base/agent/hatago images (and reconciles every declared\nor already-built `<stack> <harness>` pair). `harnessed build <stack> <harness>` rebuilds the base (so\nbase-image changes propagate), assembles in-process, then builds the hatago, agent, and derived\n`harnessed-<harness>-<stack>` images. The derived image's final layer runs an **in-image,\nadvisory** supply-chain scan over what actually landed — emitting the profile to\n`$XDG_DATA_HOME/harnessed/profiles/<stack>/<harness>/` (the clone stays immutable source) plus an advisory\n`scan-report.json` alongside it. Expect first-run latency (images build via host `podman build`);\nlater runs are cache hits.\n\n## Quickstart\n\nBuild and launch the `time` sample stack — the `time` recipe (one light stdio MCP server + one standalone skill) — on the `claude` harness:\n\n```bash\ncd /path/to/project\nharnessed build time claude && harnessed time claude\n```\n\n`time` is the smallest end-to-end stack slice: the `time` recipe (one light stdio MCP server + one\nstandalone skill), composed into a profile and run as a pod (agent + hatago). The harness is chosen\nat run time (`harnessed time omp` runs the same stack on omp). Running an unbuilt stack errors and\ntells you to `harnessed build` it first.\n\nAfter building, verify the stack's declared capabilities with the capability test:\n\n```bash\nharnessed test time claude\n```\n\n`harnessed test` launches the stack headless, runs the two-oracle capability check, and writes a per-capability report to `$XDG_DATA_HOME/harnessed/profiles/time/claude/capability-report.md` (✓ connected / ✗ missing).\n\n## Command surface\n\n| Command | What it does |\n| --- | --- |\n| `harnessed container-run <harness> [path] [--stack <name> \\| --recipe <name>…] [--fresh]` | Isolated stack on a harness: assembled profile + pod (harness + hatago). `--stack` and `--recipe` (repeatable) are mutually exclusive; with neither, runs the `default` baseline |\n| `harnessed host-run <claude \\| omp> [path] [--stack <name> \\| --recipe <name>…]` | Same stack, host-native — no podman, no container; config isolated per stack, credentials from host. Only these two harnesses: the backend needs an env var whose value *is* a per-stack config dir (`CLAUDE_CONFIG_DIR`, `PI_CODING_AGENT_DIR`) — see [BACKENDS.md](BACKENDS.md) |\n| `harnessed build [<stack> [<harness>]]` | Build the base/harness/hatago images (+ reconcile declared/built pairs), or assemble + build a stack — for one harness, or for every harness in its `harnesses:` list |\n| `harnessed test <stack> <harness>` | Capability test: launch `--fresh` headless + assert declared capabilities (markdown report) |\n| `harnessed svc up \\| down \\| recreate \\| sync \\| migrate <service>` | Manage service sidecars. `recreate` tears down and rebuilds — mounts and env are fixed at create time, so `podman restart` cannot pick up a change to how the container is built |\n| `harnessed list` | List authored stacks (with which harnesses are built) + running instances |\n| `harnessed stop \\| rm <stack> [<harness>]` | Stop / remove instances of a stack (optionally one harness) |\n| `harnessed new <stack> [--recipes a,b,c]` | Scaffold a harness-free stack manifest |\n| `harnessed install \\| uninstall <stack>` | Write / remove a `~/.local/bin/<stack>` launcher shim (forwards the harness arg) |\n| `harnessed rescan` | Re-scan installed harnessed images online (the nightly timer's trigger) |\n| `harnessed update [--check] [--yes] [--minimum-release-age MIN]` | Find outdated catalog pins and offer to bump them. `tools:` entries resolve against npm / PyPI / GitHub releases / mise; pins inside install scripts and Dockerfiles are reported as **unresolved** rather than skipped. Pins marked `hold` are never bumped. Modelled on pnpm's `minimumReleaseAge` (same unit — minutes, default 10080 = 7 days): a release younger than the window is not offered, and as in pnpm the newest version that *is* old enough is offered instead, naming the newer one it passed over. `--check` is the CI mode: non-zero exit on a stale pin, writes nothing |\n| `harnessed --fresh ...` | Tear down any existing pod/instance first (isolated) |\n| `harnessed --no-firewall ...` | Skip the egress firewall for this run |\n| `harnessed -h \\| --help` | Show help |\n\nRun `harnessed --help` for the full surface. Scanner tokens (e.g. `SNYK_TOKEN`) are read from the\nenvironment — there is no `harnessed auth` command (see [Supply chain & security](#supply-chain--security)).\n\n## Guides\n\n- **[Recipe authoring](docs/guides/recipe-authoring.md)** — writing `recipes/<name>/recipe.yaml` (MCP layer + skills/commands), with worked examples.\n- **[Stacks](docs/guides/stacks.md)** — composing recipes into `stacks/<name>/stack.yaml`, scaffolding, and the build/run/test lifecycle.\n- **[Service authoring](docs/guides/service-authoring.md)** — adding a shared sidecar under `services/` (image + manifest + server).\n- **[Secrets setup](docs/guides/secrets.md)** — opt-in varlock + 1Password (env-only, never baked).\n- **[AWS SSO](docs/guides/aws-sso.md)** — opt-in AWS credentials via the aws-sso ECS server (short-lived STS, env-only, never baked).\n- **[Egress & exposing services](docs/guides/egress.md)** — the container egress firewall; recipe-declared `egress:` hosts + pinned `tools:` to conditionally open a service (e.g. Pulumi Cloud).\n- **[Troubleshooting](docs/guides/troubleshooting.md)** — podman setup, first-run build, `--fresh`, host-persisted sessions, the nightly re-scan timer.\n- **[Architecture & design](docs/harnessed-design.md)** — the *why* behind every decision.\n- **[Roadmap](ROADMAP.md)** — where harnessed is going, at the epic level: backends, secrets, harness parity, authoring, verification.\n\n## Recipe roadmap\n\n> Catalog only — which **third-party** recipes are shipped or wanted. For where the *product* is\n> going, see **[ROADMAP.md](ROADMAP.md)**.\n\n**The rule, so this list cannot rot silently:** shipped means a `recipe.yaml` under\n`catalog/recipes/`; planned means it is still in `catalog/recipes.backlog/`. That directory listing\nis the authority — `harnessed list` reports **stacks and instances, not recipes**, so it cannot\nanswer this question. First-party recipes (`default`, `mikes-universal-setup`, `gh-issue-tracker`)\nand the tracer/development slices used to exercise the assembly pipeline and capability test\n(`greet`, `ping`, `time`, `floating-recipe`) are out of this list's scope by design.\n\n**Shipped**\n\n- [x] **[gstack](https://github.com/garrytan/gstack)** — Garry Tan's skill suite (browser automation, design, PDF, …), installed via its upstream `./setup` · *skills recipe*\n- [x] **[serena](https://github.com/oraios/serena)** — semantic code intelligence MCP (LSP-backed retrieval/editing, 40+ languages) · *MCP recipe*\n- [x] **[codebase-memory-mcp](https://github.com/DeusData/codebase-memory-mcp)** — codebase knowledge graph (158 languages, C binary) · *MCP recipe*\n- [x] **[context-mode](https://github.com/mksglu/context-mode)** — context-window optimization / tool-output sandbox · *MCP + hooks recipe*\n- [x] **[tokensave](https://github.com/aovestdipaperino/tokensave)** — pre-indexed semantic code knowledge graph (80+ MCP tools, Rust) · *MCP recipe*\n- [x] **[agentmemory](https://github.com/rohitg00/agentmemory)** — persistent memory server (53 MCP tools, HTTP :3111) · *service + recipe*\n- [x] **[openwiki](https://github.com/langchain-ai/openwiki)** — agent-written repository wiki with grounded, drift-checkable Claims · *MCP + skill recipe*\n- [x] **[repowise](https://github.com/repowise-dev/repowise)** — repository analysis / retrieval · *MCP recipe*\n- [x] **[rtk](https://github.com/rtk-ai/rtk)** — Rust Token Killer: CLI proxy compressing dev-command output · *CLI + hooks recipe*\n- [x] **[superpowers](https://github.com/obra/superpowers)** — software-development methodology skill suite (TDD, code review, subagent-driven dev) · *skills recipe*\n- [x] **[gsd-core](https://github.com/open-gsd/gsd-core)** — GSD methodology skills · *skills recipe*\n- [x] **[solidspec](https://github.com/jyjeanne/solidspec)** — multi-methodology spec-driven development CLI · *skills recipe*\n- [x] **[caveman](https://github.com/JuliusBrussee/caveman)** — concise-output / token-compression skill · *skills recipe*\n- [x] **[ccstatusline](https://www.npmjs.com/package/ccstatusline)** — status-line renderer the harness invokes for `statusLine` · *CLI recipe*\n- [x] **[pulumi](https://github.com/pulumi/pulumi)** — the Pulumi CLI plus its egress allowlist, as pure YAML · *tools + egress recipe*\n- [x] **OB1 / Open Brain** — shared persistent memory + vector search across AI tools · *service + recipe*, shipped as the `openbrain-example` **template**: copy it to your overlay catalog and set `OB1_URL`, since the recipe carries no upstream endpoint of its own\n\n**Planned** — still under `catalog/recipes.backlog/`; packages classified in\n[docs/research/recipe-stress-test.md](docs/research/recipe-stress-test.md)\n(repos, install commands, data models, and architecture gaps each one surfaces):\n\n- [ ] **[headroom](https://github.com/headroomlabs-ai/headroom)** — context/tool-output compression before it reaches the LLM · *MCP recipe*\n- [ ] **[gbrain](https://github.com/garrytan/gbrain)** — knowledge brain (synthesis, graph traversal, gap analysis) · *service + recipe*\n- [ ] **[hindsight](https://hindsight.vectorize.io)** — memory/recall sidecar (multi-container Postgres stack) · *existing service*\n\n## Supply chain & security\n\n- **pnpm everywhere** — every JavaScript install (global, per-recipe, hatago's bundled servers) uses **pnpm**, never `npm`/`npx`. `pnpm dlx` replaces `npx`. A managed supply-chain config applies `minimumReleaseAge` cooldowns and lifecycle-script default-deny. Recipe validation flags raw `npm`/`npx` and points at the pnpm equivalent ([design rationale](docs/harnessed-design.md)).\n- **In-image supply-chain scan (advisory)** — the derived image's final layer runs **snyk** (over mise node globals + recipe installs, via a synthesized manifest; token-gated by a build *secret*, warn-skips without one), plus credential-free **osv-scanner** (recipe lockfiles) and **pip-audit** (the Python env). It **reports** a compact severity summary and writes `scan-report.json` — it does **not** fail the build. Rationale: harnessed installs third-party agent tooling whose dependency trees always carry open advisories. A hard gate blocks every build on code you do not control. Visibility is the deliverable ([design rationale](docs/harnessed-design.md)).\n- **Opt-in secrets** — varlock + 1Password resolve `op://` refs as **env only** (never a profile, image layer, or repo file) — into the pod for `container-run`, into the agent process for `host-run`. Copy `.env.schema.example` to `~/.config/harnessed/.env.schema` to turn it on. See **[docs/guides/secrets.md](docs/guides/secrets.md)**.\n- **Nightly re-scan** — a systemd user timer re-runs osv-scanner **online** against installed images so a CVE disclosed *after* build still surfaces. See **[troubleshooting](docs/guides/troubleshooting.md#nightly-re-scan-timer-sec-04)** for setup (including the `loginctl enable-linger` prerequisite).\n- **Secrets/auth referenced, never baked** — Claude OAuth, scanner tokens, and 1Password secrets reach the instance as env or read-only mounts; never an image layer.\n\n> All examples in this repo use placeholder values only (`op(op://Private/Snyk/credential)`, dummy\n> tokens) — never real credentials.\n\n## How harnessed is built (in practice)\n\n- **A/B two memory systems.** Run `claude+hindsight` and `claude+openbrain` as separate stacks side by side; neither touches your host config or the other's state.\n- **Compare harnesses on equal footing.** Point `claude+hindsight` and `omp+hindsight` at the **same** service-scoped memory volume and judge which harness drives it better — same data, different engine.\n- **Clean-room a flaky plugin.** `harnessed container-run <harness> --stack <name> --fresh` reproduces from zero state, then tears down leaving no residue in `~`.\n- **Proof it built right.** Each stack ships a capability test: bring the instance up headless and assert it exposes exactly the MCP servers/skills/commands its manifest declares — rendered as a per-capability markdown report (✓ connected / ✗ missing).\n\n---\n\n### Which container solution is right for you?\n\nThree projects solve adjacent problems — pick the one that matches your threat model and workflow:\n\n|                      | This project                                         | [NVIDIA OpenShell](https://github.com/NVIDIA/OpenShell)                  | [Anthropic devcontainer](https://github.com/anthropics/anthropic-quickstarts/tree/main/computer-use-demo) | [Trail of Bits](https://github.com/trailofbits/claude-code-devcontainer) |\n| -------------------- | ---------------------------------------------------- | ------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ |\n| **Primary use case** | Power-user daily driver across multiple AI harnesses | Enterprise sandboxing with policy enforcement                            | VS Code team dev environments                                                                             | Security auditing of untrusted code                                      |\n| **Auth model**       | Seamless — host credentials shared into container    | Credential providers inject keys; never exposed in sandbox               | Per-container setup                                                                                       | Fully isolated                                                           |\n| **Threat model**     | Contain the AI, not the repo                         | Full defense-in-depth (filesystem, network, process, inference)          | Consistent team environments                                                                              | Malicious repos / adversarial input                                      |\n| **Runtime**          | Podman (rootless); Docker pending                    | K3s (Kubernetes) inside Docker                                           | Docker / Dev Containers spec                                                                              | Docker                                                                   |\n| **AI harnesses**     | Claude, omp (via bridge); more planned   | Claude, OpenCode, Codex, Copilot                                           | Claude                                                                                                    | Claude                                                                   |\n\n**Use this project** if you want composable experimentation across skill/MCP/memory combinations,\nwithout the friction of re-authentication or tool switching every session.\n\n**Use [NVIDIA OpenShell](https://github.com/NVIDIA/OpenShell)** if you need enterprise-grade sandboxing with declarative security policies, a privacy-aware LLM proxy, and Kubernetes orchestration for multi-agent environments.\n\n**Use [Trail of Bits' devcontainer](https://github.com/trailofbits/claude-code-devcontainer)** if you are doing security audits or reviewing untrusted repos — their threat model explicitly accounts for malicious code trying to escape the container.\n\n**Use Anthropic's official devcontainer** if you are on a team that wants a standardised, VS Code-integrated development environment with Claude Code.\n",
  "bytes": 23865,
  "sha": "dadf9f1040a1b8b66e75349e8360b347b42f44e23f14621fc097cba27d39f97b",
  "repo_slug": "drmikecrowe/harnessed",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/okf_drmikecrowe_harnessed_openwiki_index_md_04fe7ebd/readme"
}