Skip to main content
The protocol says what a brain is. Vitruvio is what you run against one. This page is the shape of that: the handful of verbs, the one output contract, and the two rules that every command obeys.

The lifecycle, in commands

Everything vitruvio derives — indices, statistics, the embedding cache — lives under .vitruvio/ beside the layout. Deleting it costs time, never knowledge: the blocks are the brain, and everything else is rebuilt from them. Every command needs to know which brain it means. Inside a project that is a name, --brain optics; alone it is the nearest vitruvio.toml. The full resolution order is on Selecting a brain.

One output contract

Every command that returns data takes --json and emits exactly one envelope on stdout. Notes, progress and warnings go to stderr, so vitruvio search q --json | jq works unconditionally.
Branch on ok, then on error.code. Read warnings even when ok is true: “the vector index will not be published”, “stats are stale”, “the recall floor was lowered” all arrive there, and a degraded result that looks identical to a clean one is the failure this design exists to prevent. On a failure, error.retryable says whether the same request could succeed if repeated unchanged; command names the operation that failed, whether or not it got far enough to run. browse is the one exception. It is an interface for a person, has no envelope, and refuses --json rather than printing something a caller cannot read.

Exit codes

The point of these is “may I retry, and with what changed”. The distinction that matters most: 2 means you asked wrong, 5 and 6 mean the protocol refused and retrying is pointless, 7 means your input was wrong and is fixable.

Every write names an actor

This is a refusal, not an inconvenience. An unattributed write is a provenance record that lies, and the whole value of the provenance module is that it does not. Reads never need one: inspecting someone else’s brain is legitimate and attributes nothing. The declaration in vitruvio.toml is authoritative. Once [actor] id is set, every write into the project is attributed to it: a --actor or VITRUVIO_ACTOR_ID naming the same id is a harmless repetition, and one naming a different id is refused with ACTOR_OVERRIDE_REFUSED (exit 3) rather than obeyed, because provenance must not depend on which shell ran the command. Only the commands that create the declaration — brain init, project init, brain migrate — may name any actor. A brain kept by a different person inside a shared project declares its own actor, and that declaration is authoritative for writes into that brain in the same way:
project add optics --actor colleague@example.com writes it when the id differs from the project’s; a brain that declares none is written to by the project’s actor. New writes accept exactly two stable actor-id forms: a lowercase address (you@example.com) or a lowercase namespaced name (openai/codex). Vitruvio refuses uppercase, whitespace and free-form display names rather than normalizing them, because the bytes enter provenance block identities. name remains the display label. When another agent helped the primary actor, record it instead of replacing the author. Each brain declares who may assist writes into it, and a brain that declares nobody inherits the project’s list:
With nothing else said, every declared party is recorded on every write. The global --assisted-by flags, or a JSON array in VITRUVIO_ASSISTED_BY, select among the declared parties — keeping their declared kind, name and model — and a party the brain has not declared is refused with COLLABORATOR_NOT_DECLARED (exit 3), because a collaborator that appears in provenance without appearing in the committed file is the attribution nobody reviewed. --empty-assisted-by records no assistance for one invocation. brain init --assisted-by and project add --assisted-by write the declaration; config show and inspect doctor (config.collaborators) show it. Assistance is written into provenance v2 on every new operation. Ingest is stricter, because it is where a model writes. ingest run and task commit take the actor from the declaration and from nowhere else — --actor or VITRUVIO_ACTOR_ID naming anyone the file does not declare for that brain is refused with ACTOR_NOT_DECLARED — and they refuse to run until the invocation says who assisted: --assisted-by ID for each assisting agent, chosen among the declared parties, or --empty-assisted-by when nobody did (COLLABORATOR_REQUIRED otherwise). “Every declared party” is a default, and a default is not a statement.

Integrity is not authenticity

brain verify proves that the blocks match their hashes and module roots. auth status additionally asks whether valid, authorized SSH signatures support the claimed head. A brain can be intact and still be unsigned, unpinned, or signed by a key outside its trust root — which is why the two are always reported separately, and why governed or ungoverned is the first decision when creating one.