harness-ai is a devcontainer feature that assembles AI skills, agents, and hooks into the workspace at container startup. It reads content from one or two repositories, injects per-tool frontmatter, and writes output to tool-specific paths.
Nothing is vendored into the published feature. A single script, cli.sh, does the actual work — it's fetched at runtime (pinned to the feature version in the devcontainer, or from main for standalone use) and it clones this repo to get harness.py and the content it needs. The devcontainer and the curl | bash installer run the exact same file.
Configuration follows one rule: .harness-ai/config.yaml in your workspace wins. The devcontainer feature has no options at all — everything configurable lives in that one file.
Pick one:
Add the feature to devcontainer.json (Python must come first — see Prerequisites):
{
"features": {
"ghcr.io/devcontainers/features/python:1": { "version": "3.13" },
"ghcr.io/fabriziocafolla/harness-ai/harness-ai:0": {}
}
}Rebuild the container. On create, harnessai install runs automatically: it seeds .harness-ai/config.yaml, installs RTK/Headroom/openspec (on by default), and scaffolds .claude/ (skills, agents, hooks, statusline). Every later start re-syncs via harnessai sync if content changed. Nothing to run by hand.
In any workspace, no devcontainer needed:
curl -fsSL https://raw.githubusercontent.com/FabrizioCafolla/harness-ai/main/cli.sh | bashThat's install with the defaults: Claude only, RTK + Headroom + openspec on, wikictl off. It creates .claude/, .mcp.json, .gitignore entries, and a .harness-ai/config.yaml you can edit directly afterward — no need to remember flags, just change the YAML and re-run:
curl -fsSL https://raw.githubusercontent.com/FabrizioCafolla/harness-ai/main/cli.sh | bash -s -- syncWant OpenCode too, or wikictl on from the start? Pass flags on the first run instead of editing YAML after:
curl -fsSL https://raw.githubusercontent.com/FabrizioCafolla/harness-ai/main/cli.sh | bash -s -- install --tools claude,opencode --wikictlFull flag reference: CLI § Options. Full config reference: Configuration.
harness-ai requires bash and Python 3.9+ (which includes venv out of the box).
| Base | Supported | Notes |
|---|---|---|
| Debian / Ubuntu | Yes | All mcr.microsoft.com/devcontainers/* images |
| RHEL / Fedora / CentOS | Yes | bash and python3 available via dnf/yum |
| Alpine | No | No bash by default (busybox sh only) |
harness-ai does not install Python you must provide it via the base image or the devcontainer Python feature.
For devcontainers, add the Python feature before harness-ai:
{
"features": {
"ghcr.io/devcontainers/features/python:1": { "version": "3.13" },
"ghcr.io/fabriziocafolla/harness-ai/harness-ai:0": {}
}
}For CLI usage, ensure python3 >= 3.9 is in your PATH.
Only needed if you enable Headroom or wikictl (uv tool install under the hood). Without it, those installs are skipped with a warning; everything else works.
Only needed for install.openspec (on by default). Without it, the openspec install is skipped with a warning; everything else works.
Devcontainer:
install.shruns once at image build. It doesn't install anything — it just checks Python, reads the feature'sversion, and writes/usr/local/bin/harnessai, a small launcher with that version baked in as the pinned ref.postCreateCommand: harnessai installruns on first container create: fetchescli.shat the pinned ref, resolves.harness-ai/config.yaml, installs whatever's enabled (RTK/Headroom/wikictl/openspec), and runs the first scaffold.postStartCommand: harnessai syncruns on every later start: fetchescli.shagain, but only re-scaffolds if content actually changed (a cheapgit ls-remotehash check) — no binary reinstalls.- Both exit 0 on failure. Offline, GitHub down, whatever — container start is never blocked.
Standalone CLI: cli.sh is fetched via curl | bash, clones harness-ai at --ref (default main), resolves config the same way, and runs the scaffold. It's literally the same script the devcontainer uses.
Same feature block as Getting Started — no options. Every setting lives in .harness-ai/config.yaml (see Configuration).
Download once instead of piping on every invocation:
curl -fsSL https://raw.githubusercontent.com/FabrizioCafolla/harness-ai/main/cli.sh -o harness-ai.sh
bash harness-ai.sh [install|sync] [OPTIONS]
bash harness-ai.sh init-extension <path> [--name <str>]| Subcommand | Description |
|---|---|
install |
Resolve config, install enabled binaries (RTK/Headroom/wikictl/openspec/custom), run the scaffold. Default when omitted. |
sync |
Fast path: skip binary installs, hash-check before re-scaffolding. |
init-extension <path> |
Scaffold a minimal starter content-repo extension at <path> — see Extending harness-ai. |
Every CLI flag has an equivalent key in .harness-ai/config.yaml (see Configuration), which takes priority when present — flags only matter for a workspace's first run, before that file exists. Run bash harness-ai.sh -h for the full, current flag list rather than duplicating it here.
Requirements: git, python3 >= 3.9 with venv module (pyyaml is installed automatically in an isolated venv if missing).
bash harness-ai.sh install --interactivePrompts for each setting (tools, install toggles, caveman default, content repo). Flags passed before --interactive set the defaults shown in the prompts. .harness-ai/config.yaml, if present, still wins over whatever you answer.
.harness-ai/config.yaml in the target workspace is the single source of truth, shared by the devcontainer (which has no options of its own) and the CLI. It's created copy-once on the first harnessai install, seeded from the built-in defaults, and from then on any key it sets overrides the matching CLI flag.
version: 1
tools: [claude] # claude, opencode
install:
rtk: true
headroom: true
wikictl: false
openspec: true
# Arbitrary extra install commands (run as `bash -c "<command>"`, skipped
# by `sync`). Must be self-idempotent — no already-installed check is done.
custom:
speckit: 'uv tool install speckit-cli'
scaffold:
createFileMCP: true
createFileHooks: true
createFileSetting: true
updateGitignore: true
installDefaults: true
behavior:
caveman: true
contentRepo:
url: ''
ref: mainEdit it directly to change tools, toggle an install, flip a behavior default, or point at a content repo. No rebuild needed — just run harnessai install (or wait for the next harnessai sync).
.harness-ai/lock and .harness-ai/manifest.json live alongside config.yaml in the same directory — only config.yaml is tracked in git; the other two are harness-ai's own state and are gitignored.
- Statusline (Claude Code only —
.claude/statusline.sh+statusLinein thesettings.jsontemplate, scaffolded whenclaudeis intools): model, directory, git branch, context window % with color-coded bar, token counts, session cost (API billing only — hidden on Pro/Max plans whererate_limitsis present), lines added/removed, 5-hour rate limit, and token-saving tool indicators (⚡rtk/🪨caveman, green = active, dim = installed). The caveman indicator reads.harness-ai/config.yaml'sbehavior.cavemanvalue directly rather than parsing the session transcript (that schema is undocumented and unstable across Claude Code releases) — it shows the configured default, not necessarily the exact current-turn state. Requiresjqin the container; degrades to a minimal line without it. Skipped if the workspace already has.claude/statusline.sh/settings.json. - Caveman skill, default-on (upstream): bundled in the default skills, deployed to every active tool's skills dir (
.claude/skills/caveman,.opencode/skills/caveman). Compresses the model's prose replies (~65% of output tokens). Whenbehavior.caveman: true(the default) and the skill is installed, harness-ai injects an instruction into the AGENTS.md managed block — read natively by both Claude Code and OpenCode — so caveman mode applies from the first message of every session, no/cavemaninvocation needed. Turn it off for a session with "stop caveman", or disable the default entirely withbehavior.caveman: false. Refresh the bundled copy from upstream withjust update-skills caveman. - RTK (
install.rtk/--no-rtk, on by default): installs the binary; for Claude Code, injects thePreToolUsehook into the Claude hooks template so every scaffold run merges it into.claude/settings.json; for OpenCode, drops a static plugin (.opencode/plugins/rtk.ts, vendored from rtk-ai/rtk) that self-disables at runtime if thertkbinary isn't on PATH. Bash commands are then transparently rewritten to token-compressedrtkequivalents (60-90% savings ongit status, test runners,find, …). Check savings withrtk gain. - Headroom (
install.headroom/--no-headroom, on by default): installs the CLI viauv tool install "headroom-ai[proxy]"(requiresuv; warns and continues if missing). Compresses the request payload at the API boundary — a different layer than RTK. Not a hook and not auto-active: activate per-session withheadroom wrap <cli>(e.g.headroom wrap claude,headroom wrap opencode). Overlaps RTK on the input side while active, so prefer one over the other rather than stacking both. - openspec (
install.openspec/--no-openspec, on by default): installs the@fission-ai/openspecCLI vianpm install -g. Skipped with a warning ifnpmisn't on PATH; never fails the rest of the install.
A file-based memory layer for AI agents — a wiki of Markdown entries with YAML frontmatter, queried over MCP. Its source lives at wikictl/ in this repo and is fetched by cli.sh at the pinned ref, same as everything else no separate vendoring step.
Off by default. Enabling it (install.wikictl: true in .harness-ai/config.yaml, or --wikictl on the CLI) provisions:
- CLI — installed via
uv tool installfrom the fetched checkout (requiresuv; warns and continues if missing). Provideswikictl create|read|list|search|tags|edit|move|delete|schema|index|serve. - MCP server — a gated
wikictlentry (http://127.0.0.1:9797/mcp/, started bywikictl serve) merged into.mcp.json(Claude Code) and, whenopencodeis intools, intoopencode.json'smcpkey. The server encodes a metadata-first protocol and exposesget_schema(the entry metadata contract).
The wikictl-* skills deploy unconditionally alongside the other default skills, regardless of whether wikictl itself is enabled.
install:
wikictl: truePoint at any GitHub repo that follows the content/ structure to merge additional (or private) agents and skills on top of the bundled defaults.
your-content-repo/
├── agents/
│ ├── metadata.yml # per-tool frontmatter for each agent
│ └── my-agent.md # agent content (no frontmatter)
├── skills/
│ ├── metadata.yml # per-tool frontmatter for each skill
│ └── my-skill/
│ └── SKILL.md # skill content (no frontmatter)
├── hooks/ # optional: override default hook templates
│ ├── claude.json # replaces config/claude/hooks.json
│ └── opencode.ts # replaces config/opencode/rtk-plugin.ts
├── mcp.json # optional: override shared .mcp.json template
├── opencode.json # optional: override the opencode.json starter template
└── agents.harness-ai.md # optional: extra content appended to the AGENTS.md managed block
You can include any subset anything absent falls back to bundled defaults (unless installDefaults: false / --no-defaults). Hooks and MCP overrides are full replacements, not merges. Remote content wins on key conflicts with the bundled defaults.
Set the content repo in .harness-ai/config.yaml (not as a feature option there are no feature options):
contentRepo:
url: https://github.com/my-org/ai-content
ref: mainFor private repos, set GITHUB_TOKEN as a devcontainer secret:
{
"secrets": ["GITHUB_TOKEN"]
}Auth resolves automatically: GITHUB_TOKEN env var → gh CLI token → anonymous (public repos only).
From the CLI, the equivalent is a flag instead of YAML:
GITHUB_TOKEN=$(gh auth token) bash harness-ai.sh install --content-repo https://github.com/my-org/ai-contentDon't hand-write a new content repo's layout (Content repo above) — generate it:
bash harness-ai.sh init-extension ./my-extension --name "My Extension"This scaffolds the lean shape a real extension actually needs: agents/metadata.yml + one placeholder agent, skills/metadata.yml + one placeholder skill, a starter agents.harness-ai.md, and a README.md. Verify it works before customizing:
bash harness-ai.sh install --content-repo-local-path ./my-extensionhooks/, mcp.json, and paths.yml overrides are supported but optional/advanced — init-extension doesn't scaffold them; see AGENTS.md for their format. For the skill-naming and taxonomy conventions your new skills should follow, and for contributing to harness-ai's own code, see AGENTS.md and CONTRIBUTING.md.
Skills are organized by category and subcategory. Every entry in metadata.yml carries category and subcategory fields.
Plain, single-word subcategories on purpose — grouping follows what a skill actually does, not a target count.
| Category | Subcategory | Typical prefix |
|---|---|---|
engineering |
coding |
developer-* |
engineering |
architecture |
developer-*, advisor-* |
engineering |
operations |
advisor-* |
engineering |
documentation |
advisor-* |
communication |
content |
advisor-* |
communication |
messaging |
advisor-* |
communication |
style |
caveman |
reasoning |
brainstorming |
advisor-* |
reasoning |
research |
advisor-*, research-scout |
reasoning |
speaking |
advisor-* |
tools |
cli |
developer-github-cli, wikictl-* |
meta |
creation |
skill-creator, agent-creator |
meta |
review |
advisor-work-review |
coaching |
planning |
advisor-* (private, personal-training) |
coaching |
support |
advisor-* (private, personal-training) |