Skip to content
commission

Execution policies and approval

You will be able to say which work an agent may do unsupervised, which work only a person may touch, and which work an agent may do but only a person may accept — and to explain to anyone who asks why a particular actor got a particular task.

Audience: whoever is accountable for what agents are allowed to do. Assumes: task lifecycle.

Declare who is who

Nothing below means anything until Commission knows which actors are people. Declare them in .commission.json:

{
  "version": 1,
  "project": "myapp",
  "actors": [
    { "name": "priya", "kind": "human", "roles": ["owner", "security"] },
    { "name": "blaze", "kind": "human", "roles": ["owner"] },
    { "name": "claude:api", "kind": "agent", "capabilities": ["implement", "test"] }
  ]
}

actors is one of the two blocks reconciled from the file into the project on every command, so committing it is enough — there is no separate registration step.

commission actors
blaze                human  autonomous  may: claim, approve, verify
  roles: owner
claude:api           agent  autonomous  may: claim
  can: implement, test
priya                human  autonomous  may: claim, approve, verify
  roles: owner, security

An actor whose kind is agent may claim and work. It may not approve, and it may not answer a blocking question. That is not a permission you can grant.

With no actors declared at all, nothing is restricted — Commission does not invent policy for a project that has not asked for any.

Three fields, per task

fieldquestion it answerscommand
autonomyhow far may this be taken unsupervised?commission edit <id> --autonomy supervised
mayExecutewhich roles or capabilities may claim it at all?commission edit <id> --may-execute security
preferredActorwho should do it?commission edit <id> --preferred-actor claude:api

autonomy has three values:

valueagents may claimclosing needs a human
autonomous (default)yesno
supervisedyesyes
human_onlynoyes

Eligibility refuses; preference does not

mayExecute and human_only are permission. An actor that fails one is refused, by name, with the lever that changes it:

claude:api cannot claim myapp-1: requires one of [security] and claude:api has none of them
  missing: mayExecute: security
hint: priya can: COMMISSION_ACTOR=priya commission claim myapp-1 — or drop the requirement: commission edit myapp-1 --may-execute ""
claude:api cannot claim myapp-1: autonomy is human_only and claude:api is an agent
  missing: autonomy: human_only
hint: blaze or priya can: COMMISSION_ACTOR=blaze commission claim myapp-1 — or change the policy: commission edit myapp-1 --autonomy supervised

preferredActor is bias, and this is the distinction that surprises people: a non-preferred actor is fully eligible. Preference sorts a task earlier for the actor it names; it does not reserve it. If the preferred actor is busy and someone else claims it first, that is the system working, and the explanation says so:

preferred actor is claude:api, but claude:web claimed it first — preference biases selection, it does not reserve

Ineligible tasks are filtered before ordering, so commission ready and commission next simply do not offer an agent work it could never claim. An empty frontier for an agent and a non-empty one for a human is a correct answer, not a bug.

Why did this actor get this task?

Every automatic routing decision can answer it. commission next prints one line; --json carries the structure:

{
  "why": "unclaimed; autonomy autonomous (default); no actors are declared, so nothing is restricted",
  "eligible": [],
  "ineligible": [],
  "wouldChange": [
    {
      "change": "autonomy is autonomous, so agents may claim it; human_only would reserve it for people",
      "command": "commission edit myapp-2 --autonomy human_only"
    }
  ]
}

wouldChange is the part worth using. Every lever is stated as the fact it currently is, plus the command that flips it — an explanation nobody can act on is trivia.

Approval is not completion

This is the distinction the whole feature exists for, and it is worth stating plainly:

A gate says WHAT must be attached before a task can close. An approval says WHO accepted it. No amount of evidence substitutes for a person’s judgement, and no person’s judgement substitutes for the evidence.

A manager will recognise the failure this prevents. “It’s done — the tests pass and the PR is merged” is a claim about technical completion. “It’s done — I looked at it and I’m happy” is a claim about acceptance. Teams routinely conflate them, and the conflation is invisible until an incident, when it turns out that everything was green and nobody had actually decided anything.

A worked example

A key rotation. The agent can do the work; a human has to accept that it was done safely.

commission edit myapp-1 --autonomy supervised

The agent claims it, works it, and ticks the criterion:

COMMISSION_ACTOR=claude:api commission start myapp-1
COMMISSION_ACTOR=claude:api commission check myapp-1 0
COMMISSION_ACTOR=claude:api commission done myapp-1
cannot complete myapp-1: 1 outstanding approval(s)
  missing: approval from a human — autonomy is supervised, so a human must accept the work
hint: a person has to accept this — blaze or priya: COMMISSION_ACTOR=blaze commission approve myapp-1 -m "<why it is acceptable>"

Every criterion is ticked and every gate artifact is attached. The task is technically complete and still cannot close. The agent cannot fix this by trying harder, and cannot fix it with --force:

COMMISSION_ACTOR=claude:api commission approve myapp-1 -m "lgtm"
claude:api cannot approve myapp-1: approval is a human judgement
  missing: a human actor
hint: a human runs this — blaze or priya: COMMISSION_ACTOR=blaze commission approve myapp-1 -m "<why>"

So the agent hands it over instead of closing it:

COMMISSION_ACTOR=claude:api commission review myapp-1

And that is the step that puts it in front of a person. An outstanding approval on an in_progress task is not in anybody’s inbox — the work is still being done, and interrupting a human about it would be noise. Moving it to in_review is the request:

COMMISSION_ACTOR=priya commission inbox
1 thing needs your judgment in myapp — 1 approval
scoped to priya (--all shows every actor's) · 0 in flight · 0 ready to pick up

1. [approve] myapp-1 · Rotate the signing key
   just now
   what       an agent finished it and put it up for review; 1 outstanding approval
   needs you  autonomy is supervised, so a human must accept the work; no agent can satisfy it
   if ignored it stays finished but unaccepted, and cannot close
   →  commission approve myapp-1 -m "<why it is acceptable>"

Four questions and one command. The person answers:

COMMISSION_ACTOR=priya commission approve myapp-1 -m "audit log confirms the old key is revoked"
myapp-1 approved by priya
nothing else is waiting on judgement: commission done myapp-1

And the agent closes it.

Approval is bound to a version of the work

Approve a task, then change what was approved, and the approval goes stale:

cannot complete myapp-1: 1 outstanding approval(s)
  missing: approval from a human (priya approved an earlier version — the task changed since) — autonomy is supervised, so a human must accept the work

“Changed” is deliberately narrow: creation, edits, criteria changes, and dependency changes invalidate an approval. Notes and claims do not. Leaving a breadcrumb is not a change to the work, and if it invalidated sign-off people would learn to stop leaving breadcrumbs — or to stop approving. The trigger is an event id, not a timestamp, so an edit landing in the same millisecond as an approval cannot slip through looking unchanged.

Verification, when it is not a judgement call

verification is the second kind of sign-off: a human confirming something is true rather than deciding whether it is acceptable.

commission verify myapp-1 -m "ran the migration against a production snapshot"

Unlike approval, verification can be delegated to a non-human actor that has been given the verify capability explicitly. Approval cannot — that is the whole difference between the two.

Note the current limit: commission verify refuses unless there is an outstanding verification requirement, and the only way to declare one is execution.reviewPolicy / execution.requireApproval with "kind": "verification" — which is the config block that is not yet applied (below). Until that closes, verification is reachable in the data model and not from a config file.

Escalation: asking instead of guessing

An agent that hits a judgement call has three options, and two of them are bad. It can invent an answer, which is how a task graph fills with assumptions nobody can trace. It can stop, and leave a human wondering why nothing is moving. Or it can ask:

commission ask myapp-1 -m "Reject unsigned webhooks, or log and continue?" --options "reject,log" --blocking
asked on myapp-1 (blocking — agents will not work it until answered)
a human answers with: commission answer myapp-1 -m "<the decision, and why>"

--blocking is a control, not a label. While the question is open, an agent’s claim is refused and the refusal quotes the question. Humans are unaffected — the person who can answer it is exactly the person who might want to work on it.

The question appears in the inbox as a [decide] item, and the answer is the valuable part:

commission answer myapp-1 -m "reject — a logged-and-continued forgery is worse than a dropped event"

The answer is attached to the task, indexed for search, and carried in every future context bundle as a decision. The reasoning behind a decision is normally the first thing lost between sessions; this is where it stops being lost.

What is not enforced yet

The execution block in .commission.jsondefaultAutonomy, requireApproval, reviewPolicy, project-wide mayExecute — validates, appears in commission config show, and is attributed correctly by commission config explain, but does not change behaviour. Execution policy is read from the project row, and only integrations and actors are reconciled into it from the file.

Set policy per task until that closes:

commission edit myapp-1 --autonomy supervised
commission edit myapp-1 --may-execute security
commission edit myapp-1 --preferred-actor claude:api

Stated here rather than left to be discovered, because a policy file that validates and does nothing is worse than no policy file. See worked configurations.

The whole sequence

commission actors
commission edit myapp-1 --autonomy supervised
COMMISSION_ACTOR=claude:api commission start myapp-1
COMMISSION_ACTOR=claude:api commission check myapp-1 0
COMMISSION_ACTOR=claude:api commission done myapp-1
COMMISSION_ACTOR=claude:api commission review myapp-1
COMMISSION_ACTOR=priya commission inbox
COMMISSION_ACTOR=priya commission approve myapp-1 -m "audit log confirms the old key is revoked"
COMMISSION_ACTOR=claude:api commission done myapp-1

Every command on this page is executed by tests/docs.test.ts against a scratch database.


Part of Guides.

This page is docs/guides/execution-policies.md in the Commission repository, rendered in place — the site keeps no copy of it. The repository is private, so there is no edit link to follow.