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).
7.9 KiB
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)
- First-message / greeting (the driver).
- Append-only narrator / scripted / scene turns.
- 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-rolerole(the collision that 404s). - v1 roles:
assistant(agent) +system(OOC / narrator).useris 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:
authorrisks doing double duty — provenance ("who wrote it") vs rendering-role ("how it appears in assembled context"; anassistantturn renders as model output, asystemturn as instruction). These likely want to be separable (a rendering/turn-class vs anauthored_byprovenance). 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/doneand 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(orauthored), NOTcompleted(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/userturn indistinguishable from real input is a spoofing vector. Framed as security, not just rendering. memory_importsemantics 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.