Skip to main content

Executors

An executor is the binary vagaris dev runs once a lease is verified. The CLI knows an executor through a small registry entry: the binary name, the class of situation it is for, a readiness report it makes about itself, and a function that turns the lease's tool policy into that binary's own allowlist flags. dev consumes nothing else, which is what lets a second executor be added without changing dev.

The two classes, and why the distinction is not cosmetic​

ClassRunsReady when
interactive_localbeside a person, on their credential and subscriptionthat person is present — there is no service identity behind it, no budget it can report and no expiry it can read
unattended_machinewith no person presentit holds a service identity of its own, with an explicit budget, quota reporting, credential health and a machine-readable expiry

The class is a property of the executor, not of the mission. The same mission dispatched to each differs in whether anyone is there to answer a prompt, and that is not something a mission can supply.

An interactive_local executor is never dispatched an unattended mission — not because it would refuse, but because it would wait, and a run blocked on a prompt nobody will answer is indistinguishable from one that is working.

Registered executors​

--adapter valueClassBinaryHow the lease tool policy is passed
claude-local (also claude_local)interactive_localclaudeclaude --allowedTools <tool> <tool> ... — a space-joined list
codex-local (also codex_local)interactive_localcodexcodex --tool a,b,c — a comma-joined list
spine-unattended (also spine_unattended)unattended_machinevagaris-execvagaris-exec --allow-tools a,b,c — a comma-joined list

claude-local is the default when --adapter is omitted. Underscores and hyphens are interchangeable and case is ignored.

Any other adapter type — including the adapters the server can run, such as gemini-local, cursor-local or process — is refused by dev with no CLI-hosted executor for adapter '<type>'. The refusal is deliberate: the CLI will not spawn an executor it cannot bound with an allowlist.

What the lease policy means for each executor​

The lease's tool axis is one of three things:

  • none — the spawn is refused (lease A3 axis denies all tools). This is also what a lease with no tool policy becomes.
  • a non-empty list — passed as the executor's allowlist, exactly as shown above.
  • for codex-local, a missing list is also a refusal (lease carries no token allowlist for codex).

The CLI never passes claude --dangerously-skip-permissions. The curated sandbox allowlist that server-side sandbox runs use is not on the CLI path either; the lease is the only source of permission.

Readiness is reported, never inferred​

Before every spawn dev asks the executor whether it can carry a mission. The answer comes from the executor, because only it knows. An absent report is refused rather than assumed ready.

Vagaris used to read credential files it could not see from inside a container and conclude readiness from their absence — which returns the same answer for no credential, a credential somewhere I cannot see, and a credential I am not allowed to read. Those are three different facts and only one of them means not-ready.

Three values carry that honesty:

  • ready — can it run right now.
  • credentialExpiresAt — null means it has no expiring credential; undefined means it could not tell. Reporting null when nobody stamped an expiry is a stronger claim than the executor has evidence for.
  • budget.state — unknown is not unlimited. A budget nobody reported is not an absent cap, and an unattended run that assumes otherwise discovers the cap by hitting it.

spine-unattended reports two gaps separately, because they send you to different places: the workload identity is not bound yet is a conferral gap on the identity plane, while vagaris-exec is not on PATH is a packaging gap on this machine. Collapsing them into one boolean is exactly the complaint this contract was written to answer.

The unattended executor's identity​

spine-unattended authenticates as its own service principal, Vagaris Spine Executor. It does not borrow a person's credential and it is not Vagaris Cortex.

That separation is the design, not an implementation detail. Cortex owns cognition and missions; the executor claims and performs. A binding answers who is actually allowed to act, so collapsing the two would make a control look complete while contaminating audit, metering, revocation, attribution and least-privilege.

Two consequences worth stating plainly:

  • The scope it is granted is not the authority needed to grant it. Conferring its workload binding is governance-sensitive and floors at keystone; the binding that act produces carries only capability.execution.claim. The executor must never be given capability.governance.decide or capability.governance.import to make a dispatch succeed — if a run needs those, this is the wrong actor for that work.
  • Placements get separate bindings. executor:github-ci, executor:fleet and executor:customer-node belong to one executor family but are conferred individually, so each stays independently attestable and revocable. One broad subject stretched across them would make a single compromised placement indistinguishable from the others in an audit.

Instantiating the principal and conferring a placement binding are authority acts, not code changes. Until they exist, spine-unattended reports not-ready and names the missing session — which is the correct state, not a defect.

Configuring the unattended executor​

Set by the machine path that already authenticated upstream — a CI OIDC exchange, or a node's enrolment — not by a person editing a file.

VariableMeaning
VAGARIS_WORKLOAD_SESSIONthe session the workload authenticates with. No fallback: unset means not-ready
VAGARIS_WORKLOAD_SESSION_EXPIRES_ATepoch seconds (ISO accepted). A passed expiry refuses the run
VAGARIS_WORKLOAD_SESSION_SCOPESspace-separated scopes actually granted. Must include capability.execution.claim
VAGARY_GATEWAY_BASEorigin of the provider-gateway (POST /v1/llm/chat). Defaults to http://127.0.0.1:3014

Unlike every other executor, vagaris-exec wraps no vendor CLI. It takes its cognition from the canonical Vagary provider-gateway, so the same mission resolves against whichever model the gateway routes to with no architectural difference — and provider API credentials stay behind the gateway. The runner authenticates to Vagary, never to a provider, and never sees ANTHROPIC_API_KEY or its siblings. That is what makes a customer deployment of this possible.

Its refusals carry distinct exit codes, so an unattended failure is triageable without guessing:

CodeMeaning
2the arguments were malformed, or an unrecognised flag was passed — refused, never ignored
3no workload identity (readiness) — no gateway request is made
4no tool allowlist (deny-default) — no gateway request is made
5the gateway refused or failed; a 401 here is a finding about the binding, not about the gateway being down

Adding an executor​

A packaged adapter registers itself with registerCliHostedExecutor(adapterType, { binary, executorClass, reportReadiness, leaseAllowlistArgs }) from the CLI's executor registry; the function returns a disposer so a temporary registration (a test, a plugin unload) can restore the previous entry. The leaseAllowlistArgs function must return { refused: true } for a none policy — an executor that cannot express an allowlist must refuse, never run open. Declare executorClass honestly: an interactive_local executor stretched to cover an unattended mission is the failure this contract exists to prevent, and a new class member needs its own service identity rather than a borrowed one.

Executor requirements on the machine​

  • The binary must be on PATH in the shell that runs vagaris dev. A missing binary surfaces as spawn exit code 127 in the report.
  • Provider credentials (for example ANTHROPIC_API_KEY) must be present in that shell; see Model Providers. Note that the environment is sanitised before the spawn — product variables and interpreter hooks are stripped, provider keys are not.
  • git must be available for the per-run worktree.

Server-side adapters​

Runs triggered with vagaris heartbeat run or by the scheduler execute on the server with the adapter configured on the agent. Those adapters are documented under Adapters. The CLI bundles the local adapters (Claude, Codex, Cursor, Gemini, Grok, OpenCode, Pi, ACPX and the OpenClaw gateway) so vagaris run can start a server that has them.