CLI reference¶
[!IMPORTANT] Work in progress — not a final product: The
vibeyandkryptoncommand-line interfaces are in active development and not yet a final product. Commands, subcommands, and flags are actively evolving. Folks will be notified on the Discord server once the product is finally done.Bottom Line Up Front (BLUF) — Install the
vibeyandkryptonCLIs and every krypton app:```bash
1. Install vibey-engine (vibey + all *loop runners + tools) AND krypton-app (krypton launcher):¶
curl -fsSL https://raw.githubusercontent.com/the-vibey-project/vibey/main/scripts/install.sh | sh -s -- --with-krypton
2. Or install with uv / pipx / pip directly:¶
uv tool install vibey-engine && uv tool install krypton-app
3. Connect your frontier LLM & IDE chat clients (Claude Code, Claude Desktop, Codex, Cursor, VS Code, Gemini CLI):¶
vibey connect --yes ```
For click-by-click and copy-and-paste instructions for krypton desktop (macOS
.dmg, Linux Flatpak, Ubuntu.tar.gz), krypton for VS Code / VSCodium / Cursor, krypton web, and krypton mobile (Android.apk& iOS), see Install, copy, reinstall, uninstall and Downloads.
Every vibey command and subcommand, written by hand against
src/vibey/cli/main.py
and checked against it as of 2026-09-29. Nothing generates this page. If it
and the code disagree, the code wins. vibey <command> --help prints the
code's own help text, which is shorter than this page and in places less
complete (for example, vibey work --help does not say that qwenloop is still
accepted as the old name of gptossloop).
vibey --help groups the top-level commands into six panels, in the order a newcomer
needs them (the footer of that help holds four commands to copy). Commands that are
command groups are marked with a trailing ….
- Start here:
connect,doctor,install,new,projects,status - Decisions that are yours:
gates,answer,design…,visual…,deploy… - Watch and steer:
watch,queue…,engines,loops,models,cost,budget…,ledger…,ultra…,retry,recover,abandon - Run the machinery:
work,worker,serve,hub…,supervisor…,driver…,operator - Maintain:
migrate,state…,sabbath,explain - Tools for agents:
mcp,search,fetch,llm…
Bare vibey, and each bare command group, prints help.
Commands that read or write project state need VIBEY_PG_URL (see
Environment variables). doctor needs it only
with --record or --cluster.
vibey budget is a command group too, and the one whose
bare form does not print help: bare vibey budget shows the latest project's budget.
Global options¶
These apply to every command; they must come before the subcommand name.
| Option | Default | What it does |
|---|---|---|
--version |
— | Print vibey <version> and exit. |
--verbose / -v (repeatable) |
0 |
-v debug, -vv also third-party libraries, -vvv full payloads. |
--quiet / -q |
off | Warnings and errors only. |
--log-level LEVEL |
unset | DEBUG, INFO, WARNING, ERROR, or CRITICAL (case-insensitive). Overrides the level -v or -q would pick. |
--log-file PATH |
unset | Also write redacted JSON lines to this file. |
--workflows / -w |
off | Run the command on this repository's GitHub-hosted runners instead of here (below). |
--install-completion |
— | Install shell completion for the current shell. |
--show-completion |
— | Print the completion script for the current shell. |
--help |
— | Show help and exit. |
-v and -q are mutually exclusive: together they print
Error: --quiet and --verbose are mutually exclusive and exit 2. An unknown
--log-level exits 2 with Error: invalid log level .... --log-level
fixes the level, but the -v count still widens scope, so
--log-level WARNING -vvv means warnings from third-party libraries too,
with payloads.
Running a command on the workflows¶
vibey -w <command> (or --workflows) sends the command line, without the flag, to
.github/workflows/vibey-remote.yml, which runs it with vibey on a GitHub-hosted runner and
hands back what it printed and its exit code (ADR-0085). vibey -w prints them as the command
would have (stdout to stdout, stderr to stderr), says where it ran on stderr, and exits with the
command's own code: vibey -w status --json | jq . reads exactly as vibey status --json | jq ..
The flag is read only among the global options before the command: vibey status -w passes
-w to status. It needs the GitHub CLI (gh), logged in, with actions: write on the
repository. The runner starts from an empty, freshly migrated PostgreSQL unless a private
repository declares VIBEY_WORKFLOWS_PG_URL as a secret (a public one never uses it: its runs
can be read by others), and holds no engine key or model, so a command
that needs either fails there as it would on a host without them. Its exit codes beyond the
command's own: 2 for a command line that cannot be sent (a nested -w, a NUL, more than 4,000
characters) or invalid VIBEY_WORKFLOWS_* settings; 1 when GitHub refused the dispatch or the
run ended without a report; 124 when the wait ran out, with the run's address so it can be
followed. Every krypton interface reaches the same path through the hub's
/api/v1/workflows/runs routes, which need the workflows scope and the scope of what the
command does (view for status, answer and spend for answer, every scope for a command
the policy does not name), and refuse vibey migrate and the vibey budget commands that change
caps. A device reads back only the runs it started: the hub binds each request id it mints to
the device that asked, and answers any other id 404, as it answers an unknown one. The host
reads any run.
Exit codes and errors¶
| Code | Meaning |
|---|---|
0 |
Success. Also a guarded command whose reader closed the pipe early. |
1 |
Nothing to act on, or a check failed: no project exists (no projects found; create one with `vibey new` first); an explicit PROJECT_ID is unknown in watch, cost, gates (said on stderr), abandon, or deploy *; recover without --project or --all; doctor --engine with an unknown name; doctor --conformance with a failing engine; doctor --install-postgres or install --postgres could not install/start a supported server; doctor --cluster with a failing check; operator without the operator extra; worker --azure az without a logged-in Azure CLI. |
2 |
Usage error: a bad global flag (see above); typer's own validation (missing argument, malformed UUID, a value outside an option's minimum or maximum, unknown option); install without --postgres; doctor --install-postgres with --cluster; new --skills-context-mode outside off/shadow/inject; answer mode conflicts or a --raw value that is not a JSON object; abandon without --reason, or with a --reason or --by that cannot be recorded; worker with an unknown --engines id, an --engines list matching none of the worker's engines, an unknown --provider, or an unknown --azure value; doctor --cluster with an unknown --engines id or --provider; doctor --engines or --provider without --cluster; doctor --record whose target project declares a forbidden engine_environment entry; new whose vibey.toml declares a malformed or forbidden [gates] or [engine_environment] entry. |
3 |
Blocked by a domain rule, in a guarded command. Prints Error: <message> on stderr, plus a next-step hint for some error types and, last, the error's permanent code ((E0040: run `vibey explain E0040`)). See Error codes. |
130 |
Interrupted with Ctrl-C, in a guarded command (prints Interrupted.). |
78 |
A supervisor setting no service could run with (EX_CONFIG): supervisor install or supervisor status with an unreadable [supervisor] table, a path on volatile storage or inside a linked worktree, vibey not on PATH, or no delivery bridge under --repo; supervisor exec with a missing or malformed environment file. |
75 |
Resting for the Sabbath (sub-doctrine 8.i, ADR-0072): new or work declined, said why and when it resumes, and changed nothing. Paused, not failed. |
Guarded and unguarded commands¶
Fourteen commands run inside vibey.cli.errors.guard(): new, projects,
gates, design resume, design accept, work, loops, visual accept,
visual waive, queue bump, queue unbump, queue list, queue reap, and
worker. For these, any VibeyError — an unknown project or provider,
a wrong phase, an invalid spec, no eligible engine, a rejected handoff, an
unset VIBEY_PG_URL — becomes the one-line Error: message and exit 3.
Ctrl-C exits 130 and a closed pipe exits 0. Exceptions that are not
VibeyError keep their Python traceback.
budget, budget show, budget set and budget clear run inside guard() as
well, and so does abandon.
engines and cost run inside guard() too (they did not before #1686), so an unset
VIBEY_PG_URL is the one-line Error: message and exit 3 for them as well. Their
--traceback option restores the old behaviour for a caller that relied on it: a
VibeyError then raises with its Python traceback and no code line. It changes nothing
when there is no error. --traceback is on those two commands only.
The other commands (answer, watch, recover, status,
ledger show, every deploy subcommand, doctor, operator) are
not guarded. Their own checks exit 1 or 2 as listed above; any other error,
including an unset VIBEY_PG_URL, surfaces as a Python traceback.
Known gaps in unknown-project handling:
status <unknown-id>raisesValueError: unknown project ...as a traceback.engines <unknown-id>printsno engines recorded for projectand exits 0.ledger show <unknown-id>prints nothing and exits 0.
vibey connect¶
Register vibey mcp in your frontier LLM chat and IDE clients so you can
drive vibey directly from the conversation you already have open
(ADR-0100).
vibey connect --dry-run
vibey connect --yes
vibey connect --client claude_code --client cursor --yes
vibey connect --list
| Option | Default | What it does |
|---|---|---|
--client ID / -c ID (repeatable) |
all supported clients | Client id to configure (claude_code, claude_desktop, codex, cursor, vscode, gemini_cli, or all). An unknown id prints Error: unknown client ... on stderr and exits 2. |
--yes / -y |
off | Apply the planned configuration changes without interactive confirmation. |
--dry-run |
off | Print the planned actions (create, update, unchanged) and target paths without touching any file. |
--list |
off | List every supported host client, its scope and its config target, then exit 0. |
Before writing an existing config file, vibey connect copies the original to
<path>.vibey.bak and merges the vibey MCP server entry idempotently without disturbing
other servers or settings. Re-running vibey connect when every selected client already
carries the entry prints All selected clients are already connected. and exits 0. Declining
the interactive prompt exits 1 without modifying any file.
vibey mcp¶
Run vibey's Model Context Protocol (MCP) server over standard input and standard output
(JSON-RPC 2.0, protocol 2024-11-05;
ADR-0100).
vibey mcp
vibey mcp takes no options. It is a thin client of the local hub API (/api/v1) and
authenticates with <state_dir>/agent.token as the scoped agent principal
([hub] agent_scopes: view, answer and run by default —
never spend, bump or workflows). If no hub is running when a tool is called, it runs
vibey hub start once and retries. It exposes 14 tools (vibey_projects, vibey_status,
vibey_gates, vibey_answer_gate, vibey_budget, vibey_queue, vibey_ledger,
vibey_loops, vibey_lanes, vibey_doctor, vibey_start_project, vibey_start_work,
vibey_pause_work, and vibey_abandon_project where dry_run defaults to true).
vibey install¶
Install local dependencies that vibey can manage explicitly.
vibey install --postgres
| Option | Default | What it does |
|---|---|---|
--postgres |
off | Install and start the current stable PostgreSQL major using Homebrew, apt, or dnf. |
The installer targets PostgreSQL 18 today. The application floor is
PostgreSQL 14, and the same SQL/runtime contract is tested against 14, 15, 16,
17, and 18 in CI. Installation does not invent or persist a database DSN:
set VIBEY_PG_URL to a database you own after the server is ready.
Examples in --help¶
new, answer, gates, projects, doctor and watch each end their --help with one or two
worked examples. They are one table (vibey.cli.command_examples.EXAMPLES), and
tests/cli/test_command_examples.py resolves every line against the real command tree with each
command's own parser, without running it, so a renamed flag or a removed command fails a test
instead of leaving help that no longer works. In those commands' --help, the rarely used options sit
under named panels after the common ones (new: Spending and turn limits, Skills, Advanced;
doctor: Recording, In-cluster preflight, Capacity probe; answer: Advanced; gates:
Reminders). The shared --ignore-sabbath option sits under Advanced wherever it appears
(new, work, worker). No option was renamed, removed or given a new default.
Error codes and vibey explain¶
Every error a guarded command can print has a permanent code, such as E0040, on the last line
of its output. vibey explain E0040 prints its cause and what to do; vibey explain --list prints
every code and its title; an unknown code exits 2 and names the nearest ones. It reads only the
shipped catalog, so it needs no database and works for the very first error. The full table is the
generated Error codes page. The first line of the output is unchanged, Error:
<message>. Commands that are not guarded, vibey state and the hub's HTTP error bodies do not
print codes yet.
Next-step hints¶
guard() appends a hint for these error types. Every command a hint names
exists, and a test asserts it (tests/cli/test_errors_and_logging.py).
| Error | Hint printed | Notes |
|---|---|---|
NoEligibleEngine |
Every engine is excluded, circuit-open, or missing a capability; run vibey engines or vibey doctor. |
|
BudgetExceeded |
A tripped cap parks a budget_exhausted gate; raise it with vibey answer GATE_ID --raw '{"max_dollars": 25}' (or "max_turns"), and vibey cost shows where the spend went. Ends by naming vibey gates, which lists the gate. |
Nothing in src/vibey raises this error today, and no runtime code reads [budget] from vibey.toml — which is why the hint names neither. |
EscalationExhausted |
The work item failed at every rung of the effort ladder and parked a human gate; vibey answer GATE_ID it. Ends by naming vibey gates. |
|
HandoffRejected |
The no-loss gate refused the handoff and parked a human gate whose prompt says what could not be carried over; vibey answer GATE_ID it. Ends by naming vibey gates. |
|
IllegalTransitionError |
The project is not in a phase this command applies to; vibey status shows the phase. |
|
InvalidSpecError |
Run vibey design to finish the spec before building. |
|
InvalidPhaseError |
Likely a bug in vibey rather than the project. | |
GateAlreadyAnswered |
A gate is answered once and the first answer stands; the hint names vibey ledger search --kind GateAnswered and says a --request-id makes a retry of the same answer a no-op. |
|
UnknownGate |
No gate has that id; the hint names vibey gates. |
|
CheckoutHeld |
Nothing was created; vibey abandon PROJECT_ID --reason TEXT lets the checkout go if the holding project should stop, otherwise start the new one elsewhere with --repo; vibey projects --json lists every project with its checkout and phase. |
Raised by vibey new when a live project holds the checkout. |
vibey new NAME¶
Create a project and enqueue its first DESIGN interview. NAME is a label shown by vibey projects; vibey does not require it to be unique, and the project is identified by its id and its checkout (--repo).
vibey new my-app --repo ~/src/my-app --max-cycle-dollars 15 --max-cycle-turns 40
| Option | Default | What it does |
|---|---|---|
--repo PATH |
. |
Repository the project builds against. |
--intake TEXT |
unset | Initial issue or task text to seed the DESIGN ledger with. Before the DESIGN job is enqueued it is appended as one TranscriptRecorded event with untrusted provenance and the payload {"text": TEXT, "source": "github-issue"}, so the interview starts from it. The triaged-delivery bridge (scripts/triaged_delivery.py) hands a GitHub issue over this way, and only an issue whose author, editors and triage-label curators are trusted, quoted through PromptShield (runbook). |
--max-cycles N |
10 |
Cap on delivery cycles before the project stops (min 1). |
--max-cycle-dollars F |
unset | Per-cycle dollar cap (min 0.01), stored as max_cycle_dollars in project config. Tripping it parks a budget_exhausted gate instead of starting more sessions (ADR-0024). Unset means no dollar cap. |
--max-cycle-turns N |
unset | Per-cycle engine-turn cap (min 1), stored as max_cycle_turns. |
--skills-context-mode {off,shadow,inject} |
off |
vibey-skills retrieval mode (ADR-0031) — see Configuration reference. Any other value exits 2. |
--skills-context-budget N |
6000 |
Token budget for skills retrieval (1,000–32,000). Stored only when the mode is not off. |
--design-default-scope {narrowest,model} |
unset | Whose appetite a DESIGN question's default follows — see [design.interview]. Stored as design.interview.default_scope, over whatever vibey.toml declares; unset keeps the file's value, and with neither the default is narrowest. Any other value exits 2. The triaged-delivery bridge always passes narrowest. |
--accept-spec-json FILE |
unset | Skip DESIGN: the reviewed spec JSON (the file vibey design accept --spec-json takes) is the design. The project is created, moved into DESIGN with no interview job, the spec saved and accepted at once (visual design declined; the ledger names new-with-spec), and BUILD's decomposition enqueued. No interview, no research and no spec synthesis, so nothing waits for a person or a model. A spec that cannot be read is a usage error before any project exists; one the acceptance refuses leaves the project in DESIGN with the spec saved, so vibey design accept PROJECT --spec-json FILE can finish it. Prints design skipped; entered build where the interview prints its job. |
--json |
off | Print one JSON document on stdout instead of the two lines below: {"project_id": ..., "name": ..., "outcome": ...}, where outcome is the second line (design job <id>, or design skipped; entered <phase>). A client such as the editor extension reads the project id from it rather than scraping text. |
Prints project <id> and design job <id>. The project id is the
PROJECT_ID the other commands take; vibey projects
prints it again later.
One checkout holds at most one live project. --repo is stored resolved to an
absolute path, and a checkout a project in any phase but abandoned holds -- done
included -- is refused: Error: checkout <path> is held by live project <id> (phase
<phase>); two live projects never share a checkout, so no project was created, with a
hint naming vibey abandon, and exit 3.
Nothing is created. A checkout whose every earlier project was abandoned is free:
the new project is created beside them, and they stay readable by id.
vibey serve¶
The hub (ADR-0068): the HTTP API every krypton client reaches, built on
vibey_bootstrap. Install it with pip install 'vibey-engine[hub]'. It serves until stopped.
vibey serve --host 127.0.0.1 --port 8765
| Option | Default | What it does |
|---|---|---|
--host ADDRESS |
127.0.0.1 |
Address to listen on. Any address that is not loopback needs [hub] lan = true in vibey.toml; without it the command prints why and exits 2 before opening anything. |
--port PORT |
[hub] port, else 8765 |
Port to listen on. |
--openapi |
off | Print the OpenAPI 3.1 document and exit. Needs no database. The committed copy is hub-api.json. |
At start it prints the API's address and where the host token is. Every route is under
/api/v1 and needs Authorization: Bearer <token>, where the token is the content of
<state_dir>/token (owner-only; state_dir defaults to the platform's state directory,
e.g. ~/Library/Application Support/vibey/hub on macOS). A request whose Host is not
one the hub answers is refused with 421 (the DNS-rebinding defence), and no response ever
carries a CORS header. The routes, scopes and refusals are in the
hub API reference.
While it runs it keeps <state_dir>/serving.json (address, port, process id, and whether
it serves TLS), which vibey doctor's hub-exposure line and vibey hub read.
There is one hub per user. It holds an exclusive lock on <state_dir>/serve.lock for as
long as it runs, so a second vibey serve prints that a hub is already running (with its
process id) and exits 1 without opening anything. The kernel releases the lock when the
hub dies, so a crash never leaves a stale one. To run it in the background, or to stop it,
use vibey hub start and vibey hub stop.
With [hub] lan = true the hub serves its own self-signed certificate (made on first run
in <state_dir>, owner-only), prints its SHA-256 fingerprint, and advertises itself on
the LAN as _vibey._tcp (mDNS/DNS-SD) with that fingerprint. On loopback it serves plain
HTTP and announces nothing.
vibey hub¶
The one hub on this computer: start it, stop it, ask about it, and pair devices with it
(ADR-0071). The pairing subcommands read <state_dir>/serving.json, present the host token,
and -- when the hub serves TLS -- trust only the hub's own certificate. With no hub running
they exit 3.
vibey hub start --timeout 30
Every krypton UI offers the same start, stop and status actions, and they are these
commands. Closing a UI never stops the hub: start launches it detached from the
terminal and the session, its output in <state_dir>/hub.log, and only stop ends it.
stop sends SIGTERM so the hub shuts down cleanly and clears its own record; it never sends
SIGKILL, and a hub that is slow to stop is reported as still stopping (exit 1). A record
whose process is gone is reported as stale, never as a running hub.
| Command | What it does |
|---|---|
vibey hub status |
Says whether the hub runs and where (http://127.0.0.1:8765/api/v1 (pid N)). Exit 3 when it does not. |
vibey hub start [--timeout SECONDS] |
Starts the hub in the background unless one already runs (then it says so and exits 0), and waits, 15 seconds by default, until the hub has said where it listens. Exit 1 if it exits first or does not come up. Reads vibey.toml from the current directory like vibey serve. |
vibey hub stop [--timeout SECONDS] |
Asks the running hub to stop and waits for it to go. Exit 0 when none runs. |
vibey hub pair --scope SCOPE [--scope SCOPE ...] |
Offers a pairing of the named scopes (view, answer, spend, run, bump; at least one) and shows a QR code, the 6-digit code (valid two minutes, once), the pairing URI and the certificate fingerprint. |
vibey hub devices |
Lists the paired devices: id, name, scopes. Never a key. |
vibey hub revoke DEVICE_ID |
Revokes a device. It is refused from its very next request. |
vibey sabbath¶
Print the Sabbath window on this host (sub-doctrine 8.i,
ADR-0072):
whether it is enabled, the zone, where the location came from and how accurate it is, and
when the current or next rest ends. It only reads, and is never held. From sundown Friday
to sundown Saturday vibey new and vibey work decline with exit code 75, the worker
claims no lease, and vibey doctor reports the location source (FAIL when no source could
place the host). --ignore-sabbath on vibey new, vibey work and vibey worker runs
that one command through the window and says so on stderr; no file can supply it. See
[sabbath] and the guide.
vibey sabbath
vibey projects¶
List every project, newest first (by creation time, then id), with its phase, its cycle, and how many gates are waiting for your answer.
vibey projects --json
| Option | Default | What it does |
|---|---|---|
--json |
off | Print a JSON array instead of the table. |
The table has one row per project — NAME, PHASE, CYCLE
(cycle/max_cycles), OPEN GATES, CREATED (UTC), PROJECT ID — then a
line counting them that, when any gate is open, points at vibey gates.
PHASE is the phase's name, such as BUILD. A phase a newer vibey wrote,
which this one has no name for (vibey#287), is shown as its stored text; it
never fails the listing.
--json prints an array, newest first, of one object per project with exactly
these keys:
[
{
"project_id": "0b5c9a4e-1d4c-4c47-9a2a-3c1d2b8f9e10",
"name": "greeter",
"phase": "BUILD",
"cycle": 1,
"max_cycles": 3,
"repo_path": "/Users/me/src/greeter",
"created_at": "2026-09-24T09:00:00.123456+00:00",
"open_gates": 2
}
]
phase follows the table's rule, created_at is ISO-8601 with its offset,
and open_gates counts the project's unanswered gates. With no projects both
forms exit 0: the table form prints
no projects yet; create one with `vibey new <name> --repo <path>` and
--json prints []. An empty list is an answer, not an error — unlike
vibey status, which needs a project to report on and exits 1 without one.
vibey gates [PROJECT_ID]¶
List every open gate — a job parked until a person answers it (ADR-0009) —
oldest first (by raise time, then gate id), across every project or only
PROJECT_ID's. Each gate shows its project's name, its kind, its prompt as
one wrapped paragraph, and the exact command that answers it:
1 open gate, oldest first -- each is a job waiting for your answer:
1. greeter: approval gate, raised 2026-09-24 12:03 UTC
Review artifacts ready for cycle 1. Accept, request changes, or ask
questions.
answer with: vibey answer 5e1f0c7a-8b2d-4e5f-9a61-2c3d4e5f6a7b --verdict accept
vibey gates --remind --dry-run
| Argument / option | Default | What it does |
|---|---|---|
PROJECT_ID |
every project | Only this project's open gates. An unknown id exits 1 with unknown project <id> on stderr and nothing on stdout. |
--json |
off | Print {"gates": [...]} instead. |
--remind |
off | Send the gate notices now due instead of listing: see Reminding about waiting gates. |
--dry-run |
off | With --remind: list the notices that are due and send and record nothing. Without --remind it exits 2 (--dry-run applies only with --remind). |
--json prints one object whose gates array holds, oldest first, one object
per gate with exactly these keys:
{
"gates": [
{
"gate_id": "5e1f0c7a-8b2d-4e5f-9a61-2c3d4e5f6a7b",
"project_id": "0b5c9a4e-1d4c-4c47-9a2a-3c1d2b8f9e10",
"project_name": "greeter",
"job_id": "9d2a7b1c-3e4f-4a5b-8c6d-7e8f9a0b1c2d",
"kind": "approval",
"prompt": "Review artifacts ready for cycle 1. Accept, request changes, or ask questions.",
"options": ["accept", "changes", "cancel"],
"default_answer": null,
"raised_at": "2026-09-24T12:03:04.567890+00:00",
"timeout_at": null,
"answer_with": "vibey answer 5e1f0c7a-8b2d-4e5f-9a61-2c3d4e5f6a7b --verdict accept"
}
]
}
job_id, default_answer and timeout_at are null when the gate has none;
prompt is the gate's own text, line breaks included. With nothing waiting,
both forms exit 0: the text form prints
no open gates: nothing is waiting for your answer and --json prints
{"gates": []}.
Reminding about waiting gates¶
vibey gates --remind runs one gate-reminder sweep -- the one every idle
vibey worker runs for its project at most every [notifications]
sweep_interval_seconds -- so a supervisor can schedule it (cron, a systemd
timer, a Kubernetes CronJob) where no worker idles. Each open gate gets at
most one notice: its raise notice when none is on record, else the reminder
its project's schedule says is now due ([notifications]).
Every notice is recorded on the ledger as GateNotified or
GateNoticeUndeliverable, once per gate and notice number, so running it
twice sends nothing twice. A project whose notifications are off is said once
per gate -- UNDELIVERABLE (disabled) -- and not reminded about.
2 open gates; 2 notices sent
told reminder 1 gate 5e1f0c7a-8b2d-4e5f-9a61-2c3d4e5f6a7b (approval)
UNDELIVERABLE (disabled) raise notice gate 9d2a7b1c-3e4f-4a5b-8c6d-7e8f9a0b1c2d (defect)
[notifications] enabled is false, so nobody will be told this gate is waiting; enable a channel, or watch `vibey gates`
--json prints {"project_id", "dry_run", "waiting", "sent": [...],
"planned": [...], "unreadable": [...], "ok"}; each sent entry is the
recorded payload (gate_id, gate_kind, job_id, notice, and channels
or reason and detail) with project_id and delivered. It exits 1 when a
project's notices on record could not be read: nothing was sent for that
project, and it is named under unreadable.
How each kind of gate is answered¶
answer_with is one line a shell runs as printed: every word is quoted as the
shell needs it. Its form depends on the gate's kind, declared once in
src/vibey/cli/gate_answers.py; a test fails when a gate kind is raised
anywhere in src/vibey without an entry there.
| Kind | answer_with (ID is the gate id) |
Raised by |
|---|---|---|
question |
vibey answer ID --defaults |
The DESIGN interview. Accepts every question's default -- under the default narrowest scope, a question proposing an artefact the intake does not name defaults to "No". |
approval |
vibey answer ID --verdict accept |
REVIEW. Its other options are changes and cancel. |
deploy_demo_review |
vibey answer ID --verdict approve |
The Phase ⑥ demo. Or request_changes. |
choice |
vibey answer ID --choice local_only |
The deployment opt-in after REVIEW. deploy opts in. |
deploy_interview |
vibey answer ID --choice accept_defaults |
The Phase ④ interview. |
deploy_acceptance |
vibey answer ID --choice reject |
Phase ④ spec consent; see below. |
deploy_failure_triage |
vibey answer ID --choice LOOP_DEPLOY_DESIGN |
The Phase ⑥ triage. Or RETRY_DEPLOY_EXECUTE, ABORT_DEPLOYMENT. |
bus_dead_lettered |
vibey answer ID --choice replay, or dismiss when the message cannot be replayed |
A dead-lettered bus message. |
budget_exhausted |
vibey answer ID --raw '{"max_dollars": N}' |
The budget brake. |
escalation_exhausted, attempts_exhausted |
vibey answer ID --raw '{"max_attempts": N}' |
A spent effort ladder; a job out of attempts whose last failures were not all alike. |
publish_interview |
vibey answer ID --choice skip, --choice accept_defaults, or --raw '{"choice": "custom", ...}' |
The last step of a delivered project, with [publish] mode = "interview": its prompt shows exactly what would be pushed and where. accept_defaults publishes that; skip finishes locally and is the declared default, so nothing is published by silence; custom changes only the fields you name (title, body, base, draft, visibility, repo, owner, description). It never times out to a default. |
defect |
vibey answer ID --choice requeue, or --choice abandon |
A job out of attempts whose last [queue.defect] failures were all one failure: no more attempts are offered. requeue runs it again once a fix has landed (a same-failure run parks here again at once); abandon cancels it. |
verify_repair_exhausted, integrate_repair_exhausted |
vibey answer ID --raw '{"max_rounds": N}' |
BUILD's verify and integrate repair loops. |
delivery_exhausted, research_evidence, engine_misconfigured |
vibey answer ID --raw '{}' |
Any answer retries, once the cause outside vibey is fixed. |
foreign_branch |
vibey answer ID --raw '{}' |
BUILD found a branch it cannot prove this project created and touched nothing. Check whose it is, move it aside (git branch -m), then answer: the retry cuts a fresh branch (ADR-0008). |
handoff_gate_failed, too_many_wind_downs, and any kind not listed |
vibey answer ID --raw '<json>' |
Nothing reads a particular answer; you write it. |
A verdict or choice uses the gate's declared default, else its first option;
options in --json lists the rest. N and <json> are placeholders, and
the text form says what to put there. Neither is valid JSON, so a command
pasted without filling it in is refused by vibey answer (exit 2), never
sent. How much more money or how many more tries to grant is a person's
decision, so no number is suggested; the gate's prompt usually proposes one.
deploy_acceptance prints its declared default, reject. Accepting the
deployment spec also takes explicit consent to change real infrastructure,
which no flag sends and answer_with never suggests:
vibey answer ID --raw '{"verdict": "accept", "explicit_mutation_authorized": true}'.
vibey answer GATE_ID [QUESTION_ID=ANSWER ...]¶
Answer a parked human gate. Exactly one of the following modes is required
(or --defaults alone for interview gates):
| Form | Used for |
|---|---|
QUESTION_ID=ANSWER [QUESTION_ID=ANSWER ...] |
Interview gates, answered per question. Combine with --defaults to fill in every other question's default; the explicit pairs win. |
--defaults |
Interview gates: accept every question's default. Question keys are model-minted per run, so this needs none. |
--choice VALUE |
Deployment and triage gates: sends {"choice": VALUE}. |
--verdict VALUE |
Review gates: sends {"verdict": VALUE} — accept, changes, cancel, approve, or request_changes. |
--raw JSON |
Any other gate shape, e.g. raising a tripped budget cap: --raw '{"max_dollars": 25}' or --raw '{"max_turns": 50}'. |
vibey answer 3f9c2a1e --defaults
vibey answer 3f9c2a1e --raw '{"max_dollars": 25}'
| Option | What it does |
|---|---|
--by NAME |
the name the answer is recorded under, for a tool that runs the command (the VS Code extension says vibey-vscode). Defaults to the account running the command. It is a label for the record, not a permission: the account is always recorded beside it. |
--request-id ID |
names this request so a retry is safe. The same id with the same answer is a no-op once it has landed. Without one, every run is a new request. |
vibey gates prints, beside each open gate, the
form that answers it.
Prints answered <gate_id> as <name>. The answer is written in a transaction that sets answered_at only if it was NULL; of two answers racing for one gate exactly one lands, the first answer stands, and the answer and its GateAnswered ledger event are written together. If the same request-id is used again with the same answer it prints already answered <gate_id> by this request; nothing changed and exits 0. The command exits 2 with a one-line message when --defaults is combined with --choice, --verdict, or --raw; when no mode is given or more than one is given; or when --raw is not valid JSON or not a JSON object. Exit 3 produces Error: … and a hint, never a traceback: the gate was already answered (GateAlreadyAnswered); the same --request-id was used with a different answer (also GateAlreadyAnswered); no gate exists (UnknownGate); the --by label is invalid (InvalidActorLabel); or the --request-id is invalid (InvalidAnswer). A positional argument without = raises InvalidAnswer before connection (this is still exit 3).
Finding a gate id¶
vibey gates lists every open gate with its id,
its prompt, and the vibey answer command that answers it;
vibey gates PROJECT_ID lists one project's. The worker also sends
NOTIFY vibey_gate_raised with the gate id when one is raised, for a program
that would rather listen than poll.
vibey abandon PROJECT_ID --reason TEXT¶
Abandon a project that is not going to finish -- built on the wrong spec, superseded,
no longer wanted, or stuck in intake because its dispatch never reached DESIGN. Any
phase short of done can be abandoned. Nothing in vibey abandons a project on its own;
until a person does, anything waiting on it waits (the triaged-delivery bridge holds its
one delivery slot for a dispatched project until it is done or abandoned).
vibey abandon 0b5c9a4e-1d4c-4c47-9a2a-3c1d2b8f9e10 --reason "built on a foreign spec" --dry-run
| Option | Default | What it does |
|---|---|---|
--reason TEXT |
required | Why. Recorded on the project's ledger with the move. Visible text, up to 2000 characters. |
--by NAME |
the account running it | The name the abandonment is recorded under, for a tool that runs the command. A label for the record, not a permission: the account is always recorded beside it. |
--dry-run |
off | List the jobs that would be cancelled and the gates that would be withdrawn; write nothing. It checks what the real run checks, so a refusal is the same refusal. |
--json |
off | Print JSON instead (below). |
In one transaction, under a lock on the project's row:
- every open human gate of the project is withdrawn: the gate row records
{"withdrawn": true, "reason": "project abandoned"}as its answer, the name above asanswered_byandabandon:<project_id>as its request id, so a latervibey answerto it is refused as a second answer. No row is deleted; - every job that could still run --
ready,leased,awaiting_human,awaiting_capacity-- is cancelled, its lease cleared, so a worker still running one finds its lease gone and nothing it reports revives the job. Succeeded, failed and cancelled jobs are left as they are; - the project moves into
abandonedthrough the phase machine's own guard, and its ledger gets onePhaseTransitioned(guard: "operator abandoned", withreason,by,account,cancelled_jobsandwithdrawn_gates) and oneGateWithdrawnper withdrawn gate.
A failure anywhere leaves all of it as it was. The claim never hands out a job of an abandoned project, so a follow-up job a still-running handler enqueues after the abandonment never runs.
Abandoning lets the project's checkout go: vibey new can start a
fresh project with the same --repo, beside the abandoned one, which stays readable by
id. Nothing on disk is removed. The files the abandoned project wrote under the
checkout's .vibey/ (runs/<cycle>/..., context/, deploy/) stay where they are, and
the new project's own writes replace them cycle by cycle; they are not keyed by project,
so start the new project on a clean checkout when the old files must not be read.
Abandoned greeter (9692abab-...): build -> abandoned, cycle 1.
by adam (account adam)
reason: built on a foreign spec
cancelled 2 jobs:
6f1c... build.implement (was ready)
8a07... build.verify (was awaiting_human)
withdrew 1 gate:
c3d9... defect
With --dry-run the block starts Dry run: nothing was written. and says Would
abandon, would cancel and would withdraw. --json prints one object:
project_id, name, from, to, cycle, dry_run, already_abandoned,
written, reason, by, account, cancelled_jobs (job_id, kind, state as it
was) and withdrawn_gates (gate_id, kind, job_id).
Abandoning a project that is already abandoned changes nothing, prints
<name> (<id>) is already abandoned; nothing was changed. and exits 0. A project that
is done is refused (exit 3, Error: the project is done ...): it finished, which is a
different ending. So is one in a phase this vibey does not know. An unknown
PROJECT_ID exits 1; a missing, empty or control-bearing --reason, or an unusable
--by, exits 2 before the database is opened.
vibey work PROJECT_ID¶
Process one ready DESIGN job, or, when the project is in VISUAL_DESIGN, one
visual-inventory job. Live engine use is explicit and capped. Prints
processed one job or no ready job.
vibey work 0b5c9a4e-1d4c-4c47-9a2a-3c1d2b8f9e10 --provider scripted --max-turns 3
| Option | Default | What it does |
|---|---|---|
--provider {scripted,claudeloop,gptossloop} |
gptossloop |
gptossloop runs the sovereign local DESIGN provider on Ollama (ADR-0015, ADR-0027, ADR-0064) and reads research material from $VIBEY_EVIDENCE_DIR; with that unset, research refuses rather than inventing a source, and DESIGN stops there. claudeloop runs a real, paid session capped by --max-turns and --max-dollars; its spend is recorded as budget_spent ledger events so the budget brake counts it. scripted needs no live engine. qwenloop is still accepted: it is read as gptossloop, with --provider qwenloop is now --provider gptossloop (ADR-0064) ... on stderr. Any other value exits 3 with Error: provider must be 'scripted', 'claudeloop', or 'gptossloop'. |
--max-turns N |
1 |
Turn cap for this one job (min 1). |
--max-dollars F |
0.25 |
Dollar cap for this one job (0.01–10). |
--ollama-model NAME |
$VIBEY_OLLAMA_MODEL, else gpt-oss:20b |
Local model for --provider gptossloop; ignored by the other providers. The server is $VIBEY_OLLAMA_URL. |
In VISUAL_DESIGN only --provider scripted works; any other provider exits
3 with Error: no live VisualInventoryProducer is implemented yet; use --provider scripted.
An unknown PROJECT_ID exits 3.
vibey design¶
Bare vibey design prints help. Subcommands:
| Subcommand | What it does |
|---|---|
design resume PROJECT_ID [--priority] |
Enqueue or resume the project's DESIGN interview. Prints design job <id>. --priority enqueues it bumped, so it runs next after whatever is running — the same grant and ledger record as vibey queue bump. On an interview that has already finished, --priority is a recorded no-op, as plain resume is a no-op. |
design accept PROJECT_ID [--spec-json PATH] [--visual/--no-visual] |
Accept the synthesized spec (optionally importing JSON first) and choose whether to enter the VISUAL_DESIGN interstitial. Defaults to --no-visual; the choice is never implicit. Refused while any DESIGN job of the cycle (research, synthesis, spec) is unsettled. Prints accepted design for <id>; entered <phase>; context under <repo_path>. |
vibey design accept <project-id> --no-visual
--spec-json expects a JSON object with:
objectiveandwalking_skeleton— required strings;criteria— required list of objects withcriterion_id,given,when,then,fit;constraints— optional list of{"text": ..., "kind": "hard" | "soft"};non_goals— optional list of strings;research_gaps— optional list of{"topic": ..., "reason": ...}: the topics that were not researched. Keep them when you edit a synthesized spec, since an import replaces the whole spec and a gap you leave out is no longer stated in it;nfrs— optional list of objects withnfr_id,attribute,scale,meter,must,wish(string ornull),fit_criterion.
The shapes are the dataclasses in src/vibey/domain/spec.py. A missing
required key or an unreadable file currently surfaces as a traceback,
because guard() renders only VibeyError.
vibey visual¶
Bare vibey visual prints help. Subcommands:
| Subcommand | What it does |
|---|---|
visual accept PROJECT_ID |
Accept the reviewed visual plan and enter BUILD. Prints accepted visual design for <id>; entered <phase>. |
visual waive PROJECT_ID |
Explicitly waive the visual plan (the inventory must still be complete) and enter BUILD. Prints waived visual design for <id>; entered <phase>. |
vibey visual waive 0b5c9a4e-1d4c-4c47-9a2a-3c1d2b8f9e10
vibey watch [PROJECT_ID]¶
Live TUI dashboard: current phase, queue, engine circuits, active worktrees,
and a ledger tail. Defaults to the most recently created project; an unknown
id prints unknown project <id> and exits 1.
vibey watch --replay --speed 4
| Option | Default | What it does |
|---|---|---|
--replay |
off | Replay the project's historical ledger events instead of following live state. |
--speed F |
1.0 |
Replay rate in ledger states per second while playing (--speed 4 advances four states per second). 0 or a negative value disables auto-advance. The option's --help text calls it a "multiplier"; it is a rate. |
Replay starts paused. Keys: Space play/pause, Right or n next step, Left or
p previous step, q quit.
Not on a terminal. When input or output is piped or redirected (a script, an agent, | cat),
watch does not take over the screen: it says so on stderr, prints the same one-time snapshot
vibey status prints (vibey status --json is the machine form),
and exits 0, or 1 when there is no project. --replay needs a terminal screen, so without one it
is a usage error (exit 2).
vibey llm¶
The conversation with the local model, as it happens. It only reads.
vibey llm tail --all --no-follow
| Subcommand | Options | What it does |
|---|---|---|
llm tail |
--file PATH, --all, --no-follow, --raw, --max-chars N |
Follows the wire log in every phase: each request DESIGN and DECOMPOSE send the model, its reasoning and answer as they stream back, and the model's own counts and timings; and, for BUILD and REVIEW, each engine session the worker started: every tool call and its result, the streamed answer, the reasoning, and each turn's time and tokens. It starts at the latest call or session and switches to each new session by itself (a new run id means a new events file). --all starts from the beginning, with every session; --raw prints the JSON lines as written. |
The log is written when a command runs with -vvv, or with VIBEY_LLM_WIRE_LOG=path
(see VIBEY_LLM_WIRE_LOG). Without either, nothing is
written and the model is asked exactly as before. The default file is
~/.local/state/vibey/llm-wire.jsonl, readable by its owner alone. With it on, the worker also
notes each engine session in the log (a session record naming that run's events.jsonl), which
is how the tail knows where BUILD and REVIEW write; the records hold ids and paths only.
vibey search and vibey fetch¶
vibey search "QUERY" [--json] [--limit N]
vibey fetch URL [--json] [--max-chars N]
vibey search "postgres skip locked" --limit 5
vibey fetch https://example.com --max-chars 20000
The web, for any loop that can run a command, and for the sovereign DESIGN research stage.
search lists results (title, URL, snippet) from the backend you configure; fetch reads one
public page as plain text. Both print under an untrusted banner: what comes back is other
people's text, to be read as data and never followed as instructions. Evidence handed to a
DESIGN stage is the quoted retrieved text with its URLs, never a model's summary of it.
fetch refuses a private, loopback, link-local or metadata address, re-checks every redirect
hop, follows at most three, reads text types only and stops at 1 MB. A local file path in a
query is removed before the query leaves the machine.
Configuration is environment only (never vibey.toml, so a key cannot be committed). A search
with no backend configured exits 2 and says how to configure one.
| Backend | Set | Reaches |
|---|---|---|
| SearXNG (keyless, self-hosted) | VIBEY_SEARXNG_URL |
the DESIGN research stage and every engine session |
| Brave Search | VIBEY_BRAVE_API_KEY |
the DESIGN research stage only: the key is never passed to a session |
vibey worker tells every session that can run a command how to search (an appended "Web
access" note in its plan, naming the absolute path of the running vibey), and passes the
non-secret WEBSEARCH_* settings through to it. With only a Brave key configured, sessions are
told nothing and have no search. The worker prints one line on stderr saying which of the two
is in force.
gptossloop and qwenloop also have web_search and web_fetch as tools of their own, which run
the same two commands; every other engine uses the command from its plan.
vibey recover¶
Recover stuck leased jobs (e.g. after a worker crash): every job in state
leased is set back to ready, and its lease owner, lease expiry, and
assigned engine are cleared.
vibey recover --project 0b5c9a4e-1d4c-4c47-9a2a-3c1d2b8f9e10 --stop-workers --dry-run
| Option | Default | What it does |
|---|---|---|
--project ID |
unset | Recover jobs for one project. |
--all |
off | Recover jobs for every project. Wins if --project is also given. |
| --stop-workers | off | First stop the vibey worker processes this user is running, and everything each started (an engine runner, a uv run or caffeinate wrapper), then reset the leases. With --project, only that project's workers (vibey worker --project ID): a worker that serves every project (--all-projects) or none in particular may hold a lease on it, so it is left running and named, and the leases are not reset (exit 1) until you add --include-shared. With --all, every worker. The processes are listed first. |
| --include-shared | off | With --project and --stop-workers: also stop the workers that serve every project or none in particular, so the leases can be reset. |
| --grace SECONDS | 10 | How long --stop-workers waits after SIGTERM before SIGKILL. |
| --dry-run | off | Change nothing: with --stop-workers, list the processes that would be stopped, and print would recover N stuck job(s) instead of resetting them. |
One of --project or --all is required; with neither, it prints
Must specify either --project <id> or --all and exits 1.
A worker that died leaves its jobs leased until the lease expires, and a worker that is still
alive keeps them; so the one command for a stuck queue is
vibey recover --project ID --stop-workers, then start the worker again.
Prints Recovered N stuck job(s)., where N is the number of rows actually
reset, read from PostgreSQL's UPDATE n status tag.
vibey retry PROJECT_ID¶
Get a stalled project moving again in one command. A project that stalls on a bad model answer
parks its job on an attempts_exhausted gate; this does what used to be recover --stop-workers,
gates, answer --raw '{"max_attempts": N}' and a hand-written worker restart, in that order.
vibey retry 0b5c9a4e-1d4c-4c47-9a2a-3c1d2b8f9e10 --max-attempts 10 --dry-run
| Option | Default | What it does |
|---|---|---|
--max-attempts N |
10 |
Attempts to grant on each of the project's attempts_exhausted gates. |
--keep-workers |
off | Do not stop this project's running workers first. retry stops only workers started for this project (vibey worker --project ID); one that serves every project is never stopped by it, because a retry resets no lease. |
--start-worker |
off | Then start vibey worker --project PROJECT_ID detached, with every argument after -- passed to it. |
--log PATH |
$XDG_STATE_HOME/vibey/worker-<project>.log |
Where the started worker's output is appended. |
--grace SECONDS |
10 |
How long a worker gets after SIGTERM before SIGKILL. |
--dry-run |
off | Change nothing: list the workers that would be stopped, the gates that would be granted and the worker that would be started. |
vibey retry 6b45e169-4653-4a8d-941c-7440249dc8ef --start-worker -- --engines gptossloop --auto-answer
Workers are stopped exactly as recover --stop-workers stops them, and if one survives SIGKILL
nothing else is done (a live worker holds its leases). Only attempts_exhausted gates are
answered, each as a named request (retry-<gate>-<N>, by vibey retry) so running the command
twice answers a gate once. Any other open gate is a decision: it is counted and pointed at
vibey gates PROJECT_ID, never answered. The started worker is its own session, so closing the
terminal does not stop it, and vibey recover --stop-workers finds it by its command line.
Updating vibey itself (git pull, uv sync) is not part of this.
vibey status [PROJECT_ID]¶
Show the project's name, phase, cycle, visual and deployment decisions, repository path, queue depth per job state, and engine circuits (circuit state, consecutive failures, cycle cost). Defaults to the most recently created project.
vibey status --json
| Option | Default | What it does |
|---|---|---|
--json |
off | Emit JSON instead: project_id, name, phase, cycle, max_cycles, repo_path, visual_decision, deployment_decision, queue_depth, circuits (each with engine_id, installed, version, conformance_ok, circuit, capacity_state, consecutive_fail, cost_usd_cycle, selected_count), active_worktrees, and integration_branch (the project's own integration branch for the cycle, vibey/<project8>/<cycle>/integration). |
vibey engines [PROJECT_ID]¶
Show recorded engine health for a project as a table: engine, version,
circuit-breaker state, consecutive failures, selection count, and cycle
cost. Rows exist only for engines that vibey doctor --record or a worker's
startup preflight has recorded; with none it prints
no engines recorded for project. Defaults to the most recently created
project.
vibey engines --json
--json prints one document on stdout instead of the table:
{"project_id": "<uuid>", "engines": [...]}, with "engines": [] (and exit 0)
when none are recorded. Each engine has the nine keys vibey status --json
carries under circuits (the same code writes both): engine_id, installed,
version, conformance_ok, circuit, capacity_state (a string or null),
consecutive_fail, cost_usd_cycle and selected_count. cost_usd_cycle is
the engine's metered BUILD spend across all cycles, whatever its name
(vibey cost --json calls it cost_usd_all_cycles). With no project the
message no projects found; create one withvibey newfirst goes to stderr
and the exit code is 1, as without --json.
vibey loops¶
List the family's two loops (sub-doctrine 8.c), every engine each one holds,
what each effort level passes to each engine, and what each engine can do.
sovereignloop is tier local, and canon 8.b makes it the default loop. A local
engine in it runs only while its switch is on. paidloop is tier paid, and
canon 8.b makes it declared only, with Claude through claudeloop as its
default. The canon is reported as the canon: vibey's selector does not read a
paid declaration yet. Today it prefers an eligible local engine and, when none
is eligible, picks a paid one by weighted round robin (ADR-0038). Each engine
is listed under the loop its descriptor's tier puts it in.
vibey loops --json
The command needs no database and no network. Local switches are read the way
vibey doctor reads them, from the environment and then ./vibey.toml. It
exits 0. It exits 3 when a setting it must read is malformed:
VIBEY_OLLAMA_URL, VIBEY_OLLAMA_TIMEOUT while VIBEY_OLLAMA_URL is set, or
the [engines.claudeloop_local] table in ./vibey.toml. That is the same
configuration that would stop the worker. The message names the setting and
never its value, because a URL can carry user:token@.
| Option | Default | What it does |
|---|---|---|
--json |
off | Print the whole document instead of the tables. |
Each loop gets a small table with one column per engine. Its rows are
switched on, $ per Mtok in/out, model (the model vibey hands the engine;
see default_model below), and one row per effort. An effort's row shows the
flags the engine is passed, without their dashes. It is followed by
-> STANDARD (or the level it really reaches) when the engine runs at a
different effort. After each table come the variable that switches each local
engine — gptossloop is on unless switched off by VIBEY_FEATURE_GPTOSSLOOP=0, or
by its key under [features] in vibey.toml for the on-by-default one, <engine>
is switched on by <VARIABLE>=1, ... for the others — and any notes.
--json prints one object:
efforts: the five levels, in order:TRIVIAL,LOW,STANDARD,HIGH,MAX.default_loop:sovereignloop.paid_default_engineand its aliaspaid_default:claudeloop, the paid default canon 8.b names.ladder:phase_base(each phase's starting effort, by phase name),build_attempts(BUILD's effort at attempts 1 to 6),exhausted_after(6; attempt 7 parks a human gate), androtates_when_effort_rises(true).loops: two objects,sovereignloopfirst. Each hasloop,tier,default,declared_only(canon 8.b's rule, as above),engines, andby_effort.
Each engine object has these keys:
| Key | What it holds |
|---|---|
engine_id, binary, state_dir, done_marker, plan_flag, supports_cwd_flag, base_weight, cost_per_mtok_in, cost_per_mtok_out |
The engine's descriptor, as declared. |
enabled |
Whether the engine would run right now. A local engine follows its switch. An engine with no switch is true: the selector may pick it whenever it is eligible. Being listed is not being selected. |
switch |
The variable that switches a local engine (VIBEY_FEATURE_GPTOSSLOOP, VIBEY_FEATURE_QWENLOOP, VIBEY_FEATURE_CLAUDELOOP_LOCAL), or null. |
on_by_default |
true for a local engine that is on when neither its variable nor [features] sets its switch: gptossloop, the sovereign default (ADR-0064). false for every other engine. |
repealed |
true for an engine canon 8.b repeals from both loops while vibey still carries its code; none today, since OpenCode's engine was deleted. Such an engine stays listed, and is left out of by_effort, so nothing that selects from by_effort picks it. false for every other engine. |
default_model |
The model vibey hands the engine, or null. For gptossloop this follows how the model actually reaches it. It is GPTOSSLOOP_MODEL when set. Otherwise it is vibey's model (VIBEY_OLLAMA_MODEL, else gpt-oss:20b), but only while VIBEY_OLLAMA_URL is set, because that is the only path by which vibey's model reaches the session. A worker started with --ollama-model hands that model instead, which this command cannot see. Otherwise it is null, and gptossloop's own configuration chooses the model. For qwenloop it is QWENLOOP_MODEL when set, else null: vibey hands qwenloop only its endpoint, and qwenloop runs the Qwen model it names itself (qwen3:14b unless its configuration names another). |
efforts |
All five levels, in order. Each has effort, argv (from the descriptor's projection), achieved (the level it really reaches), and model. model is the value of a --model the level passes, else default_model, else null. notes gives the descriptor's own note and, when model is null, what chooses the model instead (claudeloop preset high, qwenloop's own configuration chooses the model). qwenloop's notes also say what it became: since ADR-0064 qwenloop runs a Qwen model (qwen3:14b unless QWENLOOP_MODEL names another); the gpt-oss engine it used to be is gptossloop. |
capabilities |
images, files, paste_text, paste_images, plugins (skills-context or claude-plugins), and mcp. Each is true, false, or null for unknown, and a menu belongs only beside a value that is not null. evidence names, for each value that is set, where the runner's own code shows it. skills-context applies when the project sets skills_context.mode = inject: vibey then appends the vibey-skills context packet to the plan. A local model's own abilities come from Ollama at run time, so an image menu needs both this loop's images and the model's vision. |
run |
The argv template build_argv fills for a run: {binary}, run, {plan_flag?} (only when plan_flag is set: put that flag there), {plan}, --run-id, {run_id}, {effort_argv...}, and --cwd {cwd} when supports_cwd_flag. Tests compare it with build_argv for every engine at every effort. They also read it, filled in at every effort, against the runner's own run definition. |
controls |
stop, wind_down, and prompt: argv templates after the binary, with {run_id}, {cwd} and {text} to fill in. Each is null where the runner has no such verb, or does not act on it. A test reads each template against the runner's own Typer definition. |
events |
path ({cwd}/{state_dir}/runs/{run_id}/events.jsonl) and envelope, one of three values: type (a top-level "type"), event_type+payload, or event_type (a top-level "event_type" with no payload wrapper; no engine writes it today). |
env |
auth and passthrough: the descriptor's auth_env and env_passthrough, names only and in declared order. The command never reads a value. |
notes |
A list of strings: anything the canon says otherwise, such as a note that 8.b repeals an engine the code still lists. Empty for every engine today. |
by_effort maps each level to every engine in the loop that is not repealed,
each with its engine_id, model and achieved. Engines that reach exactly
that level come first, then those that go higher, then those that fall short.
Ties go to the lower output price, then to the engine id.
The document for one fixed environment is committed as
tests/cli/golden/vibey-loops.json. In that environment every variable the
command reads is cleared, then VIBEY_OLLAMA_URL=http://127.0.0.1:11434 is set,
so gptossloop (on by default) runs vibey's model and qwenloop (off) reports its
own. The test suite produces the
document and fails when the command's output drifts from the file, so a parser
tested against the file reads what the command prints. After an intended change,
regenerate it with
VIBEY_UPDATE_GOLDENS=1 uv run pytest tests/cli/test_loops_cli.py -k golden
and commit it with the change.
What the runners' own code shows today:
| Engine | Capabilities shown | stop / wind_down / prompt |
Envelope |
|---|---|---|---|
claudeloop, claudeloop-local |
files, pasted text, claude-plugins (run --plugin), MCP (run --connector); images unknown |
--run-id and --cwd for each; prompt TEXT --now |
event_type+payload |
codexloop |
files, pasted text, skills-context; images and MCP unknown |
--run-id for each and no --cwd: run it in the worktree; prompt TEXT --now |
type |
gptossloop, qwenloop |
files, pasted text, skills-context; no images, no pasted images, no MCP (text messages and a fixed tool set) |
each takes the run id positionally, with --cwd; prompt RUN_ID TEXT, which its runner adds to the conversation at the next turn boundary |
type |
vibey models¶
List the local model families vibey knows by name -- Qwen Coder, DeepSeek Coder, Codestral,
StarCoder, Gemma, Llama, CodeLlama and CodeGemma (ADR-0093) --
each with the Ollama tag to start from and the ollama pull that fetches it, and then the
models the Ollama on this machine actually has, with what that Ollama reports each can do.
vibey models --json
The families need no network. The installed models come from the Ollama at
VIBEY_OLLAMA_URL (default http://127.0.0.1:11434). Their capabilities are what Ollama's own
/api/show says, never a table kept in vibey: Ollama decides tool-calling from more than a
model's template, so a list kept here would be wrong for the newer models. A model whose
capabilities Ollama does not report is unknown, which is not the same as having none. No
Ollama answering is reported and is not an error; the families still print. It exits 0.
For each installed model it says what the model can be tried for. tools means it can be
tried for BUILD, and only a run shows whether it uses them: Qwen2.5-Coder 14B reports tools
and still writes its calls out as text. completion means it can serve DESIGN and DECOMPOSE,
which ask for JSON and need no tool-calling. insert means fill-in-the-middle completion. A
model that reports completion without tools is also listed for BUILD through the text
tool-call protocol, marked slower and measured unreliable.
| Option | Default | What it does |
|---|---|---|
--json |
off | Print the same facts as JSON: families (key, label, suggested_tag, pull), ollama_url, problem (null, or why no Ollama answered), and installed (tag, family, size_gb, capabilities -- null when unknown -- and jobs). |
vibey cost [PROJECT_ID]¶
Show the current cycle's spend against the caps the budget brake enforces, then
each engine's metered BUILD spend. Defaults to the most recently created project;
an unknown id prints unknown project <id> and exits 1.
vibey cost --json
Cycle spend:is the ledger sum the brake checks before every BUILD session:TurnCompletedcost andBudgetSpentdollars, and one turn perTurnCompletedplusBudgetSpentturns. DESIGN's spend is in it.Cycle dollar cap:andCycle turn cap:are the project's storedmax_cycle_dollarsandmax_cycle_turns, read through the brake's own parser, ornone (uncapped)andnone. A reached cap addsCap reached: the next BUILD session parks a budget_exhausted gate.Per-engine (BUILD sessions, all cycles):is each engine's metered spend fromengine_health, which accumulates across cycles, and how often rotation selected it. That count is not a turn count.
The spend and caps are the budget vibey budget
shows. That command also changes the caps.
--json prints one document on stdout instead: project_id, name, cycle,
caps (max_cycle_dollars and max_cycle_turns, each a number or null),
spend (dollars and turns), exhausted (a cap is reached) and engines, a
list of {"engine_id", "cost_usd_all_cycles", "selected_count"}. The first six are
vibey budget show --json's own values (without its history). An unknown id or
no project prints its message to stderr and exits 1, as without --json.
vibey budget [PROJECT_ID]¶
Show a project's per-cycle caps and this cycle's spend against them. Add,
change or remove the caps after the project exists. The caps are
max_cycle_dollars and max_cycle_turns in the project's stored config, the
one place the budget brake reads them. The spend is the same ledger sum
vibey cost and the worker use. Bare vibey budget, or vibey budget show,
shows the most recently created project.
vibey budget set --max-cycle-dollars 15 --max-cycle-turns 100
| Subcommand | Option | Default | What it does |
|---|---|---|---|
budget [show] [PROJECT_ID] |
--json |
off | Print the budget as JSON (below). |
--all |
off | Every project, newest first (by creation time, then id). With --json, an array. Not together with PROJECT_ID (exit 2). |
|
budget set [PROJECT_ID] |
--max-cycle-dollars F |
unset | Set the dollar cap: a finite number above zero. |
--max-cycle-turns N |
unset | Set the turn cap: a whole number above zero. At least one of the two is required. | |
--by NAME |
the account running it | The name the change is recorded under. A tool that runs the command names itself; the VS Code extension says vibey-vscode. |
|
budget clear [PROJECT_ID] |
--dollars, --turns, --all |
— | Remove the dollar cap, the turn cap, or both. At least one is required. The project is then uncapped for it, as if it had never been set. |
--by NAME |
the account running it | As for set. |
The text form is one short block per project:
greeter (0b5c9a4e-1d4c-4c47-9a2a-3c1d2b8f9e10), cycle 2
dollars: $3.21 spent this cycle; cap $15.00
turns: 41 spent this cycle; no cap
last change: dollar cap none -> $15.00, by adam, 2026-09-24 19:02 UTC (1 change in all)
When a cap is reached, the block says so plainly:
The dollar cap is reached: the next BUILD session will park a budget_exhausted gate.
--json prints one object with exactly these keys. With --all it prints an
array of them, newest project first:
{
"project_id": "0b5c9a4e-1d4c-4c47-9a2a-3c1d2b8f9e10",
"name": "greeter",
"cycle": 2,
"caps": {"max_cycle_dollars": 15.0, "max_cycle_turns": null},
"spend": {"dollars": 3.21, "turns": 41},
"exhausted": false,
"history": [
{"at": "2026-09-24T19:02:11.482113+00:00", "by": "adam", "field": "max_cycle_dollars", "old": null, "new": 15.0}
]
}
- A cap of
nullis no cap. The dollar cap is a number and the turn cap an integer. spend.dollarsis the ledger sum, not rounded.spend.turnsis the brake's count: one perTurnCompleted, plusBudgetSpentturns.exhaustedis true when a cap is reached.historylists every changebudget setorbudget clearrecorded, oldest first. Each entry hasat(ISO-8601 with its offset),by,field,oldandnew, wherenullis uncapped. A cap set at creation (byvibey newor the Kubernetes operator) is not a change, so it has no entry.
With no projects, bare vibey budget prints
no projects found; create one with `vibey new` first and exits 1. --all
exits 0, printing [] with --json.
Changing a cap. set and clear print what changed, then the budget block:
Changed by adam:
dollar cap: none -> $15.00
A request that leaves every cap as it is prints
Nothing changed: the caps were already as asked., and writes and records
nothing, so running it again is harmless. A cap at or below this cycle's spend
is allowed. The block then says the next BUILD session will park a
budget_exhausted gate.
Each change writes the new caps into the project's config and appends one
BudgetCapChanged event per changed cap to the ledger, in one transaction.
The event carries field, old, new, by and account. vibey ledger search
--kind BudgetCapChanged lists them, and vibey ledger export withholds them
(What gets published). --by is a label for
the record, not a permission. account is recorded beside it: the account the
command ran as, from the password database, never $USER.
The brake reads the caps at every BUILD session, so a worker that is already
running applies a change to its next session. No restart is needed. A job
already parked on a budget_exhausted gate stays parked until the gate is
answered. The command lists each such gate with the answer that resumes the job
under the stored caps, vibey answer GATE_ID --raw '{}'. Answering
--raw '{"max_dollars": N}' still raises the cap for that job alone, as before.
Exit codes: 0 on success, including an empty --all. 1 when there is no
project or PROJECT_ID names none. 2 for a usage error, which writes nothing:
a value that is not a cap, nothing to set or clear, a --by label that is empty,
longer than 200 characters or holds control or formatting characters, or
PROJECT_ID with --all. 3 for a change to a project in a phase this vibey
does not know, because nothing can be recorded under it. Showing that project
still works.
vibey budget no-cap and vibey budget cap¶
no-cap declares no cap for ULTRA runs through sub-doctrine 8.b's
whole path, in a terminal on the host:
- a full-screen warning with the measured cost per hour ("unknown" when nothing has been measured);
- the typed phrase
I accept unlimited spending; - a second warning, whose default answer keeps a cap;
- the declaration:
[budget] ultra_no_cap = truein--toml(default./vibey.toml), and a trustedUltraNoCapChangedledger event naming who, when and which device.
| Subcommand | Option | Default | What it does |
|---|---|---|---|
budget no-cap [PROJECT_ID] |
--toml PATH |
vibey.toml |
The vibey.toml that records the declaration. |
--by NAME |
the account running it | As for set: a label for the record, not a permission; the account is recorded beside it. |
|
budget cap [PROJECT_ID] |
--toml PATH, --by NAME |
as no-cap |
As for no-cap. |
Both default to the most recently created project. Run off a terminal, no-cap
refuses with exit 2 and records nothing. A wrong phrase exits 1. The default
answer to the second warning prints Kept the cap and exits 0. No environment
variable can declare it. cap withdraws the declaration in one command, binding
at the next ULTRA pass: it records UltraNoCapChanged with enabled: false, and
sets the key to false when the file exists. The worker reads only the ledger
event. The file records the declaration.
vibey ultra¶
ULTRA is effort without a ceiling (ADR-0063).
While a project's ULTRA run is on, every BUILD pass runs at ULTRA: the attempt
ladder is not consulted, so a pass never parks for running long, and qwenloop,
gptossloop and claudeloop get no --max-turns. A pass that ends with a done
verdict is a checkpoint. The checks (build.verify) are enqueued as always, an
UltraPassCompleted event is recorded, and the next pass is enqueued after the
checks, under its own job key. Two things end the run: the operator's Stop, and
the budget brake at a declared cap. A run with no dollar cap parks an
ultra_needs_cap gate unless no cap was declared (vibey budget no-cap).
vibey ultra status --json
| Subcommand | Option | What it does |
|---|---|---|
ultra start [PROJECT_ID] |
--by NAME |
Starts the run: records a trusted UltraStarted event. |
ultra stop [PROJECT_ID] |
--by NAME |
Stops it: records UltraStopped. No further pass starts; a queued pass ends without running. |
ultra status [PROJECT_ID] |
--json |
Running or stopped, passes completed, the dollar cap, this cycle's spend and the measured cost per hour (null, shown as "unknown", when nothing has been measured). |
Each event records by, account and device (the host's name). These
commands run only on the host. start after a stop does not resume a stopped
item's chain by itself: the stopped pass ended without a successor, so the next
pass starts when the item's BUILD job is enqueued again.
vibey driver¶
The driver's failover and handback (ADR-0070).
The driver is the Claude Code session steering the work. When it hits a usage
limit or runs out of credit, the work continues on [failover] target_engine
(gptossloop) at target_effort (ULTRA). When a probe of the paid model is
recorded as successful, the work goes back to the same session.
vibey driver timer --platform systemd --out ~/.config/systemd/user
| Subcommand | Option | What it does |
|---|---|---|
driver hook |
--config PATH |
The command Claude Code's StopFailure hook runs; reads the hook's JSON on stdin. On error rate_limit or billing_error it gates a brief into <cwd>/.vibey/driver/, appends EngineFailedOver and starts the sovereign engine detached. Any other error is ignored. A second hook for an active failover does nothing. Prints the outcome as JSON; exit 3 when the gate parked it (Claude Code ignores this hook's exit code). |
driver probe |
--cwd PATH, --config PATH |
Run by the timer. When a failover is active and its probe is due, runs probe_argv and appends EngineProbed (ok or not). Only after a recorded ok probe: winds the sovereign engine down, gates the return brief (it lists the commits made meanwhile), appends EngineHandedBack and resumes the session with resume_argv (claude -p --resume <session-id>). |
driver status |
--cwd PATH, --config PATH |
The state, read from <cwd>/.vibey/driver/ledger.jsonl, as JSON. |
driver timer |
--out DIR, --platform launchd\|systemd, --cwd PATH |
Writes a launchd agent (macOS default) or a systemd user service and timer that run vibey driver probe --cwd <worktree> every probe_interval_seconds, and prints the launchctl / systemctl --user command that loads it. vibey never loads it for you. |
driver hook-config |
Prints the settings.json block that runs vibey driver hook on StopFailure, with no matcher. |
Both directions pass the no-loss gate: STRICT up to three times (the brief's
transcript SHA-256 must equal the transcript's now), then FULL_TRANSCRIPT (the
transcript is copied into the worktree), then HUMAN: a PARKED-*.md brief is
left in .vibey/driver/ and nothing is started. CreditsExhausted has no
reset time; a window's reset only schedules the probe, and only a recorded
successful probe hands back.
vibey supervisor¶
Keeps vibey worker --all-projects and the triaged-delivery bridge
(scripts/triaged_delivery.py --interval N) running
(#1189): a launchd agent on
macOS, a systemd user service on Linux, rendered from
[supervisor] by the same renderer as vibey driver timer.
Each unit starts its process at load, restarts it restart_seconds after it exits with a
failure (never after a clean exit, so a worker drained by SIGTERM stays down), and appends
its output to <log_dir>/<name>.log. vibey never loads a unit for you: install prints
the commands.
vibey supervisor install --platform systemd
| Subcommand | Option | What it does |
|---|---|---|
supervisor install |
--repo PATH (.), --out DIR, --platform launchd\|systemd, --config PATH |
Writes <label_prefix>.worker and <label_prefix>.delivery (and <label_prefix>.state-sync with [supervisor] state_sync = true) into the service manager's directory (~/Library/LaunchAgents, or $XDG_CONFIG_HOME/systemd/user), or --out. Creates the log directory, and the environment file from a commented template, readable by you alone, when it does not exist (an existing one is kept); with state_sync, the state sync's own state-sync.env the same way. Prints the launchctl bootstrap or systemctl --user enable --now commands that load them. Refuses, exit 78, a log directory, environment file, vibey, Python or --repo under a directory a reboot empties or inside a linked worktree (10.h), vibey not on PATH, and a --repo with no scripts/triaged_delivery.py unless delivery = false. --config defaults to <repo>/vibey.toml. |
supervisor status |
--platform launchd\|systemd, --config PATH |
One line per service: running, stopped, not loaded or not installed, read from the unit file and launchctl print / systemctl --user is-active. Exit 0 only when every service runs, else 1. --config defaults to ./vibey.toml. |
supervisor exec |
--env-file PATH -- COMMAND... |
What every unit runs. Reads KEY=VALUE lines from the file (# comments, an optional export, one pair of matching quotes removed, nothing expanded; a declared value wins over the inherited one), then replaces itself with COMMAND. Exit 2 with no command, 78 when the file is missing or a line is not a pair (named by line number, never quoted), 127 when COMMAND cannot run. |
vibey doctor prints one supervisor-worker and one supervisor-delivery line, and a
supervisor-state-sync line with state_sync = true: PASS
when the service runs; WARN when it is not installed, not loaded or stopped, naming what
is missing; FAIL instead of WARN with [supervisor] required = true. An unreadable
[supervisor] table is a FAIL.
vibey state¶
Keeps this database and a sealed copy of it on the repository's vibey-state branch the
same, in both directions
(ADR-0086). The branch holds one file,
encrypted with AES-256-GCM under the state key, and nothing else. Every setting is a
VIBEY_STATE_* variable (State sync); the database is
VIBEY_STATE_PG_URL, else VIBEY_PG_URL. Bare vibey state prints help. Subcommands, in
vibey state --help order: sync, status, export, import, forget, key.
vibey state sync --no-push
| Subcommand | Option | Default | What it does |
|---|---|---|---|
state sync |
--no-push |
off | Merge this database with the branch's head, three-way against the commit it last synced with, and write the result to each end that differs. With --no-push, write only here: the branch is read, never written. |
--every SECONDS |
unset | Sync every this many seconds (a number above 0), for as long as the process lives. Each round's line starts with its UTC time. A failed round is said on stderr and the next one runs. What [supervisor] state_sync runs. |
|
state status |
--json |
off | What a sync would pull and push. Writes nothing at either end. --json prints one object: state, remote, commit, rows, to_pull, to_push, conflicts. |
state export |
--out FILE |
required | Write this database, sealed under the state key, with the commit it last synced with, to FILE (mode 0600, written whole or not at all). For carrying it to an empty database, as the remote-command workflow does. |
state import FILE |
Load a sealed export into this database, which must hold no rows in any synced table and have applied the same migrations as the export. Records the export's commit as this database's, so the next sync stays three-way. |
||
state forget |
Forget the commit this database last synced with, so the next sync merges both ends as a first sync would, against no base. Only for a branch that was rewritten or deleted: the sync refuses until then, and says so. |
||
state key |
Say where the state key is: VIBEY_STATE_KEY, the macOS keychain or the key file. Exit 1 when there is none. |
||
--new |
off | Make a key and keep it where it would be found first here: the keychain on macOS, the key file elsewhere. Refused while a key exists anywhere: replacing one makes every state sealed under it unreadable. | |
--show |
off | Print the key, and only the key, for another machine's VIBEY_STATE_KEY or for a private repository's runners: vibey state key --show \| gh secret set VIBEY_STATE_KEY. |
sync and status print one line: OWNER/NAME:vibey-state: in sync at <commit>, N row(s);
synced at <commit>: N row change(s) pulled, pushed; N row(s); or, from status,
N row change(s) to pull, changes here to push; N row(s). A row both ends changed
differently is settled by its table's rule (newest, mine, theirs or refuse). A
conflict no rule settles stops the sync before anything is written: it prints
N conflict(s); nothing was changed at either end: to stderr, then one line per row
(table key: reason), and exits 1. The ledger is never settled by a rule: two ends that
appended different events at the same seq have diverged, and a person decides. Two
databases that applied different migrations are refused: run vibey migrate on the one
behind. A sync that found the database or the branch moved under it starts again, up to
VIBEY_STATE_ATTEMPTS times. Run twice in a row, the second sync pulls nothing and pushes
nothing.
Exit codes: 0 done or in sync; 1 a conflict, a refusal (no key, a key that does not open
the branch, a base the branch no longer reaches) or a failure, each said on stderr; 2 a
setting that cannot be used. vibey state is never offered by the hub, and the hub refuses
it as a command for the workflows (RESERVED_COMMANDS). On the runner the remote-command
workflow runs it itself, and only for a private repository (ADR-0086).
vibey ledger¶
Bare vibey ledger prints help. Subcommands, in vibey ledger --help order:
search, export, site, show. What export and site produce can be browsed in the
ledger explorer.
vibey ledger show --kind GateAnswered --limit 20
| Subcommand | Option | Default | What it does |
|---|---|---|---|
ledger search [PROJECT_ID] |
--id UUID |
unset | Exactly this record. |
--digest SHA256 |
unset | Records whose payload has this full 64-character SHA-256 digest. A digest names a payload, not a record, so several records can match. | |
--actor ACTOR |
unset | Who produced it: an engine id, a provenance (trusted, agent, untrusted), or vibey for events vibey wrote itself. |
|
--since TIME |
unset | Produced at or after this ISO-8601 time; no zone means UTC. | |
--until TIME |
unset | Produced before this ISO-8601 time; no zone means UTC. Must be after --since. |
|
--kind KIND (repeatable) |
unset | Event kind, by name or value, any case; repeat it for any of several. A kind this vibey does not know (a newer one wrote it) is matched exactly, with a note on stderr. | |
--text TEXT |
unset | Appears in the payload's JSON text, literally, any case. Must not be empty. | |
--limit N / -n |
50 |
At most this many, the latest matches (min 1). | |
--json |
off | Print JSON: every field of every event. | |
ledger show [PROJECT_ID] |
--limit N / -n |
50 |
Show the most recent N events (min 1). |
--phase PHASE |
unset | Filter to one phase, by name or value, case-insensitive (BUILD, deploy_design). |
|
--kind KIND |
unset | Filter to one event kind, by name or value, case-insensitive. | |
--json |
off | Print {"project_id": ..., "events": [...]} on stdout: every field of every event, oldest first, in the shape ledger search --json uses. With no project the message goes to stderr and the exit code is 1. |
|
ledger export PROJECT_ID |
--out FILE / -o |
required | Write the public, redacted ledger projection. A payload that was redacted carries _redactions: a list of {path, class} entries (the JSON Pointer to what was redacted and why), never a value, a hash or a length; a private segment of a path is published as [private]. See What gets published. |
--billing |
off | Write the operator-scoped billing projection consumed by vibey-gh forecast; it keeps metered spend fields and operational event kinds only, and not _redactions. |
|
ledger site |
--from FILE |
required | A shard file written by vibey ledger export. Needs no database. |
--out DIR / -o |
required | The directory to write the site into. | |
--json-only |
off | Build the JSON surface: records/<event_id>.json, index.json, manifest.json. Required until the human-first record pages exist; without it the command exits 2. An invalid shard exits 1. |
ledger search applies every criterion it is given, in Postgres, to one project's
ledger (the latest when PROJECT_ID is omitted). A malformed criterion is a usage
error (exit 2), checked before any connection is opened; an unknown PROJECT_ID
prints unknown project <id> and exits 1. It prints one line per event, oldest
first, then a line saying how many matched and whether older matches were cut.
What gets published covers export and site.
ledger show prints one line per event, oldest first:
#<seq> <YYYY-MM-DD HH:MM:SS> [<PHASE>] <kind> [<engine>]. Filters apply
before --limit. Defaults to the most recently created project.
vibey queue¶
See the job queue in claim order, move a job to the front of it
(ADR-0054), and reap what is
stuck (ADR-0056).
Bare vibey queue prints help.
vibey queue bump 6f1c2d3e-4a5b-4c6d-8e7f-9a0b1c2d3e4f --project 0b5c9a4e-1d4c-4c47-9a2a-3c1d2b8f9e10
| Subcommand | Option | Default | What it does |
|---|---|---|---|
queue list [PROJECT_ID] |
--json |
off | Every unfinished job: running work first (running), then waiting work numbered in the order the claim will take it. A bumped job is marked bumped #N, one pulled forward for another job says which, and a job still waiting on unfinished dependencies says how many. A job in a phase or state this vibey does not know is listed with - in place of a number, marked as not claimable by this vibey (claimable_here: false in JSON). Defaults to the latest project; an unknown id prints unknown project <id> and exits 1. |
queue bump JOB_ID |
--project PROJECT_ID |
latest project | Run the job next: after whatever is running, ahead of every un-bumped waiting job, behind anything bumped before it. Its unfinished dependencies move forward with it, dependencies first. Prints every job moved with its new place and any already ahead. |
--source NAME |
unset | An automation naming itself (see below). | |
--json |
off | Print the change as JSON. | |
queue reap |
--project PROJECT_ID |
latest project | One pass of the queue reaper: every expired lease (requeued while attempts remain, parked with a delivery_exhausted gate once they are spent), work claimable and unclaimed for [queue.reap] stale_ready_seconds in every project, and -- with a broker configured -- vibey's two policies reconciled onto the queues it owns and checked on them, every queue measured, and each dead letter on an owned queue parked as a bus.dead_letter job (once, whichever project's pass reads it). A lease or claimable-work sighting is recorded under its own job's project; a broker sighting once, under this project. |
--dry-run |
off | Judge everything and change nothing: no requeue, no park, no ledger event, no policy write. Prints what it would do. | |
--json |
off | Print the report as JSON: ok, acted, surfaced (each with object, queue, condition, measured, threshold, unit, action), policy, notes, unreadable. |
|
queue unbump JOB_ID |
--project, --source, --json |
as bump |
Take the job out of the lane. The lane is always the jobs bumped by name plus their unfinished dependencies, so every pulled-in job no remaining named job needs leaves with it; the output and the ledger list exactly what was removed. Refused while another named job depends on this one. |
A bump changes order only. It never interrupts the running job or touches its lease,
never makes a job claimable before its dependencies succeed, and never shortens a
run_after a capacity deferral set; phases and human gates apply as before. A job that
is running can be bumped: it keeps its place if the attempt returns it to the queue.
Who may reorder is decided by the project's own vibey.toml — at the root of the
repository the project record names, never the current directory — and nothing on the
command line can widen it. With no --source, the caller must be the operator: the
account that owns that file (or the repository root, when there is none), checked by uid.
With --source NAME, NAME must be declared in that file's [queue.priority] sources
(see the configuration reference) and the caller must
still be that account. Anything else is refused: nothing moves and the command exits 3
with the reason.
Every request is recorded on the project ledger — JobPriorityBumped,
JobPriorityUnbumped or JobPriorityRefused — whether it moved something, moved
nothing (bumping a job already bumped or already finished, un-bumping one that is not:
exit 0 with nothing moved or nothing to move), or was refused. The grant is checked before the job is looked up, so a refused
request is recorded whether or not the job exists. vibey ledger search --kind
JobPriorityRefused lists the refusals. Refused with exit 3, and recorded:
- a caller that is not the operator, or a source not declared or not run as the operator;
- a
vibey.tomlor repository this process may not read, or one that is not valid TOML; - a job that does not exist in the project, or is in a state or phase this vibey does not know; an un-bump of a finished job;
- a bump whose dependency can never finish (failed or cancelled), or a dependency ring — the message names it;
- an un-bump of a job that another named job still depends on — the message names them;
- a request the database aborted to break a lock cycle (retry it).
queue reap prints what it reaped (reaped:), what is stuck and was only surfaced
(stuck, surfaced, nothing moved:), what was stuck and no longer is (no longer stuck,
closed on the ledger:), the broker policies' check (verified or NOT VERIFIED, naming
each owned queue that does not carry its policy), notes, and every source it could not read
(UNREAD:). A surfaced condition is recorded once for the whole fleet -- every pod and
every queue reap share the ledger -- until a pass that read its source whole records it
cleared. Each verdict line
names the action, the condition, the object, the queue, the measured value and the
threshold. It exits 0 when every source was read and the broker policy, where one was
reconciled, read back as written; otherwise 1 -- a reaper never reports success it did not
observe. It never deletes a dead letter: answer the parked job's gate with vibey answer
GATE_ID --choice replay (publish it back to the queue it died on, at least once -- offered
only when that queue is one vibey owns, since its name comes from the message's own
headers) or --choice dismiss; the broker's copy stays on the dead-letter queue either way. Its
thresholds are [queue.reap].
vibey-gh slots¶
How many runs of one local model may run at once on this device, measured per device
(ADR-0058).
This is a vibey-gh command, part of the same distribution:
| Subcommand | What it does |
|---|---|
vibey-gh slots corpus --pool FILE --out FILE |
Draw a stratified corpus of turn segments from a turn pool (the storm builds one with storm_turn_pool.py). |
vibey-gh slots calibrate --corpus FILE |
Sweep N = 1, 2, 3, ... on this device beside an idle production runner. Stops at a broken bound or a plateau, checkpoints every step, and records the evidence keyed to the device's fingerprint. |
vibey-gh slots allowed |
Print the number a queue may run at once here, with the reason on stderr. Missing or stale evidence prints 1 and requests a calibration. |
Every option is in the vibey-gh CLI reference.
vibey-gh sabbath¶
The Sabbath as the release tooling keeps it (sub-doctrine 8.i,
ADR-0072).
vibey-gh merge-train and vibey-gh promote stand down inside the window: they print
the hold, write it to the job summary, and exit 0. vibey-gh sovereign --beat keeps
beating, recording "resting until ...".
| Subcommand | What it does |
|---|---|
vibey-gh sabbath status |
The window, the zone, the location source, and every lane paused for the Sabbath. |
vibey-gh sabbath register-lane --name NAME [--cwd DIR] -- COMMAND ... |
Pause a lane for the Sabbath. The heartbeat runs COMMAND after sundown Saturday and forgets the lane once it succeeds. |
vibey-gh sabbath resume [--dispatch] |
Outside the window, resume paused lanes now. --dispatch also re-fires the held merge train and promotion. |
vibey deploy¶
Phases ④–⑥. Bare vibey deploy prints help. Each subcommand takes an
optional PROJECT_ID, defaulting to the latest project; an unknown id
prints unknown project <id> and exits 1.
vibey deploy status
| Subcommand | What it does |
|---|---|
deploy status |
Prints the project name, its current phase, cycle, and the endpoint recorded by the most recent deployment_verification artifact ((none) if there is none). |
deploy inspect |
Prints spec id, scope digest, and monthly budget from the most recent deployment_spec_accepted decision. When no spec has been accepted it prints the placeholders default / none / $100.00. |
deploy plan |
Placeholder, not yet implemented: prints Status: NOT EVALUATED with every check marked "not checked". Reads no IaC changeset and evaluates no budget. |
deploy cancel |
Placeholder, not yet implemented: prints a notice and cancels nothing. No cloud resource, ledger event, or job or phase state is touched. |
deploy rollback |
Placeholder, not yet implemented: prints a notice and performs no rollback. |
Only deploy status and deploy inspect read real state today; the other
three reserve the command surface. Deployments themselves run through
vibey worker (see --azure) after an explicit opt-in at REVIEW.
vibey doctor¶
Check local PostgreSQL readiness, engine installs and auth; optionally install PostgreSQL, run the conformance suite, or run the in-cluster preflight instead.
vibey doctor --conformance --record
| Option | Default | What it does |
|---|---|---|
--conformance |
off | Run the 9-check conformance suite against each checked engine that is installed. |
--engine ENGINE |
unset | Check one engine: claudeloop, codexloop, gptossloop, qwenloop, or claudeloop-local. An unknown name prints Unknown engine: <name> and exits 1. |
--record |
off | Persist preflight (and conformance, with --conformance) results to engine_health. Exits 1 if no project exists. |
--project ID |
latest | Project to record health for, with --record. |
--cluster |
off | Run the in-cluster preflight instead of the engine checks — see Kubernetes guide. |
--engines LIST |
unset | With --cluster: the worker's own --engines allow-list (chart value worker.engines). engine-auth requires exactly these. Parsed as the worker parses it; empty means unset. |
--provider NAME |
scripted |
With --cluster: the worker's own --provider (scripted, claudeloop, gptossloop, or the old name qwenloop; chart value worker.provider). claudeloop adds claudeloop to what engine-auth requires. |
--install-postgres |
off | Install and start local PostgreSQL when it is missing or stopped. This is explicit; the default doctor never changes the host. It cannot be combined with --cluster. |
--sovereign-fit |
off | Run the GPT-OSS capacity probe (scripts/sovereign_probe.py) against $VIBEY_OLLAMA_URL (default http://127.0.0.1:11434) and $VIBEY_OLLAMA_MODEL (default gpt-oss:20b), and persist the measured fit; see Local models. Prints sovereign fit PASS — recorded <path>, or sovereign fit FAIL — no measured capacity fit recorded, which makes doctor exit 1. |
--fit-output PATH |
~/.local/state/vibey/sovereign-fit.json |
Where --sovereign-fit writes the fit record. Its parent directory is created. |
--json |
off | Print one JSON document on stdout instead of lines — see --json. It changes only the output: the same checks run, --record, --install-postgres and --sovereign-fit do what they do without it, and the exit code is the text form's. |
With --engine unset, doctor checks the two paid engines (claudeloop,
codexloop) whether or not they are installed —
missing ones print NOT INSTALLED — and adds each local engine that is switched
on: gptossloop unless VIBEY_FEATURE_GPTOSSLOOP is set to anything but a truthy
value or, if that variable is unset, ./vibey.toml in the current directory has
[features] gptossloop = false; qwenloop and claudeloop-local when their
variable is truthy or, if it is unset, ./vibey.toml sets [features] qwenloop
/ claudeloop_local = true. --engine gptossloop and --engine qwenloop work
regardless of the switch. When qwenloop is switched on, doctor first prints
note: qwenloop is switched on, and since ADR-0064 it runs a Qwen model ....
Each engine line shows install state, version, and auth. Auth is the exit
status of <binary> doctor; if that command cannot run, doctor falls back
to the engine's API-key variable (see
Environment variables). Each probe starts from the
engine's allow-listed environment, never doctor's own: the defaults, or with
--record the target project's engine_environment, so a credential that project
declares reaches the auth check and the conformance run as it reaches a session. A
forbidden declaration in that project exits 2.
--json. One document, printed after every check has run:
{
"ok": true,
"mode": "local",
"checks": [{"name": "db-passwordless", "mark": "UNKNOWN", "detail": "VIBEY_PG_URL is not set; nothing to check"}],
"engines": [{"engine_id": "claudeloop", "installed": true, "version": "1.2.3", "auth_ok": true,
"detail": null, "conformance": [], "recorded": null}],
"postgresql": {"state": "READY", "version": "16", "ready": true, "detail": "..."},
"notes": []
}
ok is whether the process exits 0. mode is local, or cluster with --cluster, whose
document has only ok, mode, checks (one per preflight check) and notes. In checks,
mark is PASS, WARN, FAIL, UNKNOWN (or OK/SKIP for the Ollama line) and name is
the check's own name, read from the MARK name detail lines the sabbath, supervisor,
host-health, database and hub-exposure sections print. A line that fits no known shape is kept
whole as a check with name and mark null and the line under line; nothing is dropped. An
engine's conformance lists each check as {name, ok, detail}, and recorded is
engine_health or preflight when --record wrote that row. note: lines go to stderr and
into notes. A refusal before any check ran (an unknown engine, no project for --record, a
bad flag) goes to stderr with the text form's exit code and prints no document. A
--install-postgres that leaves PostgreSQL unready still prints the document, with ok false.
The final postgresql line shows READY, NOT READY, UNSUPPORTED, or
NOT INSTALLED. READY means a local PostgreSQL server at port 5432 accepts
connections and is at least version 14. --install-postgres exits 1 if the
package manager or service start fails, or if verification still does not reach
that state.
The db-passwordless line after it asks whether the app DSN's database admits a
login with no password at all: as the DSN's role and as the OS user doctor runs as,
on the DSN's host and, when that host is local, on each local socket directory. It
prints FAIL when one is let in, and doctor then exits 1. That happens with trust or peer
authentication: any process running as that user, an engine session included, can open
the database without VIBEY_PG_URL. Sub-doctrine 10.j
(ADR-0061)
requires scram-sha-256 for every connection; see
SECURITY.md §5 and §7.
It prints PASS when every attempt was refused, and UNKNOWN when none reached the server
or VIBEY_PG_URL is unset. UNKNOWN does not change the exit code.
With --conformance, the command exits 1 if any engine fails a check. The
worker does not select an engine for engine-driven jobs until a
doctor --conformance --record run has passed for it.
When VIBEY_PG_URL is set, doctor ends with two database checks
(ADR-0055).
Each prints PASS, FAIL or UNKNOWN, and doctor exits 1 if either fails:
ledger-guardfails when the roleVIBEY_PG_URLconnects as could rewrite the ledger: it is a superuser, ownsevent, holdsUPDATE,DELETEorTRUNCATEon it or a partition, or finds a guard trigger missing or disabled. A single-DSN install fails here until its roles are split (database roles).local-authfails when the server lets a password-less connection in as the owner or a superuser. It attempts one on the DSN's host and, for a local host, on the local socket directories. It also fails whenpg_hba.confhas atrust,peer,ident,md5orpasswordrule that can match them, and whenpassword_encryptionis notscram-sha-256(sub-doctrine 10.j, ADR-0061). It isUNKNOWN(not a failure, never a pass) when it could neither get in nor readpg_hba_file_rules.
With VIBEY_PG_URL unset, ledger-guard prints UNKNOWN and nothing is checked.
The last line, hub-exposure, asks whether a running hub (vibey serve) listens where
vibey.toml says it may: PASS on loopback or on a LAN address [hub] lan = true
declares, FAIL on an undeclared one (the exit is then 1), and UNKNOWN when no hub is
running or its runtime record names a process that is gone. A [hub] table that cannot be
read is a FAIL.
When a local engine is switched on, the ollama line asks the server (/api/ps, three
seconds, host and port only in what it prints) what it is holding. WARN when the configured
model (VIBEY_OLLAMA_MODEL, default gpt-oss:20b) is loaded at a context above
VIBEY_OLLAMA_CONTEXT (default 8192): Ollama reloads a model whose context differs from a
request's, so the first request pays for the reload and can time out, and a warm-up at a larger
window is how that happens. The line names the fix, ollama stop MODEL. OK when the model is
loaded within the ceiling or not loaded at all, SKIP when no server answered. Never a
failure: the exit code is not changed.
After the Sabbath lines, gate-notices counts the open gates in projects nobody will be
told about -- [notifications] off (the default), on with desktop alerts off and no
webhook, or a table that does not parse -- and prints them as WARN gate-notices N gates
waiting, nobody will be told: <project> <count> (<reason>), ..., with how many more will
be. PASS when there are none, or when every waiting gate's project has a channel;
UNKNOWN when VIBEY_PG_URL is unset or the gates cannot be read. It is a report, never
a failure: those gates are still listed by vibey gates, and each is recorded on the
ledger as undeliverable ([notifications]).
--cluster ignores --conformance, --engine, --record and --project,
runs up to eight checks, and exits 1 if any fails: the DSN host resolves beyond
its own namespace, the process is not root, the workspace (current directory)
is writable, the engines the worker uses have API keys, the database accepts a
connection, no migrations are pending, and the two database checks above
(ledger-guard, local-auth). The last three are skipped when the database
connection fails.
The engine check (engine-auth) judges what the worker was told to run, not
what is on PATH — every runner ships in the image since ADR-0037, so a
binary's presence says nothing. It requires each engine in --engines, plus
claudeloop under --provider claudeloop, to be on PATH with one of its
API-key variables set (gptossloop and qwenloop take none). With neither, nothing is
required: the check passes and reports which engines in the worker's default
pool have a key, so a default install says plainly when no engine-driven job
can run. --engines and --provider without --cluster exit 2.
vibey migrate¶
Apply migrations as the schema's owner, then make the application role's privileges
exactly the declared ones
(ADR-0055).
It reads the owner's DSN from VIBEY_PG_MIGRATE_URL and the application's from
VIBEY_PG_URL. It takes no options.
vibey migrate
- Applies pending migrations under the migration lock, as the owner.
- Creates the role
VIBEY_PG_URLnames if it is missing and the DSN carries a password. A role that is a superuser, the owner, or a member of the owner is refused. - Revokes everything that role holds and grants exactly
APP_ROLE_GRANTS. - Attaches the
TRUNCATEguard to any partition ofeventthat lacks it. - Connects as the application role and prints the ledger guard.
It exits 2 when VIBEY_PG_MIGRATE_URL is unset. It exits 1 when VIBEY_PG_URL is unset
(nothing reconciled, guard unchecked), or when the guard is not in force: for instance
while VIBEY_PG_URL still names the owner. The Helm chart runs it as the migrate init
container of the worker and the operator, the only place the owner's DSN is mounted.
vibey operator¶
Run the Kubernetes operator, reconciling VibeyProject custom resources
(ADR-0025). Requires the optional extra, pip install 'vibey-engine[operator]';
without it the command prints
operator support is not installed: pip install 'vibey-engine[operator]' and exits 1.
vibey operator --namespace vibey
| Option | Default | What it does |
|---|---|---|
--namespace NS |
unset (cluster-wide) | Watch a single namespace instead of the whole cluster. |
vibey worker¶
Long-running worker: listens on vibey_job_ready and dispatches jobs across
every phase for one project, or with --all-projects for every project.
vibey worker --provider claudeloop --engines claudeloop,codexloop -j 2
| Option | Default | What it does |
|---|---|---|
--engines LIST |
the two paid engines plus every local engine switched on (gptossloop by default) |
Comma-separated allowlist of engine ids (claudeloop, codexloop, gptossloop, qwenloop, claudeloop-local) for engine-driven jobs. An unknown id prints Invalid engine: ... and exits 2; a retired one (cursorloop, agyloop) is refused the same way, the message naming ADR-0078. A local engine joins the pool only while its switch is on (see below): gptossloop unless switched off, qwenloop and claudeloop-local only when switched on. A list that matches none of the worker's engines — --engines qwenloop with its switch off, say — is refused at startup with --engines <list> matches none of this worker's engines (...) and exits 2, rather than starting a worker with no engine that would defer every engine-driven job forever. |
--parallelism N / -j N |
1 |
Concurrent job loops, 1–16. The effective count is clamped to twice the number of allowed engines and to the CPU count, and is never below 1. |
--once |
off | Process one job and exit (processed one job or no ready job), instead of running forever. |
--provider {scripted,claudeloop,gptossloop} |
gptossloop |
DESIGN and decomposition providers. gptossloop uses the sovereign local DESIGN and decomposition providers on Ollama (GptossloopDesignProvider, GptossloopWorkPlanProducer; reads $VIBEY_EVIDENCE_DIR), recorded in the ledger as gptossloop. claudeloop uses a live session for both DESIGN and decomposition, capped by --max-turns / --max-dollars. scripted is fully offline. qwenloop is still accepted and read as gptossloop, with a notice on stderr (ADR-0064). Any other value exits 2. |
--max-turns N |
25 |
Turn cap per claudeloop DESIGN or decomposition session (min 1). |
--max-dollars F |
2.0 |
Dollar cap per claudeloop DESIGN or decomposition session (0.01–10). |
--ollama-model NAME |
$VIBEY_OLLAMA_MODEL, else $VIBEY_SOVEREIGN_MODEL, else heretic (hf.co/mradermacher/gpt-oss-20b-heretic-v2-GGUF:MXFP4_MOE) |
Local model for --provider gptossloop, and the model the worker hands gptossloop (below); accepts heretic, stock (gpt-oss:20b), or any Ollama tag (ADR-0099). Ignored by the other providers. |
--project ID |
latest | Project to work on. |
--all-projects |
off | Serve every project (#1189). Each pass asks the queue which projects have a job the claim would hand out now, ordered by each project's next job in the claim's own order (bump, priority, due time), and claims through that project's own loop — the same FOR UPDATE SKIP LOCKED claim, budgets, Sabbath gate, capacity circuits, phase gates and engine selection a single-project worker uses. A project's loops are built, and its engines preflighted, the first time it has work. A project that cannot be served (an unknown phase, an --engines list matching none of its engines, a forbidden engine_environment) prints project <name> refused: its jobs stay queued for a worker that can serve them and the worker goes on with the others. With nothing queued anywhere it waits on any project's vibey_job_ready instead of exiting, so a supervised worker never restart-loops. Parallel loops (-j) are clamped to the CPU count only. Exits 2 with --project, --wait-for-project or --auto-answer. This is what vibey supervisor install runs. |
--wait-for-project SECONDS |
unset (min 1.0) | Poll every N seconds for a project instead of exiting 1 when none exists yet — for long-lived deployments, where exiting means a restart loop. |
--ignore-sabbath |
off | Claim jobs through a Sabbath window that would otherwise leave this worker claiming nothing. For this worker only, said on stderr when a window would have held, never stored (see the Sabbath guide). |
--auto-answer |
off | Answer this project's waiting gates for you, so an unattended run keeps going: the DESIGN interview's questions with their own defaults (accept_defaults), {"max_attempts": 10} for an escalation_exhausted gate, {"max_rounds": 6} for verify_repair_exhausted and integrate_repair_exhausted, and {} to deliver a delivery_exhausted job once more. Never a spending gate (budget_exhausted), engine_misconfigured, research_evidence, an approval or a review: those wait for you, and each sweep names them. The consent is the flag, for this one worker and one project (exits 2 with --all-projects), and nothing is stored. Each answer is recorded on the ledger as GateAnswered by auto-answer, under a request id derived from the gate so a replay is a no-op. |
--auto-answer-limit N |
30 |
The most answers --auto-answer gives before it says so and leaves every gate to a person (min 1), so a job that cannot finish does not retry for ever. |
--local-max-turns N\|unlimited |
unset | The turn limit each local runner (gptossloop, qwenloop) session is started with, in place of its per-effort 8, 16, 40, 64 or 96: a whole number, or unlimited (handed over as 100000, a count no session reaches). A turn limit is a cost control, and a local model costs nothing per turn; a small model that needs many tool calls to read a repository fails at 16 for the limit's reason, not the work's. A session still ends when it reports the task done, is stopped (vibey controls) or fails. A bad value exits 2. Paid engines keep their limits; ULTRA keeps its own (no flag). Same as VIBEY_LOCAL_MAX_TURNS, which the flag sets for this worker. |
--auto-record-research-gaps |
off | DESIGN research that can obtain no evidence (no search backend, no VIBEY_EVIDENCE_DIR) records the topic as not researched and goes on, instead of parking a research_evidence gate; a gate already parked is answered with nothing, which runs the research again. The spec says what was not researched, so nothing is invented. Sets VIBEY_DESIGN_RESEARCH_ON_UNAVAILABLE=record_gap for this worker (beating ./vibey.toml and an earlier gate). One project: refused with --all-projects. |
--autonomous |
off | Every switch that is safe to leave on for an unattended run: --auto-answer, --auto-accept-all and --auto-record-research-gaps. Never --auto-publish (it sends the work to GitHub, so it is its own flag), never a deployment gate and never a spending gate. One worker, one project. |
--auto-accept-design |
off | Accept this project's DESIGN spec yourself, once DESIGN has finished (no DESIGN job of the cycle unsettled, a spec present), instead of waiting for vibey design accept. That is your decision that the spec is right, given in advance and only for this worker and one project (not with --all-projects). The acceptance guards still apply, so an open blocking question refuses it and the worker asks again. It never opts into VISUAL_DESIGN, and it accepts once. The ledger's visual-design event carries accepted_by: auto-accept-design. Without the flag nothing changes: --auto-answer still never answers an acceptance. |
--accept-spec-json PATH |
unset | With --auto-accept-design: import this reviewed spec (the file vibey design accept --spec-json takes) just before accepting, instead of accepting what the synthesizer wrote. A file that cannot be read exits 2 before any engine starts. |
--auto-accept-review |
off | Accept this project's REVIEW approval gate yourself: the verdict accept, never changes or cancel, and only when the gate offers accept. Your decision that the review is good, given in advance, for one worker and one project (not with --all-projects). Recorded on the ledger as GateAnswered by auto-answer, and the startup line names it. |
--auto-finish-local |
off | Answer the deployment choice after REVIEW with local_only, the gate's own default, so the project finishes without deploying. It never answers deploy. |
--auto-accept-all |
off | --auto-accept-design, --auto-accept-review and --auto-finish-local together: every acceptance from DESIGN through REVIEW, finishing local. It is not --auto-answer: the interview and retry gates stay yours unless you add that flag too. |
--auto-publish |
off | Answer this project's publish_interview gate accept_defaults: publish exactly what its prompt shows (a draft pull request, or a new private repository for a project with no remote), never custom. It sends the finished work to GitHub, so it is its own flag and no other --auto-accept flag implies it, --auto-accept-all included. Only meaningful for a project whose [publish] mode is interview; one worker and one project. |
--azure {memory,az} |
memory |
Azure client for the deploy stage set. memory is an in-memory adapter that touches no real infrastructure. az uses the real Azure CLI and mutates real resources on consented deploys; the worker runs az account show first and exits 1 if you are not logged in. Any other value exits 2. |
What no acceptance flag answers. The publish interview is answered only by --auto-publish. The deployment gates (deploy_interview, deploy_acceptance, deploy_demo_review, deploy_failure_triage) and every spending gate (budget_exhausted) are never answered by any of these flags, together or alone. deploy_acceptance is explicit consent to change real infrastructure, which no flag sends. --auto-accept-design never opts into VISUAL_DESIGN, so that stage is never entered by an unattended run.
The VISUAL_DESIGN stage always uses the scripted visual provider.
On start the worker preflights every allowed engine — including each local
engine that is switched on, so a local engine is visible in vibey engines
like every other. Engines with no passing recorded conformance produce
warning: no recorded conformance for <names> -- engine-driven jobs will not select them until `vibey doctor --conformance --record` passes.
It then prints
worker started: project=<name> engines=<list or all> parallelism=<n> provider=<p>
(worker started: all projects ... with --all-projects, which preflights each
project when it is first served and prints serving project=<name> (<id>) then).
Local engines (ADR-0015, ADR-0038, ADR-0064): the worker runs gptossloop
unless VIBEY_FEATURE_GPTOSSLOOP is set to a value that is not truthy, and
qwenloop or claudeloop-local only when VIBEY_FEATURE_QWENLOOP or
VIBEY_FEATURE_CLAUDELOOP_LOCAL is truthy. Unlike doctor, the worker does not
read [features] from vibey.toml; it falls back to a features table in the
project's stored config, which no creation path writes today, and then to each
engine's default. In practice the environment variables are the only switches
for the worker. When qwenloop is switched on the worker prints
note: qwenloop is switched on, and since ADR-0064 it runs a Qwen model ... at
start. With VIBEY_OLLAMA_URL set, the worker hands gptossloop
GPTOSSLOOP_BASE_URL=<url>/v1 and GPTOSSLOOP_MODEL (--ollama-model, else
VIBEY_OLLAMA_MODEL, else gpt-oss:20b), and qwenloop QWENLOOP_BASE_URL
only — never a model; a variable the operator set already is left alone.
qwenloop shared-turn worker¶
qwenloop server turn-worker hosts one inference server behind the durable
RabbitMQ turn queue so multiple lane processes can share it. Configure
turn_dispatch_mode = "rabbitmq", turn_queue_url, and optionally
turn_queue_name in the qwenloop configuration before starting it. The
command refuses direct mode, starts or verifies the configured inference
server, and then consumes turns until stopped. Keep the broker URL in
operator-owned secret configuration; use a TLS AMQP URL and least-privilege
vhost/queue credentials when the broker is not local. If the worker stops,
restart the same command after RabbitMQ is available; durable requests remain
on the queue for another worker.
qwenloop server benchmark runs a measured direct-versus-hybrid-versus-RabbitMQ
comparison against the configured local server and stores the per-mode rates and
winner in the user cache. Set
turn_dispatch_mode = "auto" to use that persisted winner on subsequent runs;
missing or invalid benchmark data safely falls back to direct dispatch. Use
--samples and --concurrency to control the experiment size.
The queue reaper (ADR-0056) runs in the same idle iterations: after the lease reap, at
most once per [queue.reap] interval_seconds across all of the worker's loops, the
worker surfaces claimable, unclaimed work in every project and -- with a broker
configured -- reaps the broker, as vibey queue reap does. Its findings go to the ledger
(QueueReaped) and to stderr (queue.reaped, queue.stuck, queue.unstuck,
queue.reap_unreadable). A reap that fails is reported (lease reap failed:, queue reap
failed:) and the worker carries on.
Shutdown (ADR-0026): SIGTERM drains the worker — it finishes the job in
hand, claims no more, and exits. A SIGTERM that arrives during startup,
before the handler is installed, is latched at import time and honoured as
soon as the event loop starts. Ctrl-C aborts immediately with exit 130. The
worker writes diagnostic lines to stderr (sigterm handler registered,
drive[<i>] iter=...); they are temporary instrumentation, not errors.
Environment variables¶
Variables read by code under src/vibey:
| Variable | Read by | Effect |
|---|---|---|
VIBEY_PG_URL |
every command that opens the database; recover; doctor; migrate |
The application role's PostgreSQL 14+ DSN (database roles). There is no default: when unset, vibey refuses with VIBEY_PG_URL is not set. vibey will not guess a database. (exit 3 from guarded commands, a traceback from the others). vibey install --postgres installs the server but does not set this variable for the parent shell. |
VIBEY_PG_MIGRATE_URL |
migrate only |
The owner's DSN. Migrations run on it and the application role's grants are reconciled from it. Give it to that one command (VIBEY_PG_MIGRATE_URL=… vibey migrate); never export it. |
VIBEY_STATE_PG_URL, VIBEY_STATE_KEY and the other VIBEY_STATE_* |
state only |
The database the state sync reads and writes (default VIBEY_PG_URL), the state key, and where the sealed state lives; every one in State sync. |
VIBEY_EVIDENCE_DIR |
work --provider gptossloop, worker --provider gptossloop |
Directory of reading material for the gptossloop DESIGN provider's research stage. Unset means research refuses and DESIGN stops there. |
VIBEY_FEATURE_GPTOSSLOOP |
doctor, worker, loops |
1, true, yes, or on (case-insensitive) enables gptossloop; any other set value, 0 included, disables it. When set it overrides config. When unset, doctor and loops fall back to [features] gptossloop in ./vibey.toml and worker to the project's stored config; with neither, gptossloop is on (ADR-0064). |
VIBEY_OLLAMA_URL |
work and worker with --provider gptossloop; doctor --sovereign-fit; loops; the worker's local engines |
The Ollama server, default http://127.0.0.1:11434. Only an http(s) URL with a host is accepted. |
VIBEY_OLLAMA_MODEL |
as VIBEY_OLLAMA_URL |
The local model tag (or variant key heretic / stock); --ollama-model beats it, and it beats VIBEY_SOVEREIGN_MODEL (ADR-0099). |
VIBEY_SOVEREIGN_MODEL |
as VIBEY_OLLAMA_URL |
Sovereign model variant key: heretic (default, hf.co/mradermacher/gpt-oss-20b-heretic-v2-GGUF:MXFP4_MOE) or stock (gpt-oss:20b). Beaten by --ollama-model and VIBEY_OLLAMA_MODEL; an unknown value raises an error naming the valid keys (ADR-0099). |
VIBEY_OLLAMA_TIMEOUT |
the sovereign DESIGN and DECOMPOSE providers | Seconds per request, default 900. |
VIBEY_OLLAMA_CONTEXT |
as VIBEY_OLLAMA_TIMEOUT |
Context ceiling in tokens (num_ctx), default 8192, at least 4096. |
VIBEY_OLLAMA_OUTPUT |
as VIBEY_OLLAMA_TIMEOUT |
Output ceiling in tokens (num_predict), default 2048. |
VIBEY_OLLAMA_THINK |
as VIBEY_OLLAMA_TIMEOUT |
The reasoning level asked for on the first request: low, medium, high, max, true, false or none. Unset or none sends no think, leaving it to the model, which for gpt-oss is a long reasoning pass that can spend the whole output budget before the answer begins. low makes the first attempt succeed where it would otherwise be cut short and retried. The retry has its own, VIBEY_OLLAMA_RETRY_THINK (default low). |
VIBEY_OLLAMA_TEMPERATURE |
as VIBEY_OLLAMA_TIMEOUT |
Sampling temperature, 0 to 2, default 0. At 0 a job's retries are the same request and fail the same way, so a plan that is rejected once is rejected every attempt (seen: seven identical build.decompose attempts). Raise it (0.6 is a start) when attempts keep failing for the same reason; the answer to an unchanged question then varies, so the DESIGN and DECOMPOSE results are no longer reproducible. A bad value exits 2. |
VIBEY_OLLAMA_RETRY_CONTEXT |
as VIBEY_OLLAMA_TIMEOUT |
The largest context window the one retry of a cut-short reply may ask for, default 32768, at least 4096. A prompt that leaves too little of VIBEY_OLLAMA_CONTEXT for the reasoning and the answer fills the window, and asking again inside the same window only fails again; the retry instead opens as much as it needs, up to this. A configured VIBEY_OLLAMA_CONTEXT above it is never lowered. Set it equal to VIBEY_OLLAMA_CONTEXT to keep the retry inside that ceiling. Ollama reloads a model whose context changes, so a retry that grows the window pays a reload: raise VIBEY_OLLAMA_CONTEXT itself when that is the usual case. |
VIBEY_OLLAMA_FIT |
as VIBEY_OLLAMA_TIMEOUT |
Path to a fit record written by vibey doctor --sovereign-fit. When it matches, its measured context and output replace the two ceilings above; see Local models. Unset, unreadable or mismatched means the configured ceilings. |
VIBEY_REVISION |
as VIBEY_OLLAMA_FIT |
When set, a fit record is used only if its revision equals this value. |
VIBEY_SEARXNG_URL |
search, fetch, worker, the sovereign DESIGN research stage |
Base URL of a SearXNG instance with its JSON format enabled. Passed to engine sessions as WEBSEARCH_SEARXNG_URL. Queries go to that instance, so run your own if they are private. |
VIBEY_BRAVE_API_KEY |
as VIBEY_SEARXNG_URL |
A Brave Search key. Used by vibey's own processes only; never passed to a session. |
VIBEY_SEARCH_PROVIDER |
as VIBEY_SEARXNG_URL |
searxng or brave; needed only when both are configured (SearXNG wins otherwise). |
VIBEY_SEARCH_TIMEOUT |
as VIBEY_SEARXNG_URL |
Seconds per search or fetch request. A bad value exits 2 before any engine starts. |
VIBEY_SEARCH_RESULTS |
as VIBEY_SEARXNG_URL |
How many results a search returns. |
VIBEY_FEATURE_QWENLOOP |
doctor, worker, loops |
1, true, yes, or on (case-insensitive) enables qwenloop, the runner on a Qwen model; any other set value disables it. When set it overrides config. When unset, doctor and loops fall back to [features] qwenloop in ./vibey.toml and worker falls back to the project's stored config; with neither, qwenloop is off. |
ANTHROPIC_API_KEY |
doctor auth fallback; doctor --cluster (which also accepts ANTHROPIC_AUTH_TOKEN) |
claudeloop credentials. |
OPENAI_API_KEY |
doctor auth fallback; doctor --cluster (which also accepts AZURE_OPENAI_API_KEY, CODEX_API_KEY) |
codexloop credentials. |
VIRTUAL_ENV |
engine session launch | Removed, together with VIRTUAL_ENV_PROMPT, PYTHONHOME, PYTHONPATH, and the matching PATH entries, from the environment passed to engine sessions, so an engine does not install into or run vibey's own interpreter. |
Engine sessions otherwise inherit the caller's environment. Build-gate and
git subprocesses inherit it minus every GIT_* variable. The engine CLIs
read further variables of their own; see each runner under
src/vibey_runners/.