Pack classes
Five shapes almost every pack falls into (public read, gated write, linked channel, emit-only, control). Pick one, copy the sample, change four fields.
Do not design a new lattice per CLI. Pick the class, copy the sample, and change the fields; the three files are the same every time.
Picker
| If the capability… | Class |
|---|---|
| Needs no login and is a small closed read set | Public read |
| Needs a connect step, and has writes that must not auto-fire | 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 |
Public read
No login. Five to twelve closed read commands, such as a discover, plan, show sequence or an equivalent closed set.
| Binding | public |
| Land | personal |
| Commands | find · plan · show · status · follow (or equivalent) |
| Sense | Optional. Pull observation until a real push exists |
| Sample | examples/packs/transit |
follow is a read command an agent may call repeatedly. If the source later
pushes, add a stimulus. Do not overload plan.
Gated write
A connect step plus at least one write that must not fire from a hallucinated turn: transfer, send, apply, delete.
| Binding | local or user, with a credential hub |
| Land | local (workspace directory) or personal |
| Commands | setup.status · setup.apply · connect · status · one read · one confirm write |
| Sense | binding.degraded when connect health moves |
| Sample | examples/packs/ledger |
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 10 --confirm --json # write + confirmProof: the transfer without --confirm returns { "ok": false, "error": "confirm required" }.
Linked channel
A personal or project stream (mail, calendar, chat, tickets) under a shared Binding family. This is the class that uses both loops.
| Binding | user, shared family |
| Land | personal or brain |
| Commands | list / get / send (send is write, usually confirm) |
| Sense | Channel event (new_item) + binding.degraded |
| Adapter | composite once the SDK is stable; wrap is a fine bootstrap |
Mail, calendar, chat, and tickets are the same class. They differ only in Binding family and payload.
Emit-only
The pack only announces. It needs no user CLI.
| Adapter | sense_only |
| Commands | none |
| Sense | stimulus refs + Policy rows |
| Binding | none or public |
Do not invent bash to "install a webhook". connect or setup.* exist only if the
upstream source needs a registration call.
Control
Admin, fleet, operator, and origin tools. They run under a different Binding than the user runtime, so they are a different pack.
| Manifest | plane: "control" |
| Binding | mothership |
| Sense | Optional health. Never a user inbox |
| Rule | Second pack. Never silently on an end-user Principal |
If a domain CLI has both an admin face and a user face, that is two packs sharing a bin and nothing else.
Promotion
Classes are stable; adapters move.
pass_through ──► named 5–12 commands + policy.json ──► composite for hot pathsEvery step keeps pack_id, verb_key, and Policy unchanged.
What not to do
- A sixth class "because this domain is different"
- One pack that is both user runtime and operator control
- A catalog of many packs with no lint, no twin-face check, and no gated-write proof
- Wrapping an MCP-only SDK that has no CLI
Related
Skill file
How to write HARNESS.md so an agent invokes your pack correctly on the first try. Structure, the lead, examples, anti-patterns, and a checklist.
Wrap a CLI
Turn a domain CLI you already ship into a conforming pack. Installer family, connect, doctor, gated writes, entity arguments, and the control/runtime split.