Virgo CLI specification (v1 draft — greenfield build)
Status: decision consolidation for the v2 greenfield installation (fresh build on a new hub machine; the currently running installation is untouched and no interoperability with it is required). Implementation tracks this document; conflicts resolve through the owner, not silent drift. v0 history: machine/persona verbs consolidated 2026-08-03..05; v1 integrates the owner's 2026-08-09 requirements (wizard UX, agents, adapters) and the seven field-proven operational verbs.
One entrypoint, two interaction modes
virgo is the single user-facing command. Every operation is a subcommand; no companion scripts, no scattered binaries. Daemons (hub services, keepers, watchdogs, advertisers) ship inside the release and are managed by the CLI — users never launch them by hand.
Every subcommand supports both modes with one grammar:
- Direct mode — the subcommand is required; everything else is a flag. Any operation is expressible as one non-interactive command line (scriptable, replayable, testable).
- Wizard mode — invoked automatically when required inputs are missing. The wizard asks stepwise questions and always shows a computed recommendation as the default answer (accept with Enter). Wizards never ask for what they can measure: every step inspects current state first and already-satisfied steps pass silently.
Wizard answers persist into the machine/installation profile, so a later run can replay unattended (--profile). Interrupted wizards resume where they left off (progress manifest). Human-only steps (credential placement, OS permission grants, engine logins) are guided, awaited, then verified — never automated.
Installation
A shell script installs the release and nothing else:
curl -fsSL https://virgo.codes/install.sh | sh # installs `virgo` (hash release)
The installer puts a release under ~/.local/lib/virgo/<sha> with a current symlink and virgo on PATH. All configuration happens after installation, through virgo onboard — the installer asks nothing.
Top-level command map
| Command | Purpose |
|---|---|
virgo onboard | Make THIS machine part of an installation: first machine (becomes the hub) or join an existing hub |
virgo init | Scaffold the owner's canon (base, classes, people); once per installation |
virgo hub start/stop/status | Manage hub services (store, transport, registry, web) — hub machine only |
virgo agent list/add/remove/repair | Manage AI engines available on machines (claude/codex/…) |
virgo adapter list/add/remove/doctor | Manage human-channel adapters (teams; later email/slack/telegram) |
virgo workspace add/list | Work-domain isolation boundaries (implicit default home) |
virgo project add/list/onboard | Work units (typically repos) inside a workspace |
virgo persona add/up/down/list/status/retire | Resident persona lifecycle |
virgo persona checkpoint <name> | Land + ingest a live persona's state without stopping it |
virgo persona reincarnate <name> | Anti-pollution context renewal: checkpoint, clear the session context, re-brief from clean records, resume |
virgo persona launch <project> | On-demand lead/deputy activation for a project |
virgo render [--scan] | Render config artifacts (CLAUDE.md, settings, hooks, AGENTS.md, skills, MCP) from canon; --scan reports drift |
virgo record sync/status/source add | Record-graph reconciliation and sources |
virgo duty list/add | Scheduled obligations (morning report, liveness alerts, receipts) |
virgo skill list/add/edit/approve | Work-skill management (shared skills gated by propose-approve) |
virgo status | Whole-system view: machines, personas, fences, queues, channels |
virgo operator verify-context / promote-completion | Attestation and receipt-bearing promotion |
virgo screen | Owner-specificity scan before any repo publication |
virgo machine profile | Canonical machine paths/identity (single source for path questions) |
virgo channel doctor [<binding>] | Diagnose a silent human channel (see CHANNEL-DIAGNOSTICS.md) |
Workspace removal stays deliberately absent (gated destructive semantics, to be specified with deletion semantics — not a casual verb).
virgo onboard — machine enrollment wizard
First question (persisted, structural): first machine (this machine becomes the installation's hub) or join existing hub (supply and verify hub address).
First-machine path — steps in order (each: measure → recommend → confirm):
- Install location — default recommended (
~/.local/lib/virgo, state in~/.config/virgo); custom allowed, recorded in machine profile. - Machine identity — hostname-derived id recommendation; renameable here and only here (identity is stamped, not guessed later).
- AI engines — pick one or more (claude / codex / more later). Per engine the wizard runs the engine's own install+login (human step: guided, awaited, verified). More engines can be added later via
virgo agent add. - Adapters — optional now, addable later via
virgo adapter add. Currentlyteams(choose: team channel or personal chatbot); planned: email, slack, telegram. Each adapter runs its own credential wizard (see CHANNEL-ADAPTERS.md) and ends with a verified delivery receipt. - Workspace — default
home(implicit, immutable id) auto-accepted; additional workspaces may be added now or later. - Project — default none; may register a first repo now.
- First persona — recommended default
root/secretary, suggested name the first Virgo-constellation suggestion (star names offered in canonical order; owner free to override). Scope/project/role selectable (see persona grammar below). - Hub start —
virgo hub startequivalent; services come up under supervision, acceptance-checked. - Finish — print the web UI URL (hub dashboard) and the one-line summary of what was created. Onboarding ends by handing the owner a working screen, not a wall of text.
Join path: verify hub address + enrollment token, register the machine with the hub registry, install engines (step 3) as needed, done — persona placement on this machine happens later through virgo persona add/up --machine. On completion it prints the hub's web UI link (hub machine remains the root screen).
A legacy/absent profile never guesses: the wizard asks the structural question again and re-stamps the profile (contract v2).
virgo init — owner canon scaffold
Runs once per installation (normally right after the first machine's onboard; re-running is a guided no-op). Scaffolds the owner's private canon: base invariants, class registry (secretary / team-coordinator / project-lead / coder-deputy), people, and the implicit home workspace. Same wizard grammar. The canon lives in the owner's private repo/graph, never inside the product release. (onboard = machine joins the system; init = the OWNER's knowledge base comes into existence. The two names are now disjoint on purpose.)
virgo agent — engines are per-machine capacity, models are bindings
Resolution of the open design questions:
- An agent = an engine installation on a machine (claude, codex, grok planned).
virgo agent addselects engine → runs its install/login on the target machine (--machine, else wizard asks; login is inherently per-machine, which is why agents are machine-scoped records). - A model is NOT a separate installable: it is an attribute chosen at binding time.
agent addrecords which models the installed engine offers; personas/roles bindengine+modelpairs. - Role defaults: each class in the registry declares its recommended engine+model binding (e.g. secretary → claude/fable, coder-deputy → codex/gpt-x).
virgo agent addends with an optional "attach to roles" checklist (pre-checked recommendations);persona addandpersona launchread these defaults and may override per persona. agent repairre-runs login/health for an existing agent record (expired logins are a measured state, surfaced byvirgo status).
virgo adapter — human channels
adapter add = pick type (teams now; email/slack/telegram planned) → run type-specific credential wizard → bind to a persona address and a direction (team channel vs personal chatbot) → verified end-to-end receipt (a real message round-trip) before the adapter is declared live. adapter doctor wraps CHANNEL-DIAGNOSTICS. Adapter bindings are canon records; the hub renders them into running gateway config (no hand-edited channel files).
virgo persona — lifecycle
persona add [name] --machine <host> --scope <ws> [--project <p>] --role <r> [--character <text>] [--language] [--tone] — creates the first character layer (class template + overlay), suggests constellation names, and defaults the FIRST persona of an installation to root/secretary (with the first constellation-name suggestion). Only the first defaults into the reserved root scope; later personas default to the home workspace, and a bare non-interactive persona add after the first is refused rather than guessed at. --machine records PLACEMENT and starts nothing. It is checked against the installation's machines — the hub's validated device registry — so an unknown machine is refused rather than written, and a CLI that cannot consult a registry refuses to place rather than recording a placement nobody can confirm. Omitting it is fine: an unplaced persona is valid, and the lifecycle refuses it by name later instead of guessing a host. --source-anchor <abs path> overrides that persona's live-tree root; without it the tree is <state_root>/personas/<name>. An anchor requires a machine — a location on no host describes nothing.
virgo persona <up|down> NAME --clear-token PATH [--as PROJECT/AGENT]
[--operation-id ID] [--recover-operation ID] [--config PATH]
virgo persona checkpoint NAME [--as PROJECT/AGENT] [--operation-id ID] [--config PATH]
up/down restore/checkpoint+stop with acceptance gates (single active copy, fence, CLEAR tokens — see AUDIT-GATED-LIFECYCLE-TOKENS.md); checkpoint lands and ingests without stopping and takes no token, so the gated flags are refused on it by name rather than ignored. Three rules the command line owns:
- The token is a FILE, never an argument. A CLEAR token on argv sits in the shell history and in every
psfor the life of the call. It is read through the same owner-only single-descriptor check as the device token. - There is no path flag. Where a persona lives is the hub's answer (see HUB-API.md, Placement); a path here would be one the token binds nothing about.
--operation-idis the retry key, and a generated one is PRINTED BEFORE the call, so a command interrupted mid-flight still leaves the caller holding the id its retry must reuse. A new id runs the operation again instead of resuming it.
--as names the seat the call ACTS as. Omitted, it is the installation's configured system seat (root/hub) read from the validated hub API config; an installation without one asks for --as rather than picking a seat. The device token authenticates the machine and never implies an identity.
persona reincarnate NAME [--as PROJECT/AGENT] [--operation-id ID] [--recover-operation ID] prevents context pollution over long residence: checkpoint (land state) → clear the live session context → regenerate the briefing from clean records (deterministic projection first, any narrative compression must cite record hashes) → resume as the same identity. Reincarnation is a scheduled hygiene step at work-unit boundaries, not an incident response; it is the minimum unit of generation lifetime policy.
It takes NO CLEAR token, and that is deliberate: a gate only a human can open is not a hygiene step. Its safety is evidence instead — it drives its own checkpoint and refuses to clear anything unless that checkpoint verifiably landed, so no unsaved work can be lost. The persona's for-life name and seat address never change; its GENERATION advances, which is what fences the session that was just cleared. --recover-operation takes over a reincarnation that cleared a session and never resumed it; that state is stuck, not self-healing, and is never resolved by a timeout or an automatic down.
persona bind NAME --record-source ADDRESS ... states, in PRECEDENCE order, the record sources a persona is briefed from; record source add ADDRESS --governing-document SHA registers a source in canon so a binding can be checked against something real. Canon decides which memories are a persona's — never machine-local config — and an unbound persona refuses to reincarnate by name rather than being briefed from whichever source looks likely. persona launch <project> activates the project's lead/deputy on demand from its class profile. persona status shows fence, host, UUID, health, last-consumed age. persona remove is not a verb; retire preserves lineage (durable, auditable).
Path grammar (unchanged from v0): root reserved scope (secretary/operator only); two segments = workspace-wide (work/coordinator); three = project duty (work/core/lead). The path is the transport address; star names are separate owner-facing metadata.
root/hub is reserved and is NOT a persona address. It is the hub's own structural system seat: canon refuses to register anybody there (a retired row included, so the address never carries a tombstone for somebody who never sat in it), and the address parser answers it as a system address rather than parsing it as a persona. It is the default acting seat for lifecycle calls made on a machine that has one configured.
Skill management
Personal persona skills may be added or edited directly inside that persona's authority. Class and workspace skills are shared behavior: an edit creates a proposed revision that must pass owner/authority approval before activation. Activation writes an owner-private, HMAC-signed root/operator approval record for the exact proposed revision hash, recording approver, receipt, and timestamp; active shared skills revalidate that signed record whenever read, and editing an active shared skill returns it to proposed and clears the prior approval.
Cross-cutting rules (carried, still binding)
- One release SHA covers CLI, daemons, and web UI assets alike.
- No owner-specific values in code — machines, workspaces, people, paths come from canon/config/stamp. Public-repo docs are English.
- Gated verbs refuse without verifiable evidence (CLEAR tokens, context receipts); refusal is loud and machine-readable.
- Every CLI mutation goes through the Hub API (see HUB-API.md) — the CLI is a client, not a second implementation of the hub's rules.