# How vibe is built {#root}

@status:doc/work @audience:dev

[p01] vibe is one binary built from a set of Rust libraries, each owning one concern: reading the description of a project, choosing versions, fetching from where packages are published, writing the tree on disk, talking to agents. This page maps the libraries, the seams between them, and the path an install takes through them.

## Five layers {#five-layers}

[p02] Read the product bottom-up. *Identity*: a package is a [coordinate](../glossary/index.xml#coordinate) plus a content [fingerprint](../glossary/index.xml#fingerprint), and the kind is metadata. *[Registry](../glossary/index.xml#registry)*: ordered package sources with mirrors, [overrides](../glossary/index.xml#override) and an optional [index](../glossary/index.xml#index-registry). *[Store](../glossary/index.xml#store)*: every fetched package version kept once per machine.

[p03] *Materialisation*: the resolved graph copied into the project's dependency tree and recorded in the [lock file](../glossary/index.xml#lock-file). *Computed boot*: the packages' [contributions](../glossary/index.xml#contribution) projected into the two generated files an agent reads. Everything a user sees is one of these five layers or a surface over them.

> [p04] 21. Surface floor — which channels a capability owes
>
> <spec://org.vibevm.core/vibevm/common/PROP-000#surfaces>

## The crates {#the-crates}

[p05]
| Concern | Crates | What they own |
| --- | --- | --- |
| core vocabulary | `vibe-core`, `vibe-wire` | manifests, the lock file, identities and content hashes; the generated types of every registered machine format |
| specifications | `vibe-spec`, `vibe-specdoc`, `progress-core`, `vibe-facts`, `vibe-trace` | addresses and the deterministic router; the document model with its Markdown and XML frontends and backends; the status markup parser and reports; the adoption-facts registry; traceability queries |
| registries and the store | `vibe-registry`, `vibe-index`, `vibe-publish`, `vibe-package-source` | git transport, mirrors and overrides, the clone cache and the machine store; the searchable index and its server; publishing; the one production composition of package sources |
| resolution and installation | `vibe-resolver`, `vibe-install`, `vibe-workspace`, `vibe-safefs` | the solver seams and cells; plan and apply; workspace discovery, materialisation, the computed boot; capability-relative filesystem mutation |
| the lifecycle | `vibe-lifecycle`, `vibe-extension-registry`, `vibe-orchestrator`, `vibe-ext`, `vibe-native-loader`, `vibe-llm`, `vibe-scrape` | the nine-phase model and chaining; the pure extension registry; surface-neutral orchestration; the safe author SDK and the quarantined loader of native extensions; the model provider seam; scrape planning |
| agents and preferences | `vibe-mcp`, `vibe-agent-projection`, `vibe-settings`, `vibe-actions`, `vibe-requirements` | the MCP server and integration manager; skill projection into agents; three-level preferences; frontend-agnostic actions; the read-only requirements query |
| surfaces and checks | `vibe-cli`, `vibe-check` | the command line; the deterministic project linter |
| documentation | `vibe-doc`, `vibe-doc-server`, `vibe-doc-shell` | the page pipeline behind `vibe doc`: build, check, manifest, surface snapshots, the maintenance queue and the site builder; the local reader's server on the loopback address; the reader's shell carried inside the binary |
| reserved and tooling | `vibe-graph`, `vibe-test-support`, `xtask` | a reserved task-graph slot; test isolation of the settings home; the maintainer gates: code generation, the traceability map, engine synchronisation, mirroring, the release build |

[p06] The dependency direction is fixed: a surface calls an orchestrator, an orchestrator calls a library through a seam, a library returns typed values. Domain libraries never prompt, never format terminal output and never decide authentication; those choices are made at the composition root, in the CLI or the [MCP server](../glossary/index.xml#mcp-server).

> [p07] 17. Production architecture in the prototype phase
>
> <spec://org.vibevm.core/vibevm/common/PROP-000#prod-arch>

[p08] Four decisions hold the tree together. The repository is one Cargo workspace with every crate under `crates/`. Each ability of the product lives in a library, and the command line, the terminal interface and the MCP server are thin surfaces over it. JSON Type Definition schemas are the single source of truth for every machine contract. And the commit discipline is the installed git-practices family, read at the start of every session.

> [p09] **Decision:** Single Cargo workspace at repo root. Crates live under `crates/` per `VIBEVM-SPEC.md` §10.2:
>
> <spec://org.vibevm.core/vibevm/common/PROP-000#WORKSPACE-LAYOUT>

> [p10] **Decision:** a capability lives in a **library**; the CLI, the TUI and the MCP server are thin surfaces over it. The rule and its vocabulary are the installed `omnichannel` flow: `spec://org.vibevm.world/omnichannel/flows/omnichannel/OMNICHANNEL-PROTOCOL#root`. This section declares only vibevm's own floor, which is what that flow asks each project to state for itself.
>
> <spec://org.vibevm.core/vibevm/common/PROP-000#SURFACE-DISCIPLINE-IS-THE-OMNICHANNEL-FLOW>

> [p11] **Decision:** JSON Type Definition (RFC 8927) schemas are the single source of truth for every client/server and machine-to-machine contract in this project.
>
> <spec://org.vibevm.core/vibevm/common/PROP-000#JTD-SSOT>

> [p12] The repository's commit-and-push discipline is the **git-practices** family (a host dependency), whose members carry the full text:
>
> <spec://org.vibevm.core/vibevm/common/PROP-000#GIT-PRACTICES-FAMILY>

## The path of an install {#the-install-path}

[p13] 1. Discover the workspace root and read the [manifests](../glossary/index.xml#manifest), the lock file, the user configuration, the registries, mirrors, overrides and local package sources.

[p14] 2. Compare the manifests with the lock file; if nothing changed, skip resolution.

[p15] 3. Otherwise qualify every requested coordinate and build the solver's view of available versions. Solve the whole graph, holding every pin the change does not touch.

[p16] 4. Fetch every selected identity: a store hit is reused, a miss walks the allowed sources and inserts the verified tree into the store.

[p17] 5. Build the plan, validate the [managed blocks](../glossary/index.xml#managed-block) of the instruction files, and ask for confirmation.

[p18] 6. Materialise the graph into the dependency tree by diff, regenerate the boot files, prune stale slots.

[p19] 7. Record the graph, the provenance and the fingerprints in the lock file.

[p20] 8. Render the human, quiet or JSON report.

> [p21] **Decision.** `vibe install` is understood as two phases, optimised independently — the current code conflates them.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-workspace/PROP-011#TWO-PHASES-SPLIT>

## The seams {#the-seams}

[p22] `GitBackend` isolates git: the production implementation shells out to the system git, so SSH agents and credential helpers behave as they do everywhere else. `Registry` enumerates, resolves and fetches across local and git-backed sources; `MultiRegistryResolver` owns the ordered walk, mirrors, overrides, authentication and the offline posture. `DepProvider` is the solver's view of the world and `DepSolver` turns roots into a graph; the default cell is resolvo, with a backtracking SAT cell and a naive cell selectable. `InstallSource` separates the transaction from the construction of cells. `RepoCreator` isolates the hosts' repository creation for publishing. Each seam has more than one implementation, and tests run the seam, not the production cell.

> [p23] **Decision.** Add a second `DepSolver` impl, `SatDepSolver`, alongside `NaiveDepSolver`. Both implement the same `crates/vibe-resolver/src/lib.rs::DepSolver` trait (`fn solve(&self, roots: &[PackageRef]) -> Result<ResolvedGraph, SolveError>`). `NaiveDepSolver` stays in tree as the "small graphs / no features / no disjunctions" fast path. **The default clause is superseded** ([PROP-017](PROP-017-resolvo-resolver.xml)): both impls shipped (`naive.rs`, `sat.rs`), but the production default became **resolvo**, not `sat`.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-resolver/PROP-003#SOLVER-TWO-IMPLS>

## Wire formats and authored formats {#wire-and-authored}

[p24] Two kinds of text cross the product's boundary. Machine formats, the JSON reports, the lock file's records, the release manifests, are described by JSON Typedef schemas and their types are generated; a handwritten parser of our own format is a defect the build counts. Authored formats, the manifest and the specifications, are parsed by handwritten code on purpose, because a person writes them and the errors must speak the person's language.

> [p25] 16. JTD + codegen for wire contracts
>
> <spec://org.vibevm.core/vibevm/common/PROP-000#jtd>

> [p26] **4.1 The format registry.** `formats/REGISTRY.toml`
> inventories every surface a foreign parser reads: id, epoch, schema path,
> recoverable-or-not, independent-parser count, sunset date, golden-corpus path.
> From it the `FormatId` enum is generated, and all wire I/O goes through
> `wire::publish(FormatId, …)` / `wire::load(FormatId, …)` — an unregistered
> format is *inexpressible in the type system*, not merely discouraged. An
> unnumbered format is a format that will be broken without anyone noticing.
>
> <spec://org.vibevm.core/vibevm/common/PROP-044#M-FORMAT-REGISTRY>

[p27] The schema language has no 64-bit integer, so any integer wider than 32 bits rides the wire as a decimal string.

> [p28] **4.2b Integers wider than 32 bits ride the
> wire as decimal strings** *(owner ruling 2026-08-20, the B-091 fork answered
> once and generally)*. JTD (RFC 8927) has no 64-bit integer type at all — the
> pinned generator rejects `uint64` and `int64` as InvalidType (measured
> 2026-08-15) — so every field wider than 32 bits would otherwise re-litigate
> the same bad trilemma: a `uint32` that is false at and above 2³², a `float64`
> that loses precision past 2⁵³, or an untyped `{}` that loses the field
> entirely. The general answer: such a field is encoded as a **canonical decimal
> string** — ASCII digits only, no sign, no leading zeros except `"0"` itself —
> the schema says `string`, the Rust type stays the true integer, conversion
> lives at the serde boundary, and non-canonical input is refused loudly rather
> than coerced. Timestamps are not this rule's business: they ride as RFC 3339
> through the `timestamp` vocabulary. First application: the catalog manifest's
> file `size` (`formats/breaks/003.md`).
>
> <spec://org.vibevm.core/vibevm/common/PROP-044#M-WIDE-INTEGERS-AS-STRINGS>

## Where to read next {#reading-order}

[p29] The specifications are the authority: `PROP-000` for the foundational decisions, `PROP-009` for the loading model, `PROP-002` and `PROP-010` for registries and the store, `PROP-054` for the [lifecycle](../glossary/index.xml#lifecycle) and the extension machine, `PROP-045` for the document model, `PROP-057` and `PROP-058` for documentation packages, the site and how this manual is maintained. The traceability page of this manual explains how code cites them and how to ask the map which code implements which rule. The developer guide in the repository covers building, testing and the self-check panel.

