Skip to content

Plans and tokens

How an agent declares a plan, and what the signed intent token carries.

Before an agent touches any tool, it declares what it intends to do. A plan has a goal, the person the agent acts for, and up to 50 steps:

JSON
{
  "version": 1,
  "goal": "Summarise open issues",
  "user": "alice@acme.dev",
  "steps": [
    {
      "tool": "github_list_issues",
      "args": { "repo": { "eq": "acme/public-app" } },
      "maxCalls": 3
    },
    { "tool": "github_get_issue", "args": { "repo": { "eq": "acme/public-app" } } }
  ]
}
Step field Default Meaning
tool Exact tool name, never a glob. Must match ^[a-zA-Z0-9_-]{1,64}$.
args none Pinned arguments, one constraint each: eq, in, glob, regex, lte or gte.
maxCalls 20 How many times this step may run.
required false The step must run for the plan to complete.

A call matches a step when the tool name is equal and every pinned argument satisfies its constraint. A pinned argument that is missing from the call does not match, so the call is blocked as not_in_plan. The constraint operators work as in policies.

With the agent guard, the model writes the plan itself through a built-in declare_plan tool, before it reads any tool output. Your app can also pass an explicit plan.

The plan’s user must be an active member of your team. Plans for unknown or disabled users are refused.

POST /v1/plans returns a compact JWS signed with Ed25519. It is a signed bundle with everything a client needs to decide locally:

Claim Meaning
sub The person’s email.
jti The plan id (a ULID).
iat, exp Issued and expiry times. Default lifetime 15 minutes, at most 60.
ph The plan hash: SHA-256 of the plan in canonical JSON (RFC 8785).
plan The full plan.
agent The agent that requested it, from its API key.
pol, polv, polh The agent’s active policy at signing time, its version number and hash.
role, groups The person’s role and groups at signing time.
kid (header) The signing key id, for key rotation.

Changing any byte breaks the signature, so a client cannot widen the plan or loosen the policy it was given. The server’s public keys are published at /v1/keys/jwks.

Tokens are short-lived on purpose. The guard refreshes an expired token with POST /v1/plans/:id/refresh, which re-signs the same plan hash with the agent’s current policy. A refresh for a different plan is rejected. To do something the plan does not cover, the agent needs a new plan.

Because the policy is a snapshot, a policy change reaches running plans at their next refresh, at most one token lifetime later.