Build a pack
Wrap a CLI you already have into a Harness Pack. Copy a gold pack, change four fields, write one policy row per command, lint, exec.
By the end of this page you will have a pack that installs into the reference runtime, executes from the CLI face and the JSON face, and refuses a gated write without confirmation.
You start by copying the closest gold pack and changing four fields.
Pick a gold pack
| If your capability… | Copy |
|---|---|
| Needs no login and is mostly reads | examples/packs/transit |
| Needs a connect step, or has a write that must not auto-fire | examples/packs/ledger |
cp -r examples/packs/transit ./packs/weatherChange four fields in the manifest
Open packs/weather/surface.json. Set the id, the CLI binary, the Binding mode,
and the commands.
{
"id": "weather",
"name": "Weather",
"kind": "wrap",
"cliBin": "weather",
"plane": "runtime",
"transports": ["cli", "json_exec"],
"binding": { "mode": "public", "hub": "public" },
"wrap": { "bin": "weather" },
"verbs": {
"find": { "description": "Resolve a place name.", "cli": ["weather", "find", "{{query}}"] },
"now": { "description": "Current conditions.", "cli": ["weather", "now", "{{place}}"] },
"forecast": { "description": "Next 48 hours.", "cli": ["weather", "forecast", "{{place}}"] },
"alerts": { "description": "Active warnings.", "cli": ["weather", "alerts", "{{place}}"] },
"status": { "description": "Upstream health.", "cli": ["weather", "status"] }
}
}Keep the command set closed and small: five to twelve names. Each cli array is
the space-separated CLI form, and {{name}} placeholders map to JSON args. The
runtime appends --json and never shell-interpolates.
Field by field: Manifest.
Write one Policy row per command
Every command needs a row. Reads are read; anything that changes the world is
write; a write that must not fire unconfirmed adds confirm: true.
{
"surface_id": "weather",
"version": 1,
"verbs": {
"find": { "operation": "read", "privacy": { "land": "personal" } },
"now": { "operation": "read", "privacy": { "land": "personal" } },
"forecast": { "operation": "read", "privacy": { "land": "personal" } },
"alerts": { "operation": "read", "privacy": { "land": "personal" } },
"status": { "operation": "read", "privacy": { "land": "personal" } }
},
"stimuli": {}
}land says where data may land: personal (actor-private), brain (shared
project), or local (a workspace directory). Nothing else goes in this file: no
roles, user lists, or keys. Details: Policy file.
Write the Skill on one screen
HARNESS.md teaches an agent (and a human) the hot path. It has a lead, a table,
examples, and anti-patterns. It contains no essays, secrets, or host-absolute
paths.
# HCP — Weather
Public read pack. No login. Resolve a place, then read conditions, forecast, or alerts.
## Hot path
| Command | CLI |
|---------|-----|
| `find` | `weather find "Berlin" --json` |
| `now` | `weather now berlin --json` |
| `forecast` | `weather forecast berlin --json` |
| `alerts` | `weather alerts berlin --json` |
| `status` | `weather status --json` |
## Anti-patterns
- Calling `now` in a tight loop — read `forecast` once instead
- Guessing a place id — always `find` firstWriting guide: Skill file.
Lint until clean
hcp lint ./packs/weather --json{ "ok": true, "pack_id": "weather", "issues": [] }Lint fails when a command lacks a Policy row, a Policy row names a command that
does not exist, HARNESS.md is missing, a wrap declares neither named commands
nor pass_through, or a control-plane pack is not on mothership Binding.
Install, activate, exec
hcp install ./packs/weather --json
hcp activate weather --json
hcp availability weather --json
hcp exec weather now --json -- place=berlinThe exec envelope the CLI sent on your behalf:
{ "pack_id": "weather", "verb_key": "now", "args": { "place": "berlin" } }And the result envelope:
{ "ok": true, "pack_id": "weather", "verb_key": "now", "result": { "temp_c": 17, "sky": "overcast" } }The CLI face of the same command, weather now berlin --json, produces the same
result. That is the twin face.
Add a gated write (optional)
If your CLI has a mutation that must never fire from a hallucinated turn, declare
it as a confirm write. Copy the shape from ledger:
"transfer": {
"description": "Move funds. Requires confirm.",
"cli": ["ledger", "transfer", "{{amount}}", "--confirm"],
"policy": "write"
}"transfer": { "operation": "write", "privacy": { "land": "local" }, "confirm": true }Then prove the gate:
hcp exec ledger transfer --json -- amount=10
# { "ok": false, "error": "confirm required" }
hcp exec ledger transfer --json -- amount=10 confirm=true
# { "ok": true, … }The runtime enforces the gate before the adapter runs, so no Invoker can skip it.
What you did not do
You did not write a connector, implement a server, register tools in a host, put a
token in a file, or wrap the whole weather binary. You named five commands and
gave each a rule.
Next
- Pack classes: is this a public read, a gated write, a linked channel?
- Wrap a CLI: when the binary needs credentials or a setup step
- Best practices
- Adopt in a host: make the pack visible to Cursor or OpenCode
Quickstart
Pick the path that matches what you are building (a pack, a runtime, or a host integration) and get to a working exec in minutes.
Implement a runtime
Build the thing that loads packs, enforces grants, mints Binding, sandboxes adapters, and reports availability, with the reference SDK or from scratch.