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.
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
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:
--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.