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
| Class | Runs | Ready when |
|---|---|---|
interactive_local | beside a person, on their credential and subscription | that person is present — there is no service identity behind it, no budget it can report and no expiry it can read |
unattended_machine | with no person present | it 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 value | Class | Binary | How the lease tool policy is passed |
|---|---|---|---|
claude-local (also claude_local) | interactive_local | claude | claude --allowedTools <tool> <tool> ... — a space-joined list |
codex-local (also codex_local) | interactive_local | codex | codex --tool a,b,c — a comma-joined list |
spine-unattended (also spine_unattended) | unattended_machine | vagaris-exec | vagaris-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—nullmeans it has no expiring credential;undefinedmeans it could not tell. Reportingnullwhen nobody stamped an expiry is a stronger claim than the executor has evidence for.budget.state—unknownis notunlimited. 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 givencapability.governance.decideorcapability.governance.importto 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:fleetandexecutor:customer-nodebelong 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.
| Variable | Meaning |
|---|---|
VAGARIS_WORKLOAD_SESSION | the session the workload authenticates with. No fallback: unset means not-ready |
VAGARIS_WORKLOAD_SESSION_EXPIRES_AT | epoch seconds (ISO accepted). A passed expiry refuses the run |
VAGARIS_WORKLOAD_SESSION_SCOPES | space-separated scopes actually granted. Must include capability.execution.claim |
VAGARY_GATEWAY_BASE | origin 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:
| Code | Meaning |
|---|---|
2 | the arguments were malformed, or an unrecognised flag was passed — refused, never ignored |
3 | no workload identity (readiness) — no gateway request is made |
4 | no tool allowlist (deny-default) — no gateway request is made |
5 | the 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
PATHin the shell that runsvagaris dev. A missing binary surfaces as spawn exit code127in 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. gitmust 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.