> ## Documentation Index
> Fetch the complete documentation index at: https://ralphy.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Memory schemas

> The three JSONL logs under a project's logs directory — append-only fields, lifecycle, and read patterns.

Every project has an append-only memory at `.ralphy/workspaces/<ws>/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

```
.ralphy/workspaces/<ws>/projects/<id>/logs/
  generations.jsonl   # every model call (one line per call)
  user-prompts.jsonl  # chronological user prompts and overrides
  user-assets.jsonl   # uploaded references (screenshots, photos, ref-urls)
```

Each file is line-delimited JSON. One JSON object per line. No header line, no trailer.

## 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 with `fs.appendFile()`. Never `fs.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.

The logs are the cost rollup, the reasoning trail, and the postmortem source. Truncating them destroys all three.

## generations.jsonl

One line per model call. Written by `cli/lib/gen-log.ts → logGeneration()`. Read by `ralphy project log <id>`, `ralphy project timeline <id>`, and the cost-rollup verbs.

### Example line

```json theme={"dark"}
{
  "timestamp": "2026-05-11T15:49:57.306Z",
  "provider": "openrouter",
  "endpoint": "google/gemini-3-pro-image-preview",
  "kind": "image",
  "slot": "scene-01-bg-image",
  "input": { "prompt": "...", "size": "1080x1920", "refs": [] },
  "output": { "url": "https://...", "local": "artifacts/images/scene-01-bg-image.png" },
  "status": "ok",
  "latency_ms": 8430,
  "cost_usd": 0.15,
  "request_id": "or-req-abc123",
  "note": "v2 — softened the cheek-kiss to a forehead-kiss per user feedback"
}
```

### 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

```ts theme={"dark"}
// cli/lib/gen-log.ts
export async function logGeneration(
  projectId: string,
  entry: Omit<GenerationEntry, "timestamp"> & { timestamp?: string }
): Promise<void>;
```

The `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 the `working/logs-and-costs` doc page.

## user-prompts.jsonl

Chronological user prompts, plus structured-override entries (e.g. `--no-ref-consent` reasons). Written by `logUserPrompt()`.

### Example lines

```json theme={"dark"}
{"timestamp":"2026-05-07T12:16:46.930Z","text":"Soft-soap fruit drama about a peach husband and a peach wife. Native EN VO via Veo 3.","stage":"brief"}
{"timestamp":"2026-05-07T14:33:12.001Z","text":"Make scene-03 longer, more emotional","stage":"scenario-feedback"}
{"timestamp":"2026-05-07T15:02:44.118Z","text":"explicitly accepting risk on Old Spice reference","stage":"no-ref-consent","note":"slot=scene-02-vid"}
```

### 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:

```json theme={"dark"}
{
  "timestamp": "...",
  "text": "<reason from the flag>",
  "stage": "no-ref-consent",
  "note": "slot=<slot-id>"
}
```

This is how future sessions see that the user deliberately accepted the quality hit on a named-entity generation. The agent reads `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-consent` and 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 by `logUserAsset()`.

### Example lines

```json theme={"dark"}
{"timestamp":"2026-05-12T10:18:01.741Z","kind":"photo","source":"/Users/me/books/headphone-01.webp","dest":"artifacts/refs/headphones/headphone-01.webp","purpose":"product-ref","note":"Nothing Headphone (1) reference pack — angle 01"}
{"timestamp":"2026-05-18T12:18:10.041Z","kind":"photo","source":"/Users/me/Downloads/photo.jpeg","purpose":"character-ref","note":"User selfie — DO NOT view content; pass-through only."}
{"timestamp":"2026-04-22T08:09:02.846Z","kind":"screenshot","source":"screenshot-cap-front.png","purpose":"product-ref","note":"nobody.solutions METAL bucket hat"}
```

### 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 `.ralphy/workspaces/<ws>/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 `--ref` list for `ralphy 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:

```ts theme={"dark"}
const gens = await readLog<GenerationEntry>("fruit-drama-001", "generations");
const prompts = await readLog<UserPromptEntry>("fruit-drama-001", "user-prompts");
const assets = await readLog<UserAssetEntry>("fruit-drama-001", "user-assets");
```

For ad-hoc work, raw `jq` is fine:

```bash theme={"dark"}
# total spend on a project
jq -s '[.[] | select(.status=="ok") | .cost_usd // 0] | add' \
  .ralphy/workspaces/<ws>/projects/fruit-drama-001/logs/generations.jsonl

# every model call that failed
jq -c 'select(.status=="error") | {endpoint, error, ts: .timestamp}' \
  .ralphy/workspaces/<ws>/projects/fruit-drama-001/logs/generations.jsonl
```

The CLI verbs are the preferred path:

```bash theme={"dark"}
ralphy project log fruit-drama-001 --kind generations
ralphy project timeline fruit-drama-001
ralphy project log-prompt fruit-drama-001 --text "make scene-03 shorter"
ralphy project log-asset fruit-drama-001 --source ./ref.png --purpose product-ref
```

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

The writer types live in `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](/concepts/generation-log) — what the log is *for*, narrated.
* [Working: logs and costs](/working/logs-and-costs) — how to use the data day-to-day.
* [Asset manifest](/advanced/asset-manifest) — the per-slot pointer table that sits next to the logs.
* [`cli/lib/gen-log.ts`](https://github.com/alecs5am/ralphy/blob/main/cli/lib/gen-log.ts) — writer source.
* [`AGENTS.md`](https://github.com/alecs5am/ralphy/blob/main/AGENTS.md) — invariant #13 (append-only).
