.md

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.

On this page

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 loginPublic read. It may still write; Policy confirm gates the risky commands
Needs a login, a token, or a connect stepGated write
Is a linked personal or project stream with real push eventsLinked channel
Is ingress or webhook only, with no user commandsEmit-only
Runs under a different Binding than the user runtimeControl, which is a second pack
Is MCP-only with no CLI and no doctorDo not wrap. Ship a CLI first
ClassBindingLandCommandsSense
Public readpublicpersonalfind · plan · show · status · follow (or equivalent)Optional. Pull observation until a real push exists
Gated writelocal or user, with a credential hublocal (workspace directory) or personalsetup.status · setup.apply · connect · status · one read · one confirm writebinding.degraded when connect health moves
Linked channeluser, shared familypersonal or brainlist / get / send (send is write, usually confirm)Channel event (new_item) + binding.degraded
Emit-onlynone or publicnoneStimulus refs + Policy rows
ControlmothershipOptional 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" }.

Terminal
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 + confirm

Linked 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 paths

Sense#

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: true and --confirm in cli[]
  • 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