Docs

Executing

The Operator

The Operator runs planned tasks through the controller with six task tools: start, status, pull-request read, escalation read, resume and cancel. It decides when a task runs, never what it contains, and never plans, codes, rules, approves or merges.

Six tools
Process authority
Agent
MCP

About the Operator

The Operator is a Vinaya role, played by an agent (see Roles). It has process authority, not content authority: it decides when a task runs, pauses, resumes or stops, never what the task contains.

Its grant is exactly the six task tools. Every other call is refused: no shell, no forge write, no Issue edit.

One MCP server serves the tools (vinaya task-tools serve). It is registered for Claude through .mcp.json and for Codex through [mcp_servers] in TOML.

SeatRelation to the Operator
PrincipalNames which planned task to run, pause, resume or cancel; rules on the pauses the Operator presents; merges
PlannerOwns scope and readiness. The Operator runs what the Planner cut.
ControllerThe one controller the Operator operates. The Operator never runs a second one.

How it works

  1. 1

    Start the task

    task_start launches the run in the background and waits, bounded, until the controller holds the task lock.

  2. 2

    Watch it

    task_status shows each task's state and the one next action for it.

  3. 3

    Read the pull request or the pause

    task_pr_read reads checks and reviews; task_escalation_read reads the pause packet to present to the Principal.

  4. 4

    Continue or stop

    After the Principal rules, task_resume or task_cancel acts on the pause.

The six tools

Every tool that refers to a task takes the same address: { tranche, id } for a tranche task (branch task/<tranche>/<n>), or { issue } for a standalone Issue (branch task/issue-<n>). Both forms are strict.

ToolInputOutput
task_start{ tranche, id } or { issue }{ requestId, run, started, startedAt, mode: 'attended' }
task_status{ task?, cursor?, limit? } (limit default 20, max 100){ items[], nextCursor, table }
task_pr_read{ task, pr? } (pr is only a cross-check){ task, issue, pr, head, checks[], review, observedAt, freshness }
task_escalation_read{ task, cursor?, limit? }{ items[] (packets), nextCursor, observedAt, freshness }
task_resume{ task }{ task, pr, escalationId, outcome: 'started' | 'already_resumed', authenticatedBy, authenticatedFrom }
task_cancel{ task, reason }{ task, pr, escalationId, outcome: 'confirmed' | 'pending' | 'uncertain', authenticatedBy, authenticatedFrom, fencedEffectKeys[] }

How task_start runs a task

task_start does the same as vinaya task run. It writes a durable claim, then launches one of these in the background:

vinaya task run <tranche> <n> --agent <agent>
vinaya task run --issue <n> --agent <agent>

It then waits, bounded, for the driver lock to name a live process.

Wait outcomeResult
The lock appears with a live processConfirmed start
The process exits or errors firstAn infrastructure error with the run's stderr; the claim is released
The wait runs out and the process is aliveReported started; the claim is kept

The request identity is the caller, repository, target and a digest of the payload, including the address form. A repeat with the same identity replays the same run. A stale claim whose launch is gone is superseded and launches again.

task_start also continues a task that is:

  • not started, start did not come up, exited, no driver or published;
  • paused, already ruled on, with a dead driver;
  • in an infrastructure pause within the retry bound;
  • paused with a Principal ruling posted that is newer than the pause (it hands this to task_resume's authentication).

An exited run re-attaches to the task's open pull request rather than opening a second one.

One action per state

task_status stateOperator actionPrincipal next
not startedtask_startstart
startingtask_statuswait
start did not come uptask_startstart
runningtask_statuswait
pausedtask_start (continues, or refuses naming the tool that moves it)rule, start, cancel or investigate, by what the pause waits for
publishedtask_pr_readwait
exitedtask_startstart
no drivertask_startstart

merge replaces the row's word when the review gate is green and both verdicts on the head are APPROVE and PASS, unless a driver is live, the pause was cancelled, or a record is unreadable.

Tranches and batches

The Operator runs one explicitly selected task. For a tranche, it starts each dispatchable task itself. Batch start and scheduling by the controller are planned; see Batches. For a tranche walk-through, see Deploy medium work.

Refusals

ToolRefuses whenKind
task_startThere is no authenticated caller in the server's environment (VINAYA_MCP_CALLER). It is never read from an argument.authority
task_startNo dispatch.agent is configured (checked before claiming)precondition
task_start{ issue } names no Issue, a closed one, or one with a vinaya/tranche:* label (the refusal names the tranche form)precondition
task_startThe state is running or starting (names task_status)precondition
task_startA pause awaits a decision with no ruling posted (names task_resume, where the ruling goes, its marker and the command); the pause was resolved as cancel (names task_cancel); a record is unreadable (names the reads)precondition
task_statusNever refuses a valid read. An absent fact is null; a read not attempted is not read.—
task_pr_readpr is not the task's own pull requestauthority
task_escalation_readNever: with no pause, it returns an empty page with unknown freshness, not an error—
task_resumeThe pause is missing or superseded by a later published round; the escalation record is missing or targets something else; a driver is liveprecondition
task_resumeNo Principal ruling is read fresh from the pull request (or from the Issue, for a pause with no pull request)authority
task_resumeA pre-pull-request widen_surface escalation (names the Planner's vinaya task run … --widen-surface <glob,...> --reason <text>)—
task_resumeAn infrastructure pause within its bound (it would want a ruling nobody posts; task_start continues it)authority
task_resumeThe pause was already ruled on: answers already_resumed and launches nothing—
task_cancelNothing to cancel (never started, live driver, published, exited with no pause); the escalation was already consumed as a resumeprecondition
task_cancelNo Principal ruling; a wrong-target, stale or replayed resolutionauthority / precondition

Gates

The Operator runs no gate. Its own boundaries are mechanical:

  • the grant check that refuses any tool outside the six;
  • the caller check on task_start;
  • the claim and driver-lock checks, which stop a second developer on one branch;
  • Principal-ruling authentication on resume and cancel (see Pauses and rulings).

Before the Operator starts a task, the task must be planned and dispatchable: the Issue exists, its dependencies are merged, and no conflicting sibling pull request is open.

Configuration

Key or variableEffect
dispatch.agentThe agent task_start and task_resume launch. The tools take no agent field.
VINAYA_MCP_CALLERThe caller identity the server reads once. Without it, only task_start refuses.
VINAYA_TASK_RUN_COMMANDOverrides the background launch, for example with a supervisor wrapper
principalsWhose ruling comments authenticate resume and cancel

What gets logged

Every Operator call emits operation · completed with the tool, the target and the outcome: ok, error, refused, timeout, cancelled or unavailable.

A resume the Operator triggers produces dev_review_loop · resumed and handoff · resolved from the loop. A cancel produces dev_review_loop · cancelled. See Events.

What's next