Companion files¶
A fiber owns a directory, not just a file. The <slug>/<slug>.md layout exists
for that reason: whatever the work produced can sit beside the markdown that
describes it.
.felt/bao-analysis/mock-validation/
├── mock-validation.md
├── report.html
├── residuals.png
├── chains.pdf
└── interview.m4a
felt does not manage these files. It does not copy, index, or validate them.
They stay ordinary files in a directory that happens to be a fiber, and they
travel with the fiber through nest, unnest, git, and sync.
Embedding an artifact¶
Inline any companion in the body where it helps the reader, using an
:::{embed} directive:
:::{embed} residuals.png
:::
:::{embed} build/paper.pdf
:height: 600
:title: Latest build
:::
Paths resolve relative to the fiber's directory. Absolute paths also work.
The renderer dispatches by file extension:
| Extension | Rendered as |
|---|---|
.png .jpg .jpeg .gif .webp .svg .avif |
image |
.wav .mp3 .m4a .ogg .flac .aac |
audio player |
everything else, .pdf and .html included |
fixed-height iframe |
Who does the rendering
The shuttle board's fiber viewer understands the :::{embed} directive —
see the shuttle layer. The felt CLI itself treats
the directive as ordinary body text. A plain markdown viewer or an Obsidian
vault shows it as a literal block. Use it where the board (or your own
renderer) reads it.
The report.html convention¶
felt names exactly one companion: report.html.
Put what a human reads — findings, figures, analysis prose — in a sibling
report.html when it outgrows the outcome line. felt detects the file during
its directory walk and surfaces the absolute path as report_path in JSON
output:
felt show mock-validation -j | jq -r .report_path
felt implies nothing further. It does not open the file, render it, or require it. Most fibers need no report; work whose story is commits plus an outcome line does fine without one.
To make the report the first thing a reader meets, embed it explicitly at the top of the body:
:::{embed} report.html
:::
The jackknife covariance is now the default. …
HTML beats markdown here: sections, tables, inlined plots, and collapsible depth in one self-contained file. Keep it self-contained — base64 the images — so the report renders wherever the fiber is opened, including on a different machine.
shuttle workers follow a shape for these reports — current state, standing findings, open questions, pointers to depth — rewritten whole each session, never appended. See Optional: report.html.
Sent files (shuttle only)¶
A shuttle worker can also push a file at you directly, with
shuttle send-file <path> [path...].
shuttle hook event records that push on the host's event stream
(~/.shuttle/events.jsonl), and the board surfaces it two ways: a per-card
sent-files trail in the fiber viewer, and the Board
canvas, where every send from
the last month renders as a card.
The two channels overlap — the file a worker sends is very often its own
report.html, a companion. What differs is durability.
| Companion | Send | |
|---|---|---|
| What it is | a file in the fiber directory | an event record pointing at a path |
| Travels with | nest, unnest, git, sync |
nothing — it stays on the machine that ran the worker |
| Lifetime | as long as the fiber | as long as the live events.jsonl; a trail that rolled over is gone |
| Scope | any machine that has the fiber | that host, capped at 50 entries per card |
Sending a file does not put it in the fiber, and putting a file in the fiber does not surface it on the board.
Needs ~/.shuttle
shuttle hook event refuses to create its own directory, so an install
without daemon state grows no event stream and every trail stays empty. See The event
stream and the
ledgers.