Skip to content

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.md holds the subject matter when no article is linked. A workspace linked to a content directory takes the article itself as its subject, so source.md is the substitute for a reel that stands alone. It seeds storyboard draft and the studio chat.
  • direction.md holds 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.yaml carrying 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:

unknown lock kind "bogus" (want vo|card|music|cover)

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:

my-post  [social]  theme=editorial  bundle=content/posts/my-post
other    [draft]

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>).