# Proposal: Authored History Write (non-generating ledger seed) **From:** ratatoskr-dev (reference Tier-3 consumer) **To:** worldtree-dev (Conversation API / engine owner) **Status:** Draft for scoping — pre-contract (heid-panel-reviewed 2026-07-05) **Date:** 2026-07-05 ## Motivation Consumer apps need to write a turn into a session's history **as the agent** (or another author) *without triggering a model generation* — e.g. an authored opening/greeting, imported history, scripted narration. Ratatoskr's immediate driver is a SillyTavern-style **first-message**: a fixed authored opening that replaces the model-generated greeting and sets tone/tense/style by example. This **cannot** be done client-side. Worldtree assembles context server-side, and the current API exposes no author-role write path: `POST /sessions/{id}/messages`'s `role` is a *model-role* override (`role: "assistant"` → `404 "Unknown model role"`), and `assistant` as an *author*-role exists only as a read-side `/search` filter. So a model-visible authored turn needs engine support. ## The primitive (recentered) The fundamental operation is **write a turn into the session ledger WITHOUT generation**. "Author" (who wrote it) is an *attribute* of that write, not the defining axis — so we name the operation, not the attribute: > **Authored history write** — persist a model-visible turn into a session's > ledger: no generation, no lived-turn side-effects by default, provenance > always set. The design space is two independent axes; this primitive is one cell: | | side-effects ON | side-effects OFF | |-----------------------|------------------------------|-----------------------------| | **generation ON** | `POST /messages` (today) | — | | **generation OFF** | *(future: affect replay)* | **authored history write** | First-message = one caller: `author=assistant`, at session-create, `effects=none`. ## v1 use cases (narrowed) 1. **First-message / greeting** (the driver). 2. **Append-only narrator / scripted / scene turns.** 3. **Debug / test state injection** (ratatoskr instrumentation). ## Explicitly OUT of v1 — separate future primitives (share infra, not shape) - **History import (batch)** — atomic multi-turn seed with memory/trust policy + idempotency. A batch API, not a single POST. - **Edit / regenerate** — history *mutation* (replace / supersede / tombstone / audit), not injection. - **Few-shot priming** — likely context-assembly config (exemplar block), not fake ledger history. - **Arbitrary mid-history insertion** — a "rewrite-history" capability with explicit invalidation semantics. - **Prefill / assistant-continuation** (`author` + generate) and **authored tool-result turns** — noted; outside the seed-only contract. ## Design decisions ### 1. Side-effects — DEFAULT OFF; bounded opt-in `[operator-locked default; opt-in surface tightened by review]` Authored writes are inert by default: no affect appraisal (no PAD update), no memory write, no Bifrost/tool emission. Opt-in is a **bounded enum**, not loose booleans: ``` effects: "none" (default) | "memory_import" ``` Synthetic affect and Bifrost emission are deliberately **not** opt-in-able here — replaying affect for authored content is a separate primitive (the generation-OFF / side-effects-ON cell). Rationale: keep this one write-API from becoming a cross-subsystem mutation backdoor. Load-bearing for affect/memory consumers — ratatoskr instruments exactly these signals. ### 2. Author-role — distinct field, restricted set `[rec]` - New field **`author`**, distinct from the model-role `role` (the collision that 404s). - v1 roles: **`assistant`** (agent) + **`system`** (OOC / narrator). **`user` is NOT injectable** on this endpoint — model-visible spoofed user input is a consent / audit / abuse surface; deferred to the future import API under owner/service scope. - Nuance for the engine owner: `author` risks doing double duty — *provenance* ("who wrote it") vs *rendering-role* ("how it appears in assembled context"; an `assistant` turn renders as model output, a `system` turn as instruction). These likely want to be separable (a rendering/turn-class vs an `authored_by` provenance). Final shape is engine-owned (context assembly is yours) — but the concern is ours to raise, not punt. ### 3. Generation contract — seed-only, DISTINCT SUB-RESOURCE `[position taken]` Authored writes never trigger generation. We take a position (not defer): a **distinct sub-resource**, e.g. `POST /sessions/{id}/history`, **not** a `generate:false` flag on `POST /messages`. Reasons: explicit-over-implicit (don't make "did generation happen?" a parameter — the same implicit-mode coupling that bit us with `role`); different response contract (no generation id, no SSE stream, no token usage); different error surface. Exact path is yours. ### 4. Provenance — structured, always present `[rec, expanded]` Not a boolean. Every authored turn carries: the **write actor** (which consumer/caller injected it), the **claimed author**, **injected-at vs claimed-original** timestamps, **trust/origin**, and **visibility** flags (model-visible? user-visible? memory-eligible?). Available to admin/audit APIs even when not rendered to the model. ### 5. Positioning — append-only + create-time (v1) `[revised: was arbitrary insertion]` v1 supports **create-time seed and append-to-tail only**. Arbitrary mid-history insertion is deferred: it breaks turn-numbering, stales existing embeddings, desyncs the affect timeline, and races in-flight generation — a separate future "rewrite-history" capability with explicit invalidation semantics. ## Event / lifecycle contract — positions we take (consumer contracts we validate) - **Default-off authored seed emits NO `turn.started` / `done` and NO Bifrost appraisal wire.** Stated explicitly so instrumented consumers (us) don't read silence as failure. - **Authored turns get a distinct lifecycle phase** — propose **`seeded`** (or `authored`), NOT `completed` (which implies generation ran). Consumers filter/display by phase. - **Idempotency keys required** on authored writes (retries must not duplicate turns). - **In-progress generation** — authored writes are rejected or serialized while a session has an active generation (ordering safety). ## Inherent property (documented, not a bug) **Indirect affect contamination.** Even with `effects:none`, the *next generated turn is appraised in the context of* the authored turn — so an emotionally charged authored beat perturbs affect regardless of any flag. No flag prevents it; it is inherent. Consumers (ratatoskr especially, as the affect instrument) must not misattribute the resulting drift. ## Genuinely engine-owned open questions - Exact endpoint path + field / enum names. - **Model-visible provenance in assembled context** — an engine-consistency call *and a security one*: an authored `system` / `user` turn indistinguishable from real input is a spoofing vector. Framed as security, not just rendering. - `memory_import` semantics when the future import API opts in (embedding, origin/trust tagging, retrieval ranking vs lived memory). - Auth/scope: we assume **owner-only for v1**; per-author-role restrictions (esp. `system`) TBD — confirm or correct. ## Ratatoskr as reference consumer First consumer: first-message (`author=assistant`, create-time, `effects:none`) in the web surface + debug seed in the CLI. We commit to validating the primitive — including the event-silence contract and the `seeded` phase — end-to-end against the reference planes. --- *This brief was cold-read-pressure-tested by a cross-frontier panel (Grok / Codex / GLM) before handoff; the v1 narrowing (append-only, bounded `effects` enum, edit/regenerate + import split out) and the positions-taken (sub-resource, event-silence, `seeded` phase, structured provenance, `user`-author restriction) are the triaged result.*