Command model
How a service declares its commands as arrays of segments, and how a client resolves them into a subcommand chain of any depth.
Status v1 · Part of the Services specification
An action declares its command as an ordered list of segments:
{ "command": ["orders", "refunds", "create"] }A client turns that into the subcommand chain developers already know from
git, gh and vercel:
npx interagentic popcorn.club orders refunds create --id ord_8fJ21. Design
The command tree is declared by the service, never inferred by the client. Four properties follow from that.
Any depth works the same way. A deeper command is a longer array. Nothing changes at the third or the tenth level.
Every level can explain itself. Intermediate nodes are declared as groups with their own description, so stopping halfway down a chain prints something useful instead of failing.
A chain is never ambiguous. Actions are leaves and groups are branches, so every sequence of words resolves to exactly one thing.
Mistakes are caught locally. The client holds the whole tree from the manifest, so it can complete a segment, or reject an unknown one, before any request is made.
2. Resolution
Given the arguments after the domain, a client resolves them greedily:
- Take the longest prefix of arguments that exactly matches an action's
command. That is the action; the remainder are its arguments. - If no action matches but the arguments are a prefix of one or more commands,
the node is a group: print its description and children, and exit
0. - Otherwise the command is unknown: print the nearest matches and exit
2, before making any request.
Stopping at a group prints help rather than failing, which is what makes a deep tree explorable:
npx interagentic popcorn.club ordersorders — Place and track orders
list List your orders
create Place an order [$]
refunds Issue refunds → 2 more3. Segments
Each segment MUST match:
^[a-z0-9]+(-[a-z0-9]+)*$Lowercase, digits, single hyphens between words. Depth is unbounded.
Commands MUST be unique within a service, and an action's command MUST NOT be a
prefix of another action's command. An action is always a leaf: if orders
needs children, it is a group, and the thing it did becomes one of them —
orders list, for instance.
4. Groups
{
"groups": [
{ "command": ["orders"], "description": "Place and track orders" },
{ "command": ["orders", "refunds"], "description": "Issue refunds" }
]
}Groups are optional. A client encountering an undeclared intermediate node MUST still resolve it, listing its children without a description. Declaring groups is how a service makes itself pleasant to explore.
5. Arguments
Everything after the resolved command is an argument.
- Names listed in the action's
argsarray may be given positionally, in order. - Everything else is a
--flag. --jsonswitches off human-readable rendering and is reserved by clients.--helpprints the action's parameters, types and example, and sends nothing.
npx interagentic popcorn.club orders create 2
npx interagentic popcorn.club orders create --qty 26. Action files
Each action's detail file lives at a path that mirrors its command, one directory per segment:
| Command | Detail file |
|---|---|
["orders", "list"] | /.well-known/interagentic/services/orders/list.json |
["orders", "create"] | /.well-known/interagentic/services/orders/create.json |
["orders", "refunds", "create"] | /.well-known/interagentic/services/orders/refunds/create.json |
Because an action is always a leaf, the tree on disk has the same shape as the tree in the CLI, and a static file server needs no configuration to serve it.
7. Deriving commands from OpenAPI
If you already publish OpenAPI, commands fall out of your paths and methods:
| Operation | Command |
|---|---|
GET /orders | orders list |
GET /orders/{id} | orders get |
POST /orders | orders create |
PATCH /orders/{id} | orders update |
DELETE /orders/{id} | orders delete |
POST /orders/{id}/refunds | orders refunds create |
Path segments become groups and path parameters are skipped. The method chooses
the verb: GET on a collection is list, GET on a single item is get,
POST is create, PUT and PATCH are update, DELETE is delete.
Path parameters become request templates
in the endpoint, so /orders/{id}/refunds is published as
/orders/{{$id}}/refunds with a required id argument.
To name something differently, set x-interagentic-command on the operation:
paths:
/orders/{id}/ship:
post:
x-interagentic-command: [orders, ship]
summary: Mark an order as shipped