Best practices
Pick one of five pack shapes, then hold the pack to the habits for scope, naming, Policy, Skill, Binding, wrapping and Sense that review checks.
Lint decides whether a pack is valid, and its shape and these habits decide whether agents use it correctly. Pick a class first, copy its sample, and run the review checklist at the end before you share.
Pack classes#
Every CLI fits one of five classes, even in a domain that feels different. The three files keep their shape in each, so copy the sample and change the fields.
| If the capability… | Class |
|---|---|
| Needs no login | Public read. It may still write; Policy confirm gates the risky commands |
| Needs a login, a token, or a connect step | Gated write |
| Is a linked personal or project stream with real push events | Linked channel |
| Is ingress or webhook only, with no user commands | Emit-only |
| Runs under a different Binding than the user runtime | Control, which is a second pack |
Is MCP-only with no CLI and no doctor | Do not wrap. Ship a CLI first |
| Class | Binding | Land | Commands | Sense |
|---|---|---|---|---|
| Public read | public | personal | find · plan · show · status · follow (or equivalent) | Optional. Pull observation until a real push exists |
| Gated write | local or user, with a credential hub | local (workspace directory) or personal | setup.status · setup.apply · connect · status · one read · one confirm write | binding.degraded when connect health moves |
| Linked channel | user, shared family | personal or brain | list / get / send (send is write, usually confirm) | Channel event (new_item) + binding.degraded |
| Emit-only | none or public | none | Stimulus refs + Policy rows | |
| Control | mothership | Optional health. Never a user inbox |
Public read#
A public read pack has five to twelve closed reads, such as a discover, plan
and show sequence, and its sample is examples/packs/transit. follow is a read an agent may call
repeatedly, so when the source starts to push, add a trigger instead of
overloading plan.
Gated write#
At least one write must not fire from a hallucinated turn (transfer, send,
apply, delete). The examples/packs/ledger sample runs the whole family, and
without --confirm its transfer returns
{ "ok": false, "error": "confirm required" }.
ledger setup status --json # read, idempotent
ledger setup all -y --json # write, no wizard on the agent path
ledger connect demo --json # write, gated by pack.connect
ledger list --json # read
ledger transfer 2.80 --to demo-transit --confirm --json # write + confirmLinked channel#
Mail, calendar, chat and tickets differ only in Binding family and payload.
This is the one class that uses both loops, and its adapter is composite once
the SDK is stable, with wrap as a fine bootstrap.
Emit-only#
The pack only announces, with adapter sense_only and no user CLI. Never write
bash to "install a webhook"; add connect or setup.* only if the upstream
source needs a registration call.
Control#
Admin, fleet, operator and origin tools set plane: "control" and ship as a
second pack that never sits silently on an end-user Principal. A domain CLI with
an admin face and a user face becomes two packs that share only the bin.
Scope#
Name 5 to 12 commands. More means two packs, because a large set dilutes the Skill and agents pick wrong, and a CLI with forty subcommands probably has five that matter to an agent. Keep one class per pack: an operator face mixed into a user pack is the most common reason review rejects one.
Naming#
Name the outcome: plan, send and transfer, never doPlanning, sendMail
or execTransfer. Use families for lifecycle steps (setup.status,
setup.apply, setup.update), never for hierarchy (mail.folder.item.get).
Renaming a command breaks the Skills, host projections and grants that name it,
so add a new command and deprecate the old one.
Policy#
Mark a command read only if an agent could safely retry it. Confirm the
irreversible (transfers, deletes, external sends, publishes) and leave confirm
off idempotent writes such as setup.apply and sync, so agents retry them
freely. Choose the land by where the data lives, never by adapter or Binding
mode, and never encode who may run a command: roles and users are runtime
grants, and a pack that embeds them is wrong for every other runtime.
Skill#
Hosts keep roughly the first 480 characters of the lead in stable context, so
the lead must stand alone and name the first command. Show the confirm flag in
examples, because agents copy them and an example without --confirm gets the
first call denied, after which the agent improvises. Separate setup from daily
ops, or agents will connect on every turn. Anti-patterns are the most useful
section, so write the ones you have seen.
Binding#
Use one mode per pack, because a CLI that needs two is two packs. Declare the
family when a sibling pack shares the login, so users consent once. Never fall
back to env: when the runtime cannot mint, the command fails. Mock is explicit
(mode: mock in the envelope, never PACK_MOCK=1).
Wrap#
Template with cli[] and {{placeholders}} and never interpolate. Set
agent_env for output mode if the bin needs it (FOO_OUTPUT=json), so the
runtime never special-cases your bin. Pass-through is only a bootstrap, so
promote it to named commands. A pack keeps its class while its adapter moves on,
and every step keeps pack_id, verb_key and Policy unchanged:
pass_through ──► named 5–12 commands + policy.json ──► composite for hot pathsSense#
Declare only real pushes, since polling is a read command. Always declare
binding.degraded on a pack with user or local Binding, because that is how
a person learns to reconnect.
Testing#
Ship the proof script and run it in CI, with the twin-face check. Never publish a catalog of many packs without lint, a twin-face check and a gated-write proof.
Review checklist#
- 5 to 12 named commands, one class
- Every command and trigger has a Policy row; lint is clean
- Irreversible writes have
confirm: trueand--confirmincli[] - Land chosen by data ownership
- One Binding mode; family declared if shared; no env fallback
- Skill lead names the first command; setup separated; anti-patterns present
- No secrets, absolute paths, UI, or MCP-as-install anywhere
- Control-plane commands are in a second pack
- Proof script passes in CI