Composable, on-demand configuration for agentic harnesses: managed stacks, isolation, controlled upgrades, and security scanning in a repeatable host or container environment
Warning
Alpha software. harnessed is under active development and the field of agentic AI security
is very young. Expect breaking changes. Rootless podman is the reference runtime and the only one
in testing; Docker and Apple container are pending. The runtime table and its caveats are in
troubleshooting.
harnessed keeps every Claude Code (or omp) configuration separate, and lets me compose one per
project or per task. Nothing it installs lands in ~/.claude.
I built it because plugin suites install globally. Try superpowers for one project and it is in every project. Add a Jira MCP server for a client and it is running while I work on a hobby repo. I also have a set of rules and skills I want everywhere, and I want them there without copying them into every repo. harnessed gives me a baseline stack, a stack per client, and a way to switch one tool on for an afternoon and off again.
I don't type claude anymore. I launch a stack.
You can read the announcement post. Forked from kevinMEH/code-container and rebuilt around rootless podman, composable stacks, and more than one harness.
- A recipe is one thing with its install pinned: a skill suite, a rule set, an MCP server, or a
CLI tool.
rtk,serena,pulumi, andsuperpowersare recipes. - A stack is a named list of recipes. Mine is
[default, ccstatusline, local, mikes-universal-setup, context-mode, rtk, openbrain, old-coder]. - A harness is the CLI that runs the stack:
claudeoromptoday. You pick it at launch. It is never part of the stack.
Launching a stack assembles its recipes into one profile, points the harness at that profile and nothing else, and puts every MCP server behind one hub. The rest of the vocabulary (agent, service, catalog, overlay, profile) is in ARCHITECTURE.md. You don't need it to start.
A stack per project. My default stack runs everything personal. My isolated stack is the
same baseline minus my memory server, plus Jira and Confluence, and it authenticates with the
client's Claude account instead of mine. Both run at the same time in two terminals, and neither
one can see the other's config.
harnessed container-run claude --stack default ~/Programming/Personal/blog
harnessed container-run claude --stack isolated ~/Programming/Clients/acmeA recipe for this session only. I don't want the Pulumi CLI, its egress hosts, and my
~/.pulumi login mounted into every session. I want them while I'm doing deployments.
harnessed container-run claude --recipe pulumiThat runs the default baseline plus pulumi. Nothing is written to a stack file, and the next
launch without the flag has no Pulumi in it.
You can already put skills and an .mcp.json in a repo, and for a lot of people that is enough.
What that doesn't give you is the plugin that insists on installing globally, the rules you want in
every repo without committing them to every repo, or a second identity for client work.
Start in host mode. It needs no podman, and it proves the composition part in a few minutes. The container boundary comes last, once you have a stack you like. The longer version, with what each step writes where, is the getting started guide.
1. Install. harnessed is a host Python CLI (Python 3.12 or newer). install.sh checks for uv
(and podman, which it never installs for you), then installs the CLI.
git clone https://github.com/drmikecrowe/harnessed.git
cd harnessed && ./install.sh # --install-uv to also install uv; --uninstall to removeOr by hand: uv tool install ./harnessed (or pipx install ./harnessed). Either way the binary
lands in ~/.local/bin. Working on the CLI itself? Use the editable env in
CONTRIBUTING.md instead.
2. Run the baseline. The shipped default stack is one recipe: the skill that helps you author
more recipes.
cd /path/to/project
harnessed host-run claudeClaude starts with a config dir that holds exactly that profile. Your ~/.claude is untouched.
3. Add a recipe for this session.
harnessed host-run claude --recipe superpowersNow the superpowers skills are loaded. Exit, launch without the flag, and they are gone. Nothing was installed into your host config.
4. Save it as a stack. Author the manifest in your overlay catalog. Three lines is a stack.
# ~/.config/harnessed/catalog/stacks/mine/stack.yaml
name: mine
recipes: [default, superpowers, rtk]harnessed host-run claude --stack mine5. Add the container boundary. Build the image once, then launch the same stack as a podman pod
with the egress firewall on. harnessed install writes a ~/.local/bin/mine shim so the launch
is one word plus the harness.
harnessed build mine claude
harnessed container-run claude --stack mine
harnessed install mine && mine claudeThe first build is slow (it builds the base toolchain image). Later builds are cache hits.
harnessed test mine claude launches the stack headless and asserts it exposes exactly the skills
and MCP servers it declares.
| Command | What it does |
|---|---|
harnessed host-run <claude | omp> [path] [--stack <name> | --recipe <name>…] |
Host-native: no podman. Config isolated per stack, your real filesystem and credentials. Only harnesses whose config dir is an env var (CLAUDE_CONFIG_DIR, PI_CODING_AGENT_DIR); see BACKENDS.md |
harnessed container-run <harness> [path] [--stack <name> | --recipe <name>…] [--fresh] |
Isolated podman pod: harness + MCP hub + declared services, egress firewall on. --stack and --recipe are mutually exclusive; with neither, runs default |
harnessed build [<stack> [<harness>]] |
Build the shared images and reconcile every stale stack, or assemble and build one stack |
harnessed test <stack> <harness> |
Capability test: launch headless and assert the declared capabilities, written as a markdown report |
harnessed list |
Authored stacks (with which harnesses are built) and running instances |
harnessed stop | rm <stack> [<harness>] |
Stop or remove instances of a stack |
harnessed install | uninstall <stack> |
Write or remove the ~/.local/bin/<stack> launcher shim |
harnessed svc up | down | recreate | sync | migrate <service> |
Manage service sidecars |
harnessed update [--check] [--yes] |
Find outdated catalog pins and offer to bump them; --check is the CI mode |
harnessed --fresh … / --no-firewall … |
Tear down the existing pod first / skip the egress firewall for one run |
harnessed --help has the full surface.
Everything below lives on the wiki.
- Getting started: the five steps above, in more detail.
- Choosing recipes: what ships, what is planned, and which recipes overlap.
- Stacks: the
stack.yamlschema and the build, run, test lifecycle. - Recipe authoring: writing
recipe.yaml, with worked examples. - Service authoring: shared sidecars.
- Build and images: the base, agent, and stack image lineage, and why the agent installs last.
- Supply chain and security: pnpm everywhere, the advisory in-image scan, secrets as env only, the nightly re-scan.
- Secrets and AWS SSO: opt-in credentials, never baked.
- Egress: the firewall and how a recipe opens a host.
- Troubleshooting: podman setup, runtimes, first-run build,
--fresh. - Alternatives: how this compares to OpenShell, the Anthropic devcontainer, and Trail of Bits.
- Design rationale: the why behind every decision.
- Roadmap: where this is going, at the epic level.
- Recipes are harness-independent. There is no
harnesses:field on a recipe; a Dockerfile branches on${HARNESS}instead. - Every download is pinned.
@latestand--branch mainfail the build. - pnpm everywhere, never raw
npmornpx. - Credentials are referenced, never baked. Claude OAuth, scanner tokens, and 1Password secrets reach the instance as env or read-only mounts.
- All examples in this repo use placeholder values. Never real credentials.
