Reel workspace layout¶
Exactly what a reel workspace contains, which files belong in git, and which are regenerable. Why it is shaped this way is on Reel workspaces.
Where a workspace lives¶
<reel root>/<slug>/. The reel root is reels/ by default. The workspace.root
config key is meant to move it but
does not work reliably
— keep workspaces in reels/ until that is fixed.
The slug is validated as lowercase kebab-case with no path separators:
^[a-z0-9]+(-[a-z0-9]+)*$. Anything else is rejected with
invalid slug (want lowercase kebab-case).
Files a workspace contains¶
| Path | Written by | In git |
|---|---|---|
workspace.yaml |
reel new, and any command that changes meta |
yes |
storyboard.json |
storyboard draft, the studio, hand-editing |
yes |
source.md |
the studio, or by hand | yes |
direction.md |
the studio, or by hand | yes |
social.json |
social set, social gen, approve, post |
yes |
vo/NN.mp3 |
voice pick |
yes |
cards/NN.png or cards/NN.mp4 |
cards pick, cards set |
yes |
music.mp3 |
music pick |
yes |
cover.png |
the studio's cover pick | yes |
portrait.png |
the studio's portrait pick | yes |
reel-<slug>.mp4 |
reel build |
yes |
media.lock |
media push |
yes |
media.log |
media push |
yes |
vo/takes/NN-T.mp3 |
voice gen --takes |
no — ignored |
cards/takes/NN-T.png |
cards gen --takes |
no — ignored |
cards/takes/screen.json |
cards screen |
no — ignored |
music/takes/T.mp3 |
music gen --takes |
no — ignored |
.cache/ |
every generative command | no — ignored |
cover/takes/T.png |
the studio's cover generation | yes, and probably should not be |
portrait/takes/T.png |
the studio's portrait generation | yes, and probably should not be |
The keryx cover and keryx portrait CLI commands are different: they write numbered
files to an --out prefix in the working directory (default cover / portrait) and
do not touch a workspace at all. Only the studio's generation path fills
cover/takes/ and portrait/takes/.
NN is the 1-based card or line number, zero-padded to two digits. T is the take
number, not padded.
What source.md and direction.md are for¶
Both are optional and both feed AI generation; neither affects rendering.
source.mdholds the subject matter when no article is linked. A workspace linked to a content directory takes the article itself as its subject, sosource.mdis the substitute for a reel that stands alone. It seedsstoryboard draftand the studio chat.direction.mdholds the creative intent — what the reel should convey, the feeling to invoke. It grounds generation alongside the subject rather than instead of it, and applies whether or not an article is linked.
What reel new writes¶
keryx reel new <slug> creates the directory and, inside it:
workspace.yamlcarrying the slug and a schema version;- an empty but schema-valid
storyboard.json([]); - the
vo/,vo/takes/,cards/,cards/takes/,music/takes/and.cache/directories, so the first generation has somewhere to write; - a
.gitignore.
It refuses to overwrite an existing workspace unless --force is passed.
What the generated .gitignore contains¶
Verbatim, this is the whole file:
# keryx — disposable, regenerable (cleared by `keryx reel prune`)
vo/takes/
cards/takes/
music/takes/
.cache/
cover/takes/ and portrait/takes/ are not in it. Cover and portrait candidate
takes generated through the studio are therefore tracked by git and will be committed
with everything else. Add those two lines by hand if you generate either.
What reel prune removes¶
keryx reel prune deletes vo/takes/, cards/takes/, music/takes/ and .cache/.
It never touches a selected asset, the rendered mp4, workspace.yaml or the recorded
lock state.
It does not remove cover/takes/ or portrait/takes/ — the same two directories
the generated .gitignore misses. Clear those by hand.
workspace.yaml fields¶
| Field | Type | Meaning |
|---|---|---|
slug |
string | The workspace id; matches the directory name. |
schema_version |
int | Currently 1. An older workspace fails loudly rather than mis-rendering. |
label |
string | Human name shown in the studio. Optional; falls back to the slug. |
theme |
string | Theme keyword for this reel. Empty uses the configured default. |
avatars |
list of strings | The reel's cast — avatar names from the project registry. |
aspect |
"W:H" |
Overrides reel.aspect for this reel. |
bundle |
string | Associated content directory, set by reel link. |
unreviewed |
bool | The storyboard is an unreviewed AI draft. |
locked |
map | Reviewed/approved assets — see below. |
The locked block: which asset kinds are indexed¶
reel lock and reel unlock record approval under locked in workspace.yaml.
| Kind | Keyed by | Stored as |
|---|---|---|
vo |
1-based line number | vo: [1, 3, 5] |
card |
1-based card number | cards: [2] |
music |
nothing — singleton | music: true |
cover |
nothing — singleton | cover: true |
An index is required for vo and card and must be ≥ 1; omitting it gives
vo requires an index >= 1. An index passed to music or cover is ignored.
Any other kind is rejected before any state is touched:
Unlocking something that is not locked is a no-op, not an error.
Which files reel duplicate copies¶
keryx reel duplicate <src> <dst> copies the authoring inputs and omits:
social.json— a copy has not been posted, so posting status resets;vo/takes/,cards/takes/,music/takes/and.cache/— regenerable.
The new workspace's workspace.yaml gets the new slug; everything else, including the
locked block, carries over.
Status in reel list¶
keryx reel list prints one line per workspace with a coarse status in brackets, plus
theme= and bundle= when set:
The status is decided by one test: a workspace with a social.json file is
social, and a workspace without one is draft. It does not read the per-platform
status inside social.json, so a reel whose platforms are all still drafts, and a reel
already posted everywhere, both show social. A posted status is intended but not
implemented.
With no workspaces at all, reel list prints
no reels (create one with keryx reel new <slug>).