Skip to content

Fibers and the store

One fiber, one concern

Give each concern its own fiber. A task, a decision, a question, a finding, a spec, a reference doc — anything worth naming.

On disk, a fiber owns a directory holding a markdown file with YAML frontmatter:

.felt/covariance-estimation/covariance-estimation.md

The frontmatter carries metadata. The body holds plain markdown. That covers the whole format.

---
id: 01KTCA2CGCYT0VW8320JRE79VS
name: Covariance estimation
status: active
tags:
    - decision
created-at: 2026-01-31T02:40:05.884858+01:00
outcome: Jackknife over 200 patches beats the analytic model below ℓ=300.
---

The pipeline needs a covariance we trust at large scales. …

felt reads the markdown tree directly. It computes everything else — back-references, reverse consumers, body search — on demand by walking the tree.

Name, body, outcome

Three fields carry the content, and they have distinct jobs.

  • name labels the fiber concisely. Keep the content out of it.
  • outcome states the conclusion in one line. felt show prints it, and a kanban card shows it. (felt ls lists the status icon, the id, the name, and the tags.)
  • body carries the substance: what is true now, why it matters, what connects.

The body describes the present state, not the journey. Chronology lives in the git log of the file — fibers are ordinary text files, so version control does that job already.

Addressing a fiber

Address a fiber by its slug path. Nested fibers use /:

felt show covariance-estimation
felt show bao-analysis/damping-prior

A bare slug resolves as long as it is unique across the store. Ambiguity raises an error rather than a guess.

Store layout

A store lives in a .felt/ directory at a project root. It follows the shape .felt/<path>/<slug>/<slug>.md.

Each fiber owns a directory for a reason. Companion files — plots, PDFs, a report.html — live beside the markdown. See Companion files.

.felt/
├── myst.yml
├── .gitignore
├── project.md                      ← entry-point fiber (bare, at root)
└── bao-analysis/
    ├── bao-analysis.md
    ├── damping-prior/
    │   └── damping-prior.md
    └── mock-validation/
        ├── mock-validation.md
        └── report.html             ← companion file

The entry-point fiber

The directory rule has one exception. A single bare .felt/<slug>.md at the store root serves as the entry-point fiber — the project's front door. felt preserves it as-is and never migrates it.

Two or more bare .md files at the root create ambiguity. felt cannot tell the entry point from stray legacy files, so felt check flags it and felt migrate converts them all to directory form.

Creating a store

cd my-project
felt init

felt init creates or repairs .felt/ and writes two support files:

  • myst.yml — a MyST project config, so the store renders as a site.
  • .gitignore — ignores *.md.lock, felt's per-fiber write locks.

Re-running it is safe. It leaves existing files alone.

Then add your first fiber:

felt add covariance-estimation "Covariance estimation" \
  -t decision \
  -o "Jackknife over 200 patches beats the analytic model below l=300."

Commit your fibers. Git versions the text like any source file, so the log tracks how the thinking moved.

Checking the store

felt check lints the store. It reports:

  • broken narrative wikilinks and broken body links
  • broken inputs.from data-flow references
  • legacy title frontmatter keys
  • legacy depends-on frontmatter keys
  • legacy MyST body anchors
  • slug collisions between bare and nested fiber forms
  • multiple bare .md files at the .felt/ root
  • fibers with a blank name
  • orphaned pins — a fiber claiming a pinned role with no shuttle: block (warning)
felt check
felt check --json

Cross-store links look broken

A [[wikilink]] pointing into a different store reads as broken to felt check. felt scopes to one store at a time. Expect that warning; it marks no defect. See Cross-project stores.

Migrating a legacy store

felt migrate normalizes an older store into the current model:

  • flat .felt/<slug>.md files become <slug>/<slug>.md
  • title frontmatter becomes name
  • inert depends-on keys are dropped
  • leading MyST anchors like (slug)= are stripped from bodies
  • myst.yml is ensured

Look before you leap:

felt migrate --dry-run
felt migrate

--dir <path> points the migration at a store other than the current project's.

A separate one-off pass, felt backfill-ids, mints intrinsic ULIDs for fibers that lack them. Run it on the canonical copy of a store only, then sync the files, so replicas inherit the committed ids instead of minting their own. See Frontmatter for what the id is for.