# Write a flow package {#root}

@status:doc/work @audience:author

[p01] A flow tells an agent how a team works: how to commit, what to check before pushing, how to keep notes between sessions. This page writes one from scratch, including the short text the agent reads at every session start.

[p02]
```prompt
Create a flow package org.acme/review-notes in the current VibeVM project, as an in-tree package under vibevm/vibepacks/. It should teach an agent to leave a short REVIEW.md note at the root of the project after every change it makes, with the date and what changed. Write the boot snippet, the protocol document and the manifest, then run vibe check on the package.
```

- needs: the vibevm skill installed for your agent; a project with `vibe.toml` at the root

outcome: `vibevm/vibepacks/org.acme/review-notes/v0.1.0/vibe.toml` declares a `flow` package with a boot snippet; the snippet is under two hundred words and names the protocol by address; the protocol document has anchored sections; `vibe check --path vibevm/vibepacks/org.acme/review-notes/v0.1.0` reports no errors

- assert: `vibe check --path vibevm/vibepacks/org.acme/review-notes/v0.1.0 --quiet`
- assert: `test -f vibevm/vibepacks/org.acme/review-notes/v0.1.0/vibevm/vibespecs/boot/review-notes.xml`

## What happens {#what-happens}

[p03] The agent runs `vibe init package org.acme/review-notes`, which adds a package slot to the project at `vibevm/vibepacks/org.acme/review-notes/v0.1.0/`: a [manifest](../glossary/index.xml#manifest) with a `[package]` table, a README and a placeholder snippet. The project's own [registry](../glossary/index.xml#registry) sees the slot, so the flow can be installed and tried in place before it is published. It then writes three things. The [boot snippet](../glossary/index.xml#boot-snippet) is a short instruction the agent of a consuming project reads at every session start. The protocol is the full set of rules under `vibevm/vibespecs/flows/review-notes/`, with anchored sections the snippet cites. The manifest's `[boot_snippet]` table points at the snippet with the category `flow`. Finally it runs `vibe check` on the package, which validates the manifest and the layout like it would for any project.

> [p04] **Decision.** A package has the identical on-disk shape as a consumer project:
>
> <spec://org.vibevm.core/vibevm/common/PROP-024#PKG-PROJECT-LAW>

## By hand {#by-hand}

[p05] 1. Create the package slot:

[p06]
```sh
vibe init package org.acme/review-notes
```

```output
Creating package `org.acme/review-notes` in `<TMP>/work`
  ✓ created  vibevm/vibepacks/org.acme/review-notes/v0.1.0/vibe.toml
  ✓ created  vibevm/vibepacks/org.acme/review-notes/v0.1.0/vibevm/vibespecs/boot/10-tool-review-notes.md
  ✓ created  vibevm/vibepacks/org.acme/review-notes/v0.1.0/README.md
  • kept     vibevm/vibespecs/boot/INDEX.md (regenerated)
  • kept     CLAUDE.md (regenerated)
  • kept     AGENTS.md (regenerated)
  • kept     GEMINI.md (regenerated)

Done. Project `org.acme/review-notes`: 3 files created, 4 kept.

Next:
  • edit vibevm/vibespecs/boot/00-core.md and vibevm/vibespecs/common as your project takes shape
  • install packages with `vibe install <kind>:<name>` (e.g. flow:wal)
```

[p07] 2. Edit the manifest the scaffold wrote: set `kind = "flow"`, fill in the description, and point `[boot_snippet]` at your snippet. As generated:

[p08]
```sh
cat vibevm/vibepacks/org.acme/review-notes/v0.1.0/vibe.toml
```

```output
[package]
group = "org.acme"
name = "review-notes"
kind = "tool"
version = "0.1.0"
epoch = 1
authors = ["vibevm docs fixtures"]
license = "UPL-1.0"
description = ""
format = "normal"

[boot_snippet]
source = "vibevm/vibespecs/boot/10-tool-review-notes.md"
category = "tool"
link = "dynamic"
```

[p09] 3. Inside the slot, write the snippet at `vibevm/vibespecs/boot/review-notes.xml`. Keep it short: what to do, and the address of the rule that says why. It is read by every agent, in every session, of every project that installs your flow.

[p10] 4. Write the protocol under `vibevm/vibespecs/flows/review-notes/`: one anchored section per rule, one idea per section, a status marker on each.

[p11] 5. Check and publish:

[p12]
```sh
vibe check --path vibevm/vibepacks/org.acme/review-notes/v0.1.0
```

```output
vibe check: clean — every check passed against `<TMP>/work/vibevm/vibepacks/org.acme/review-notes/v0.1.0`
```

## The snippet is the expensive part {#the-snippet}

[p13] A snippet is paid for on every session start by every consumer. Write it as an instruction, not an explanation: name the rule, the address, the command. Put the reasoning in the protocol, where an agent reads it once when it needs to. And never assume another package is installed: a snippet that says «as the WAL protocol requires» in a project without that protocol sends the agent looking for a rule that is not there. If your flow builds on another, declare the concept it relies on and let vibe warn when the concept is absent.

> [p14] **Everything is layered by mutation frequency — the whole
> loaded context is one monotone gradient (owner, 2026-08-23, near-verbatim:
> «всё в приложении устроено слоями; на самой вершине — динамическая загрузка
> быстрых изменений»).** Reading order equals stability order: the
> rarest-changing text reads first, because a change at depth N re-prices every
> byte after it — the earlier a layer sits, the more cache its mutation burns.
> The concrete gradient: *(0)* the instruction files (`CLAUDE.md` / `AGENTS.md`
> / `GEMINI.md`) — read first, so an edit there resets the ENTIRE cache; they
> carry only what must hit every session (the four rules, the standing
> directives) and change only for large causes, everything else living in specs
> loaded later; *(1)* the generated STATIC lane — structural-events-only (§3),
> and INTERNALLY sorted by the same law: contributions of rarer-changing
> packages belong earlier in the tape; *(2)* the INDEX manifest and the
> conditional dynamic lane — per-boot variability; *(3)* the live session tail —
> task text, tool results, fast state, which lives in context and is never
> compiled into any lane. This is a GLOBAL architectural idea for all of
> VibeVM, not a spec of any one mechanism (owner, 2026-08-23): whenever a new
> system is designed or an existing one changed, the design review checks it
> against this layering — where does each byte it adds sit on the gradient,
> and does anything fast-changing sneak ahead of anything slow.
>
> <spec://org.vibevm.core/vibevm/common/PROP-048#THE-LAYER-LAW>

> [p15] **A boot snippet never presupposes another
> discipline.** A snippet speaks unconditionally only about its OWN flow;
> any mention of another flow's artifacts or duties is CONDITIONAL — and the
> only lawful conditional form is structural, not verbal: the mention lives in
> a snippet fragment guarded by `when = "installed:<group>/<name>"`, so the
> text physically enters a project's lanes only when that discipline is
> actually installed. Prose hedges («if you keep a WAL», «or equivalent») are
> NOT the lawful form — they are unverifiable by machine and still teach the
> concept unasked. This is ##THE-LAYER-LAW's sibling: a presupposition must
> never travel ahead of its own discipline.
>
> <spec://org.vibevm.core/vibevm/common/PROP-049#SNIPPET-GENRE-RULE>

[p16] The consumer decides how your snippet is linked, compiled into the priority lane or listed in `INDEX.md`; you may suggest a default in `[boot_snippet]`, and the consumer's choice wins.

> [p17] A package MAY declare a suggested default inclusion type in its own `[boot_snippet]`; the consumer's declaration always wins. Absent both, the type is `dynamic`.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-workspace/PROP-009#SUGGESTED-DEFAULT>

## Subskills: content that arrives when it is needed {#subskills}

[p18] A package may split its content into [subskills](../glossary/index.xml#subskill), the smallest units an agent can activate: each looks like a tiny package with its own manifest and files. What differs per subskill is its `delivery`, the primary axis: `eager` puts the content on disk at install, `lazy-push` and `lazy-pull` hold it back until an activation rule matches. A subskill activates when any one of its channels matches, once. For the lazy modes a `description` is required, because it is the whole trigger, and `vibe check` refuses a lazy subskill without one.

> [p19] A **subskill** is the smallest activatable content unit inside a package. Structurally it looks like a tiny package: own manifest, own files, own optional further subskill children (§2.5.5). What changes per subskill is the **delivery mode** (§2.5.0 below) and the **activation rules** (§2.5.2): together they decide when the subskill's content reaches the agent and how.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-resolver/PROP-003#SUBSKILL-DEFINITION>

> [p20] A subskill's `delivery` field is the **primary axis** of the manifest, not a follow-up bolt-on. It picks how the subskill's content reaches the agent. Three values, each well-defined:
>
> <spec://org.vibevm.core/vibevm/modules/vibe-resolver/PROP-003#DELIVERY-PRIMARY-AXIS>

> [p21] A subskill becomes "active" if any one of these channels matches. Channels compose orthogonally; an active subskill activates once regardless of how many matched. The full set, more comprehensive than revision r1:
>
> <spec://org.vibevm.core/vibevm/modules/vibe-resolver/PROP-003#ACTIVATION-ANY-MATCH>

> [p22] **`description` is required for `delivery = "lazy-push"` and `lazy-pull`.** The activation trigger is the entire mechanism for those modes — without it, `vibe-mcp` has nothing to match against. `eager` mode also benefits but is not required. `vibe check` errors out (not warns) on a lazy-push subskill missing `description`.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-resolver/PROP-003#DESCRIPTION-REQUIRED>

## What goes where {#layout}

[p23] Paths are relative to the package root, the slot `vibevm/vibepacks/org.acme/review-notes/v0.1.0/`.

[p24]
| Path | Purpose |
| --- | --- |
| `vibe.toml` | the manifest: `[package]`, `[boot_snippet]`, optional `[[skill]]`, `[requires]` |
| `README.md` | what the flow is, shown on the registry and the site |
| `vibevm/vibespecs/boot/<name>.xml` | the boot snippet |
| `vibevm/vibespecs/flows/<name>/` | the protocol and its supporting documents |
| `vibevm/vibespecs/skills/<skill>/SKILL.md` | skills the flow offers to agents, declared in `[[skill]]` |
| `LICENSE.md` | the licence; permissive licences only in the public registry |

> [p25] **Prompt/spec content lives under the package's `spec/` subtree** — boot
>   snippets (`vibevm/vibespecs/boot/`), cards, guides, manifesto, appendix — laid out exactly
>   as an ordinary project's `spec/` (`VIBEVM-SPEC.md` §4.2). `[boot_snippet].source`
>   is a `spec/`-relative path (e.g. `spec/boot/20-stack-rust-ai-native.md`).
>
> <spec://org.vibevm.core/vibevm/common/PROP-024#SPEC-SUBTREE>

[p26] Everything under the package root except build output is the package: that is what is fingerprinted, copied into consumers and shown by the site.

> [p27] **Decision.** A package's **shippable tree** is its directory minus a
> build-output denylist:
>
> <spec://org.vibevm.core/vibevm/common/PROP-024#SHIPPABLE-TREE-DEF>

[p28] A package is `simple` by default: its files are carried whole and read because they are present. `format = "normal"` opts into a split between `contract/`, small and cheap to load like a header, and `source/`, the heavy body pulled only when a directive asks for it; a normal package that nobody uses does not enter the build at all.

> [p29] **`format = "simple"`** — **the default** (absent `format`, a package is `simple`). Legacy / adapted prompts, carried **whole**, with no VibeVM-specific structure — for importing existing corpora without rewriting them, and the fail-safe posture. Rules: inclusion in `[requires.packages]` means (a) structural — the agent reads the file; (b) static — its text is compiled into the target. If `[boot_snippet].source` names a file, only that file is read/spliced; **absent even that, every file in the package is read/spliced by a recursive walk** — the over-load is the author's problem, the deliberate cost of not adopting `normal`.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-workspace/PROP-035#FORMAT-SIMPLE>

> [p30] **`format = "normal"`** — the VibeVM-native form, **opt-in**: the `contract` / `source` split (§4), directives (§7), and the compiler (§8). A `normal` package is **not read just because it is present** — it participates only when something actually `#use`s it (§7.2). This is tree-shaking; the optimized posture for authors who understand the machinery, at the price of structuring the package correctly.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-workspace/PROP-035#FORMAT-NORMAL>

> [p31] **`contract/`** — small, simple, boot-snippet-like. The surface a package exposes outward; short files, cheap to load. The analogue of a header.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-workspace/PROP-035#DIR-CONTRACT>

> [p32] **`source/`** — large, heavy. The full implementation; pulled only when actually needed. The analogue of a translation unit.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-workspace/PROP-035#DIR-SOURCE>

> [p33] **Tree-shaking default.** A `format = "normal"` package that nobody `#use`s does not participate — not read, not used, anywhere. The moment any text does `#use spec://…`, that package **enters the build** and MUST be linked **before** its user in topological order.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-workspace/PROP-035#USE-TREE-SHAKING>

## Edge cases and rules {#edge-cases}

[p34] Cross-references inside the package are addresses, `spec://org.acme/review-notes/flows/review-notes/PROTOCOL#anchor`, never relative file paths; they survive being copied into any consumer.

> [p35] **L1 — physics moves, addresses do not.** `spec://`
> addresses, anchors, fact ids, specmap edges, `scope!` citations and
> recorded verdicts are LOGICAL and do not change: only the physical
> prefix maps differently (`vibevm/vibespecs/common/PROP-000.xml`
> still canonicalises to the same `common/PROP-000` document path).
> Any relayout step that would rename an address is a defect of the
> step.
>
> <spec://org.vibevm.core/vibevm/common/PROP-052#ADDRESSES-SURVIVE-THE-MOVE>

> [p36] The layout: every project and every package carries ONE
> distinctive root directory `vibevm/`, holding `vibevm/vibespecs`
> (was `spec/`), `vibevm/vibepacks` (was `packages/`),
> `vibevm/vibedeps` (was root `vibedeps/`) and `vibevm/vibefacts`
> (was root `vibefacts/`). Nothing else moves; `vibe.toml` stays at
> the project root.
>
> <spec://org.vibevm.core/vibevm/common/PROP-052#THE-LAYOUT>

[p37] While you develop a package inside the same repository, edit its source and run `vibe install`: the change reaches the dependency tree without a flag, an update or a forced reinstall.

> [p38] **No new flag (consistent with §2.5).** This is automatic and source-aware — the author edits the in-repo source and runs `vibe install`; nothing to remember, and neither `vibe update` nor `reinstall --force` is needed for the local-authoring loop, while those bypasses remain for the immutable case.
>
> <spec://org.vibevm.core/vibevm/modules/vibe-workspace/PROP-011#AUTOMATIC-NO-FLAG>

[p39] Versions never move: to change a published snippet, bump the version and publish again. A consumer sees the change at its next update, not before.

[p40] A flow may require other packages; a consumer installing yours gets them too, and their snippets enter the consumer's lane after yours.

