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-). It is registered for Claude through .mcp.json and for Codex through [mcp_servers] in TOML.
| Seat | Relation to the Operator |
|---|---|
| Principal | Names which planned task to run, pause, resume or cancel; rules on the pauses the Operator presents; merges |
| Planner | Owns scope and readiness. The Operator runs what the Planner cut. |
| Controller | The one controller the Operator operates. The Operator never runs a second one. |
How it works
- 1
Start the task
task_startlaunches the run in the background and waits, bounded, until the controller holds the task lock. - 2
Watch it
task_statusshows each task's state and the one next action for it. - 3
Read the pull request or the pause
task_pr_readreads checks and reviews;task_reads the pause packet to present to the Principal.escalation_ read - 4
Continue or stop
After the Principal rules,
task_resumeortask_cancelacts 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/), or { issue } for a standalone Issue (branch task/issue-<n>). Both forms are strict.
| Tool | Input | Output |
|---|---|---|
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_ | { task, cursor?, limit? } | { items[] (packets), nextCursor, observedAt, freshness } |
task_resume | { task } | { task, pr, escalationId, outcome: 'started' | 'already_ |
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 outcome | Result |
|---|---|
| The lock appears with a live process | Confirmed start |
| The process exits or errors first | An infrastructure error with the run's stderr; the claim is released |
| The wait runs out and the process is alive | Reported 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 driverorpublished; - paused, already ruled on, with a dead driver;
- in an
infrastructurepause 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 state | Operator action | Principal next |
|---|---|---|
| not started | task_start | start |
| starting | task_status | wait |
| start did not come up | task_start | start |
| running | task_status | wait |
| paused | task_start (continues, or refuses naming the tool that moves it) | rule, start, cancel or investigate, by what the pause waits for |
| published | task_pr_read | wait |
| exited | task_start | start |
| no driver | task_start | start |
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
| Tool | Refuses when | Kind |
|---|---|---|
task_start | There is no authenticated caller in the server's environment (VINAYA_). It is never read from an argument. | authority |
task_start | No 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_start | The state is running or starting (names task_status) | precondition |
task_start | A 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_status | Never refuses a valid read. An absent fact is null; a read not attempted is not read. | — |
task_pr_read | pr is not the task's own pull request | authority |
task_ | Never: with no pause, it returns an empty page with unknown freshness, not an error | — |
task_resume | The pause is missing or superseded by a later published round; the escalation record is missing or targets something else; a driver is live | precondition |
task_resume | No Principal ruling is read fresh from the pull request (or from the Issue, for a pause with no pull request) | authority |
task_resume | A pre-pull-request widen_surface escalation (names the Planner's vinaya task run … -) | — |
task_resume | An infrastructure pause within its bound (it would want a ruling nobody posts; task_start continues it) | authority |
task_resume | The pause was already ruled on: answers already_resumed and launches nothing | — |
task_cancel | Nothing to cancel (never started, live driver, published, exited with no pause); the escalation was already consumed as a resume | precondition |
task_cancel | No Principal ruling; a wrong-target, stale or replayed resolution | authority / 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 variable | Effect |
|---|---|
dispatch.agent | The agent task_start and task_resume launch. The tools take no agent field. |
VINAYA_ | The caller identity the server reads once. Without it, only task_start refuses. |
VINAYA_ | Overrides the background launch, for example with a supervisor wrapper |
principals | Whose 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.