Docs · 03

Agents and standing grants.

Agents on CommandAGI have real-time control of real machines. Authority comes from one place — a standing grant a person made — and every action is on the record before it happens.

No human in the loop — by design

CommandAGI is built for machines that run unattended. An agent that holds a grant arms a unit, runs programs and sends commands in real time, through the workbench, the API or MCP, without a confirmation for each action. What keeps that safe is not a person watching; it is the grant, the driver's bounds and the record.

Principals

Everything that acts is a principal: agent:<name> or person:<name>. A principal proves who it is with a credential — it never just names itself. A request that only says who it is is refused.

  • Host credential — cagp_…, issued once by commandagi grant token --principal agent:<name> (or when a grant is added). The host stores only its SHA-256. grant token rotates it; grant forget removes it.
  • Account credential — cagc.<payload>.<signature>, signed with your account's key (Ed25519) for agents running in the cloud. It names the principal, the account, an expiry and optionally one host, and is honoured only for grants that account made. Revoke one with grant revoke-credential.

Standing grants

A standing grant is authority an account gives one principal, made by a person's explicit act. No tool or agent route creates or widens a grant; an agent's API key is refused on every route that changes authority.

shell
commandagi grant add --principal agent:night-shift \
  --world shop --unit mill-1 --channel gcode \
  --verb open --verb arm --verb run --verb send \
  --for 8h --note "night shift"
commandagi grant list
commandagi grant revoke g-3f9a1c20b7de

A grant names a world and, optionally, units, channels and verbs — leaving one out means all of them. It may expire (--for 8h, --expires <ISO time>). Grants are kept in ~/.commandagi/grants.json, read on every action, and can also be made in the workbench under settings → grants. A malformed grants file honours no grant at all.

json
{ "grants": [ { "id": "g-3f9a1c20b7de", "principal": "agent:night-shift",
  "grantedBy": "acct:user:u_8f2k", "world": "shop", "units": ["mill-1"],
  "channels": ["gcode"], "verbs": ["open", "arm", "run", "send"],
  "expires": "2026-10-01T06:00:00Z", "note": "night shift" } ] }

The same command makes three other kinds of grant: --file PREFIX… (write files under a path), --outreach APP… --per-day N (send messages through connected apps) and --market VENUE… --symbol S --max-qty N --max-notional N --per-day N (place orders).

Grants apply to physical worlds. A simulation world needs none.

Verbs, arming and safing

A grant can give four verbs: open a channel, arm a unit, run a program, send a command.

  • Drivers that move hardware open disarmed. Any principal with a grant on the unit can arm it. An armed session that sits idle for five minutes disarms itself.
  • Drivers that only take input — a shell, a Python kernel, a job hand-off — need no arming; the grant is checked on every input.
  • Safing is always allowed. stop, disarm and close — and safing commands such as feed hold, pause, emergency stop and torque off — are allowed to anyone holding any unexpired grant on the unit, to whoever opened the session, and to the account's own person. Cancelling a resting order is safing too.

The rules every action follows

  • Recorded before it is sent. Before the driver is called, a note — the principal and the action — is appended to the run's stream and flushed. A read-only project refuses the action rather than act without a record.
  • An acknowledgement is not a state. A reply or ok never implies the commanded state was reached. Commands stay pending until the machine reports a state.
  • Unknown stays unknown. A lost link is recorded as state: unknown. A stream that ended without an end record reads interrupted: state unknown.
  • No silent retries. A physical command is never retried by the system. An agent may decide to try again; that decision is its own recorded command.
  • Bounds are the driver's job. Declared limits, intervals and allowed verbs are enforced by the driver, not by a person. Input streams are paced: one action at a time, at least 30 ms apart.
  • Simulated and physical never mix. A run belongs to one world, and its records' source must match the world's kind.

Threads and their agent

Starting a thread in a physical world grants the thread's agent (threads.agent, agent:mcp by default) every verb on that world's units, for at most 24 hours, and revokes it when the thread closes. Turn this off with the setting "threads.agentControl": false.

Next

Connect an agent to all of this over MCP: SDK and MCP.