workspace/projects/<id>/asset-manifest.json is the single pointer table for everything a project has generated. One key per asset slot, one record per pointer. The runtime shape is defined in cli/commands/generate.ts (Manifest type, readManifest / writeManifest). This page documents the schema, the slot-id convention, and how auto-versioning interacts with the pointer.
Full example
ralphy generate <kind> lands here as a slot. The manifest is the “current pointer”; the file on disk is the latest version.
Schema
The runtime shape fromcli/commands/generate.ts:
Fields
| Field | Type | Nullability | Notes |
|---|---|---|---|
slots | object | required | Map from slot id to slot record. Empty object on a fresh project. |
slots[id].kind | enum | required | image, video, voiceover, music, captions. |
slots[id].path | string | required | Project-relative path to the current version of the asset (e.g. assets/images/scene-01-bg-image.png). |
slots[id].model | string | optional | Full provider model id (e.g. google/gemini-3-pro-image-preview). |
slots[id].costUsd | number | optional | Cost in USD for the latest generation, not lifetime. Lifetime cost comes from generations.jsonl. |
slots[id].url | string | optional | Provider URL when the asset was downloaded from a remote source. Useful for re-fetching. |
slots[id].generatedAt | ISO 8601 string | required | Timestamp of the latest generation. |
projectId, version, or totalAssets field in the runtime shape. (Some older example manifests under profiles/ have a richer shape — those are legacy and not used by the current ralphy generate flow.)
Slot ID convention
Slot ids are lowercase kebab-case. The canonical pattern is:scene-01-bg-image— background image for scene 01scene-01-vid— video for scene 01scene-01-vo— voiceover for scene 01scene-04-bg-image— background image for scene 04bg-music— project-wide background music (no scene prefix)
cli/commands/generate.ts → normalizeSlot() accepts [a-zA-Z0-9_-]+ and normalizes uppercase to lowercase and _ to -, with a stderr warning. The canonical form is [a-z0-9-]+. Characters outside the relaxed set (spaces, dots, slashes, unicode) raise E_INPUT_INVALID.
Auto-versioning
When you regenerate a slot that already has a file,cli/lib/providers/media.ts → protectExistingAsset() archives the existing file before the new one lands:
<base>.v{N}<ext> where N is the next free version number. The archiver scans siblings, finds the highest existing N, and uses N+1.
Bypass with --force-overwrite only when the user explicitly asks for legacy destructive behavior.
Manifest interaction
The manifest’spath field always points at the latest version — the unversioned file. The archive files (.v1.png, .v2.png, …) are not listed in the manifest. To enumerate them, walk the assets directory.
Every generation also appends a row to generations.jsonl (see Memory schemas). The full history of a slot lives in those rows; the manifest is the current pointer only.
Promote workflow
If the user prefers v1 over the latest, the promote flow is manual and user-driven — Ralphy does not flip the pointer without explicit consent.path does not change (the unversioned filename is the same). The new file is the new latest. A future ralphy project doctor verb may automate this, but until then the swap is by hand.
Manifest vs generations.jsonl
| File | Purpose |
|---|---|
asset-manifest.json | Current pointer per slot. “What is the latest version of scene-01-bg-image?” |
logs/generations.jsonl | History. “How many times has scene-01-bg-image been generated? At what cost? With what prompt?” |
ralphy generate <kind> writes the file, appends to the log, and updates the manifest in the same call.
Writer behaviour
readManifest returns {slots: {}} when the file is absent or unparseable — empty-slot recovery is silent so a corrupted manifest does not block a fresh generation.
writeManifest does a full rewrite of the file with JSON.stringify(m, null, 2). The file is small (a few hundred bytes to a few KB per project), so the rewrite cost is negligible.
This is the one place in the project memory where rewrites happen. The JSONL logs are append-only; the manifest is rewrite-allowed because it tracks current pointers, not history.
Sister files: render output
render/final.mp4 and friends are written by ralphy render <id> and do not appear in the manifest. The renderer reads the manifest to find slot files, composes the video, and writes the output to render/ directly. Render outputs are tracked separately by ralphy project show <id>.
Doctor verb
Related
- Memory schemas — the sibling JSONL logs.
- Concepts: generation log — narrated overview.
- Working: reviewing and iterating — how to read versions and pick the keeper.
cli/commands/generate.ts—Manifesttype and writer.AGENTS.md— invariant #13 (regen → new version, never overwrite).