.md

Command

What a command is made of, how to name it, how its CLI face renders from JSON args, and the rules that keep it safe for an agent to call.

On this page

A command is a named operation a pack declares and the unit of Execute. Its CLI face and its JSON face are the same command under one Policy row, one Binding and one Adapter, so the name you give it holds on every surface a host shows. Give a pack 5 to 12 commands and write each CLI form to the agent-safe rules.

Anatomy#

The wire field verb_key and the manifest map verbs both mean command, since HCP has no separate verb ontology.

PartWhereExample
Stable namemanifest verbs key; wire verb_keyplan, mail.list, setup.apply
Descriptionmanifest"Time-first options between two places."
CLI formmanifest cli[]["transit", "plan", "{{from}}", "{{to}}"]
Argument placeholders{{name}} in cli[]map to JSON args
Title (optional)manifest title"Searched the Inbox for “Laura”"
Approval view (optional)manifest previewThe card a person sees before a gated write
Policy rowpolicy.jsonoperation, privacy.land, confirm
Adapter pathpack kindspawn the wrap bin, or call in-process

The twin face#

Terminal
transit plan "A" "B" --json

Only the Invoker differs, which makes the twin face the command's identity (L2). hcpx exec sends the JSON envelope from the command line for runtime operators and tests, while Skills teach the domain bin. Its k=v pairs after -- become args, and confirm=true or apply=true satisfies the confirm gate.

Terminal
hcpx exec transit plan --json -- from=A to=B

Naming#

Names are lower-case, dot-separated when a family is useful, and stable.

PatternUse
find plan show status followPublic read set
setup.status setup.apply setup.updateInstaller family (HCP/1.1)
connectEstablish Binding; needs pack.connect
doctor capabilities validateHealth, discovery, and pre-flight checks; always read
list get send createDomain ops on a linked channel

Dots group names without creating a hierarchy, so mail.list is one command. A pack that needs more than 12 commands is usually two packs.

Each name is identical in every projection, and lint fails on drift:

FaceName
CLImail list (space grammar from cli[])
JSON"verb_key": "mail.list"
Skill`mail.list` in the hot-path table
Host toolhcpx exec mail mail.list in a shell, exec mail mail.list through the one hcp tool, or mail__mail_list in an in-process loop (pack id, two underscores, verb_key with dots as underscores)

The CLI face may carry aliases in other languages, declared as manifest metadata and never as separate commands, while the JSON verb_key stays English.

Titles#

A host shows a person each step of a run, and title gives the words: live while the command runs and done once it ran. The pack declares them because a command line shows only flags and ids, and model text uses words the pack never said.

surface.json (fragment)
"find": {
  "description": "Mail in the Inbox matching a query or a sender.",
  "cli": ["mail", "find", "--query", "{{q}}", "--from", "{{from}}"],
  "title": {
    "live": ["Searching the Inbox for “{{q}}”", "Searching the Inbox"],
    "done": ["Searched the Inbox for “{{q}}”", "Searched the Inbox for mail from {{from}}", "Searched the Inbox"]
  }
}

Each form is one template or a list tried in order. The first template whose placeholders all have a value wins, so a title never shows an empty slot, and the last one in each list should have no placeholders, so a title always shows. {{name}} is a call argument, and {{result.a.b}} is a field of the JSON result that works only in done, as in "Read {{result.mail.subject}}". Values are flattened to one line and cut to 60 characters, and a list of strings is joined with commas.

Calllivedone
q=LauraSearching the Inbox for “Laura”Searched the Inbox for “Laura”
from=anna@acme.exampleSearching the InboxSearched the Inbox for mail from anna@acme.example
no argumentsSearching the InboxSearched the Inbox

The SDK returns the words: commandTitle(pack, verb_key, args) while the command runs and commandTitle(pack, verb_key, args, result) once it has a result, or null when the pack declares no title and the host uses its own. hcpx check reports a title that names an unknown argument, uses a result in live, or has no plain fallback (M026).

RFC 0007 proposes titles and the reference SDK implements them. It asks hosts to show a declared title in place of the command line, keep the exact command one action away, and never replace the title with text the model wrote.

CLI projection#

Every command projects to a CLI form an agent can run safely (Projections):

<bin> <space…> [--flags] [--json]
Terminal
mail list --folder=inbox --json
mail send --to user@example.com --subject Hello --confirm --json

How cli[] renders#

The runtime substitutes placeholders from args, drops empty ones, and appends the JSON flag.

DeclaredArgsRendered
["transit", "plan", "{{from}}", "{{to}}"]{ from: "A", to: "B" }transit plan A B --json
["mail", "list", "--folder={{folder}}"]{ folder: "inbox" }mail list --folder=inbox --json
["ledger", "transfer", "{{amount}}", "--to", "{{payee}}", "--memo", "{{memo}}", "--confirm"]{ amount: "2.80", payee: "demo-transit", confirm: true }ledger transfer 2.80 --to demo-transit --confirm --json
["ledger", "setup", "all", "-y"]{}ledger setup all -y --json

The first token is the bin, and the arguments go to spawn as an array with no shell in between, so a value with spaces stays one argument. A missing optional value drops its token and the flag right before it, so an empty memo leaves no dangling --memo. Without cli[] the runtime falls back to wrap.argv_template, where {{verb}} expands to the command tail (mail.list becomes list).

The JSON flag#

The runtime appends --json unless the pack or the command selects JSON another way.

DeclaredAppended
Nothing--json
wrap.json_flag: ["-o", "json"]-o json on every command
A command's own json_flagThat value instead, for that command only; [] appends nothing, for CLIs that print JSON by default
cli[] already selects JSON (--json, --json=…, --json name,url, or -o, --output, --format, -f followed by json) and the command has no json_flagNothing

The JSON arguments always come last, and hcpx check probes render commands the same way. RFC 0004 proposes the JSON flag and the reference runtime implements it.

Exit codes#

CodeMeaning
0Success
1Failure
2Warning; used by doctor / status for degraded but usable
127Bin not found or not allow-listed (runtime-side)

A non-zero exit becomes { "ok": false, "error": <stderr or "exit N"> } on the JSON face.

Agent-safe rules#

RuleWhy
--json or JSON-by-default stdoutAgents parse; humans still read
Non-zero exit on failureOrchestrators branch correctly
No interactive prompts on the agent pathAgents hang or invent answers
Idempotent readsRetries are safe
Explicit writes: operation: write and clear namesNo surprise side effects
--confirm / --apply on gated writes, mirroring Policy confirm: trueThe gate is visible on both faces
Stable space grammarSkills stay teachable across versions
doctor / capabilities for discoveryBeats hidden flags

What is not a command#

A mega-command such as run or exec -- <argv> forwards arbitrary arguments and behaves as a shell, which Execute does not cover. A PATH wrap over allowedSubcommands is a bootstrap to promote to named commands. "New message arrived" is a Sense trigger such as new_item, while mail.list is a command, and follow stays a read command however often an agent calls it.