docs(proposals): authored history write primitive — heid-panel-reviewed v1 scope

Non-generating ledger-seed primitive for Worldtree, driven by ratatoskr's
first-message need. v1 narrowed to append-only + create-time; bounded effects
enum (none|memory_import); edit/regenerate + batch-import split to future
primitives. Positions taken: distinct sub-resource, event-silence for
default-off seed, seeded lifecycle phase, structured provenance, user-author
restricted. Consumer proposal for worldtree-dev (engine owner).
This commit is contained in:
vh
2026-07-05 11:06:11 -07:00
parent 9ca931e148
commit c457520ae4
@@ -0,0 +1,163 @@
# 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.*