.md

Design principles

Twelve laws every conforming pack, runtime and host follows, the failure each one prevents, and what HCP/1.x leaves out on purpose.

On this page

HCP is small on purpose: twelve laws, each mapped to a lint check or a runtime behaviour and each there because the alternative was tried and broke. The non-goals list what HCP/1.x leaves out, and only an RFC changes either list.

L1: The CLI is the harness#

A capability enters HCP as a CLI, or as an in-process SDK that projects to one, and every command has a CLI face with structured output (--json, or JSON by default). That is the interface people and CI already script, so agents share it. MCP is an optional export from a pack and never the install path.

L2: The twin face is identity#

transit plan A B --json and { "pack_id": "transit", "verb_key": "plan", … } are the same command under the same Policy, Binding and Adapter, and verb_key is the command name with no second ontology behind it. No Invoker gets a code path of its own, which is where separate auth and audit would grow.

L3: Named commands, 5 to 12, never a PATH wrap#

A pack declares a closed set of commands, never a whole binary behind an allowedSubcommands allow-list or a catch-all run or exec -- <argv>, because Policy and Skill can only be written for things with names. A pass-through wrap is a bootstrap step, and the pack conforms only once it is promoted to named commands.

L4: Binding mints; packs never hold secrets#

A pack names a Binding mode (public, user, local, mothership, mock) and the runtime's hub mints the credential. Exec fails closed when the mint fails, with no silent fallback to environment tokens, and mock mode is explicit in the envelope, never an environment flag. A mode is portable, while a secret in a manifest, Skill or host config leaks, drifts and escapes audit.

L5: Three lands; entity arguments are not tenants#

privacy.land is personal (private to the actor), brain (a shared project) or local (a workspace directory). Tenant and project come from the authenticated Principal's session, so an argument such as --entity picks an account inside the Binding's scope but never the tenant, and one user cannot reach another user's data through a pack argument.

L6: Writes are gated#

A write that must not fire from a hallucinated turn declares confirm: true, passed as --confirm or --apply on the CLI face and confirm: true on the JSON face, and the runtime checks it before any Adapter runs, whoever invokes. A dry run is still operation: write and must not persist. Health commands (doctor, status) report and never mint, and exit 0 for ok, 2 for warn and 1 for fail.

L7: Execute and Sense are two loops on one pack#

Inbound commands and outbound triggers share Policy, Binding and Principal but never merge into one tool, because an event that could execute could also spend money. A trigger cannot run a command, and an Invoker that reacts to one still goes through Execute.

L8: Sense must not mint, and polling is not Sense#

A trigger cannot mint credentials or bypass Binding. Calling status or follow repeatedly is Execute, and uptime monitors, UI refresh timers and control-plane ticks are not pack triggers, otherwise every pack would claim to sense by burning tokens in a loop.

L9: Control and runtime are two packs#

Admin, fleet and operator CLIs run in a second pack with plane: control and Binding mothership, never silently available to an end-user Principal. That closes the classic privilege-escalation bug in agent tooling, an end-user agent inheriting operator credentials because a binary happened to be on PATH.

L10: The Skill is one screen; Policy covers everything#

HARNESS.md teaches the declared commands on one screen (a lead, a hot-path table, copy-paste examples, anti-patterns, pointers), because a Skill that grows into a manual stops being read. Every command and every trigger has a Policy row or lint fails, since a runtime cannot reason about a command without one.

L11: Invokers never redefine Policy or Binding#

Agents, APIs, UIs and people run the same command, each carrying a Principal whose grants the runtime evaluates, and none adds its own auth or policy for it, because when one command has two policies the weaker one decides.

L12: One living repo per pack; proof is a script#

A pack id maps to one maintained repository, and the pack proves conformance with a script CI can run: lint, one read, one gated write in mock or paper mode, one live deny without confirm and, if it declares Sense, one announce with the grant on and one deny with it off. A claim in Markdown does not count, because a pack that cannot prove itself drifts away from its Skill.

How the laws become checks#

LawEnforced by
L3Lint: wrap requires named commands or pass_through
L4Runtime: fail-closed mint; the sandbox denies env-flag mocks
L5Runtime: brain requires project_id; the Policy schema rejects unknown lands
L6Runtime: confirm required before the Adapter
L9Lint: control plane packs default Binding mothership
L10Lint: missing HARNESS.md, verb … missing from policy.json, stimulus … missing from policy.json
L12Testing and Conformance

Non-goals#

HCP/1.x leaves these out on purpose.

HCP does notWhy
Make MCP the install unitMCP is a transport HCP can export to, and a transport carries no Policy, Binding, Sense or Skill
Define a verb ontologyOntologies rot, so packs name commands after the CLI they wrap, and read/write is the only class a runtime needs
Put tenant, user or role identifiers in packsA pack is shared across tenants, so anything tenant-specific in it is a leak or a lie; tenant comes from the session and grants from the runtime
Carry credentialsPacks are copied, published and read by agents, so they hold no tokens, key files or .env files (L4)
Mandate a network client/server splitA runtime may be in-process, a CLI or a hosted service, and the smallest useful one is a function call
Require UI or A2UIAgents and CLIs invoke most capabilities first, so transports and host rendering stay optional
Wrap PATHThe sandbox does not resolve bare names on the host PATH, or an agent could run a binary nobody allow-listed
Merge teaching packs with capability packsA Skill that only teaches is an Agent Skill, and Policy needs commands to govern
Bake product schemas into core typesLedger, CRM and database record types belong to the packs that wrap those products
Name vendors in the specificationAn open standard names roles, so hosts and runtimes appear as examples only in the Hosts section
Define a signed registry or pack versions (yet)Packs are directories identified by pack_id while the pack model and the two loops come first. Signed fetch by pack_id@version is on the roadmap for HCP/2.0, and the community catalog indexes directories and CLI scaffolds without being a registry
Poll on behalf of packsSense is push, and polling for a pack would turn every read into a silent background write to the runtime's own store

A pack author who needs one of these is probably building a runtime feature (Build runtimes). A runtime builder may add any of them inside the runtime but may not require packs to change for it, and moving the boundary takes an RFC that names the item and the cost of keeping it out.