Skip to main content
Every project keeps three append-only logs under workspace/projects/<id>/logs/. Together they tell you what the model did, what you asked for, and what you uploaded — chronological, structured, never overwritten. The logs are how Ralphy reasons across sessions (“we tried this prompt three times and it kept missing the same way”), how the cost rollup gets built, and how the auto-generated postmortem knows what happened. They are also your audit trail when something looks wrong. The contract is AGENTS invariant #13: append-only. No “tidy up” passes, no in-place rewrites, no silent truncation. Reading or filtering is fine; mutating is not.

The three log files

FileWritten byCarries
generations.jsonlralphy generate (every sub-verb), ralphy render, ffmpeg recipesProvider, model, slot, input, output, latency, cost, status
user-prompts.jsonlralphy project log-prompt, intake stages, --no-ref-consent overrideStage label, prompt text, timestamp, optional note
user-assets.jsonlralphy project log-assetKind (screenshot / photo / video / doc / ref-url), source path or URL, purpose
Every line is one JSON object. Files grow, never shrink. The append happens via fs.appendFile in cli/lib/gen-log.ts.

Reading with project log

ralphy project log syrup-001 --type generations --limit 50
Returns the most recent N entries (newest last) as a JSON array. Filter by type:
  • --type generations — model calls and renders.
  • --type user-prompts — what you said, when, at which stage of the intake.
  • --type user-assets — what you uploaded as a reference.
  • --type all — all three, merged and sorted chronologically.
Pretty output is -p. The default JSON is what you want for piping to jq.

Chronological view with timeline

ralphy project timeline syrup-001
Renders the merged log as a chronological table — who said what, when, in human-readable form. Good for reconstructing what happened in a session three days ago when you can’t remember whether you regenerated scene-03 once or three times.
┌─────────────────┬────────────┬──────────────────────────────────────────┐
│ timestamp       │ type       │ summary                                  │
├─────────────────┼────────────┼──────────────────────────────────────────┤
│ 14:32:11        │ user       │ brief: "selfie review, cozy autumn"      │
│ 14:32:14        │ generation │ image scene-01-bg → $0.04                │
│ 14:33:02        │ generation │ video scene-01-vid → $0.30 (kling, 5s)   │
│ 14:34:18        │ user       │ feedback: "scene-03 too slow"            │
│ 14:34:33        │ generation │ video scene-03-vid v2 → $0.42 (seedance) │
│ 14:36:01        │ render     │ final.mp4 → 4.3 MB                       │
└─────────────────┴────────────┴──────────────────────────────────────────┘

Writing to the user logs

Two verbs append to user-prompts.jsonl and user-assets.jsonl from the CLI directly. Use them when the agent isn’t running and you want to record context for the next session.
ralphy project log-prompt syrup-001 \
  --text "user wants the hook to feel more spontaneous, less rehearsed" \
  --stage feedback \
  --note "iteration round 2"
ralphy project log-asset syrup-001 \
  --kind ref-url \
  --source https://www.tiktok.com/@username/video/1234 \
  --purpose style-reference \
  --note "the energy I'm aiming for"
Stage labels are conventional, not enforced: brief, feedback, decision, no-ref-consent, clarification, etc. The intake protocol uses them to reconstruct the conversation flow.

Cost rollups with jq

generations.jsonl carries cost_usd and model fields on every entry. Roll up by model:
ralphy project log syrup-001 --type generations --limit 500 \
  | jq -s 'group_by(.model)
           | map({ model: .[0].model,
                   calls: length,
                   total_usd: (map(.cost_usd // 0) | add) })'
[
  { "model": "google/gemini-3-pro-image-preview", "calls": 12, "total_usd": 1.80 },
  { "model": "kwaivgi/kling-v3.0-pro", "calls": 5,  "total_usd": 1.50 },
  { "model": "elevenlabs/eleven_multilingual_v2", "calls": 4, "total_usd": 0.62 },
  { "model": "elevenlabs/music", "calls": 1, "total_usd": 0.15 },
  { "model": "hyperframes", "calls": 3, "total_usd": 0.0 }
]
Filter to failures:
ralphy project log syrup-001 --type generations --limit 500 \
  | jq '. | select(.status == "error")'
Per-slot cost:
ralphy project log syrup-001 --type generations --limit 500 \
  | jq -s 'group_by(.slot)
           | map({ slot: .[0].slot, total_usd: (map(.cost_usd // 0) | add) })'
For batch-level rollups, ralphy batch show <id> --json aggregates the same numbers across all projects in the batch.

The auto-generated postmortem

A POSTMORTEM.md lands at the root of any project that the /postmortem skill has been run on. The skill triggers automatically on batch completion and on demand any time the user types /postmortem or asks for a “retro” / “lessons learned” / “debrief”. For non-trivial multi-iteration sessions, the skill writes six structured files under workspace/projects/<id>/postmortem/:
  • Chronological chat history extracted from the log files.
  • Lessons learned (with project-specific receipts).
  • Ralphy CLI bug list — the raw bunx / ffmpeg / curl workarounds the agent had to reach for, so the verb gap can be filed.
  • Model and cost rollup.
  • Workflow and playbook fixes.
The postmortem is how Ralphy gets better over time. Lessons from one project show up in the playbooks; CLI gaps from one project become new verbs in cli/commands/.

Cost discipline

Three rules from the playbooks worth internalizing:
  • Always cross-check MODELS.md before assuming a model’s price. Claude’s training is stale; the canonical numbers live in MODELS.md and the live OpenRouter catalog (ralphy models list).
  • --dry-run is free. Every paid generate sub-verb supports it. Always preview before a 10-second kling call or a long ElevenLabs Music render. Detail: Generating assets.
  • Budget caps are the lever, not model downgrade. AGENTS invariant bans “use a cheaper draft model” as a cost strategy — Ralphy uses the best model throughout. If you need to control cost, set a budget cap on the batch or generate fewer variants; don’t drop to a worse model.

When a log file looks weird

The append-only contract means the logs should grow monotonically. If you ever see a generations.jsonl that shrank between sessions, something violated the contract — either a tool wrote to the file outside the CLI, or a backup restore overwrote it. Both are bugs worth reporting. The CLI’s own append path is well-tested; the failure modes are usually outside. If you suspect drift between the manifest and the files on disk:
ralphy project verify syrup-001
The verify pass walks the manifest, checks every referenced file exists, checks the gen-log entries match the manifest entries, and reports drift. The verify is read-only — it won’t fix anything, but it’ll tell you what’s broken so you can fix it by hand or by regenerating.

Cross-project workspace stats

ralphy workspace stats
Aggregates across every project: total cost, total render count, disk usage, count by status. Useful for “how much did I spend this month” — pipe to jq and filter by generatedAt to scope to a date range.