workspace/projects/<id>/logs/. Three JSONL files: every model call, every user prompt, every uploaded reference. The writers live in cli/lib/gen-log.ts. This page is the schema reference: field-by-field shape, nullability, examples, and the append-only contract.
File layout
The append-only contract
AGENTS invariant #13 makes this a hard rule: the logs are append-only. Never truncate, rewrite, or filter them in place. Read-and-rewrite to “tidy” is a defect. Concretely:logGeneration(),logUserPrompt(),logUserAsset()open the file withfs.appendFile(). Neverfs.writeFile().- Failed and rejected generations stay on disk.
status: "error"rows are how cross-session reasoning works. - If the user explicitly asks to clear a project’s logs, use
ralphy project delete <id>(registry-aware) — never patch the files.
generations.jsonl
One line per model call. Written bycli/lib/gen-log.ts → logGeneration(). Read by ralphy project log <id>, ralphy project timeline <id>, and the cost-rollup verbs.
Example line
Fields
| Field | Type | Nullability | Notes |
|---|---|---|---|
timestamp | ISO 8601 string | required | Set at write via new Date().toISOString() unless a backfill timestamp is passed. |
provider | enum | required | fal, elevenlabs, openai, openrouter, vercel, ffmpeg, replicate, other. |
endpoint | string | required | The fully-qualified endpoint string (e.g. kwaivgi/kling-v3.0-pro for OR, music_v1 for ElevenLabs). |
kind | enum | required | image, video, audio, music, voiceover, sfx, text, embed, other. |
slot | string | optional | The asset slot this call targets (e.g. scene-01-bg-image). Persisted so per-slot rollups don’t need to infer from the note. |
input | object | required | The request body passed to the provider. Keep small — prefer URLs over byte blobs. |
output | object | optional | {url?, local?, bytes?}. Set on success. |
status | "ok" or "error" | required | Drives cost rollups (status: "ok" only). |
error | string | optional | Set only when status: "error". Free-form message. |
latency_ms | number | optional | Wall-clock duration of the call. Used by the daemon’s throughput metrics. |
cost_usd | number | optional | Best-effort estimate. May be empty for providers without a cost contract. |
request_id | string | optional | Provider-side id (fal queue id, ElevenLabs generation id, OR request id). Used for support tickets. |
note | string | optional | Free-form human tag. Conventions: "clip-03 v2 hand crumples sample", "--variants 4 A/B sweep". |
Writer signature
timestamp is auto-filled from Date.now() if you don’t pass one. Pass an explicit timestamp only when backfilling.
Lifecycle
- Written by
ralphy generate {image,video,voiceover,music,sfx,captions}automatically. Skill code never writes directly. - Written by
cli/lib/providers/media.ts → loggedFetch()for any provider call routed through that helper. - Read by
ralphy project log <id> --kind <generations|user-prompts|user-assets>and surfaces under theworking/logs-and-costsdoc page.
user-prompts.jsonl
Chronological user prompts, plus structured-override entries (e.g.--no-ref-consent reasons). Written by logUserPrompt().
Example lines
Fields
| Field | Type | Nullability | Notes |
|---|---|---|---|
timestamp | ISO 8601 string | required | Set at write. |
text | string | required | The user’s utterance verbatim (or the override reason for structured stages). |
stage | string | optional | Conventional values: brief, scenario-feedback, regeneration-request, no-ref-consent. Free-form. |
note | string | optional | Structural context. For no-ref-consent rows, the conventional shape is slot=<slot-id>. |
The no-ref-consent row
When the user overrides the reference-required gate (AGENTS invariant #3) via --no-ref-consent "<reason>", cli/commands/generate.ts → maybeLogNoRefConsent() writes:
stage: "no-ref-consent" rows before refusing a similar request again.
Lifecycle
- Written by the agent / playbook on every user-driven beat (intake answer, scenario feedback, regen ask).
- Written automatically by
--no-ref-consentand similar override flags. - Read by
ralphy project timeline <id>to reconstruct the user-flow.
user-assets.jsonl
References the user supplied — local files, screenshots, ref URLs. Written bylogUserAsset().
Example lines
Fields
| Field | Type | Nullability | Notes |
|---|---|---|---|
timestamp | ISO 8601 string | required | Set at write. |
kind | enum | required | screenshot, photo, video, audio, doc, ref-url, other. |
source | string | required | Original path or URL from the user. Absolute paths are common — ~/Downloads/.... |
dest | string | optional | Stored path inside the project (relative to workspace/projects/<id>/) when the file was copied in. Empty for refs the user kept in-place. |
purpose | string | optional | Free-form role label. Conventions: character-ref, product-ref, brand-screenshot, style-ref, voice-ref. |
note | string | optional | Author’s note. Privacy reminders, version tags, source attribution. |
Lifecycle
- Written when the user drops a file into the chat, runs
ralphy ref add <url-or-path>, or attaches a reference via the intake protocol. - Read by the art-director playbook to assemble the
--reflist forralphy generate image / video. - Read by
ralphy project show <id>for the references panel.
Read patterns
cli/lib/gen-log.ts → readLog<T>(projectId, name) returns the parsed array:
jq is fine:
Stability commitment
The fields above do not change without a migration. Specifically:- Renaming a field is a breaking change. Add a new field, deprecate the old in a separate PR after one major release.
- Tightening a field’s type is a breaking change. A field that was
string?cannot become required without a migration script that backfills. - Adding a new optional field is safe. Readers must tolerate unknown fields.
- Adding a new value to an enum (
provider,kind,stage,purpose) is safe — readers must tolerate unknown values.
cli/lib/gen-log.ts. Open a PR that touches the type to discuss any breaking change.
Why JSONL, not a single JSON document
Append-only is the constraint. A single JSON document would force a read-and-rewrite on every event; the JSONL shape allows naive append without parsing. The trade-off is that consumers must handle line-by-line parsing, which is fine — every read patten above does exactly that.Related
- Concepts: generation log — what the log is for, narrated.
- Working: logs and costs — how to use the data day-to-day.
- Asset manifest — the per-slot pointer table that sits next to the logs.
cli/lib/gen-log.ts— writer source.AGENTS.md— invariant #13 (append-only).