190 lines
8.1 KiB
Markdown
190 lines
8.1 KiB
Markdown
# Current-Branch Context Checkpoints
|
|
|
|
Status: design frozen for the Wave 7 implementation slice.
|
|
|
|
## Purpose
|
|
|
|
Long stories must stay inside the selected LAPP model's real context window without silently
|
|
dropping the current input, triggered world-book entries, runtime facts, or recent committed
|
|
scenes. A checkpoint is a disposable narrative cache. It is never an authoritative source for
|
|
flags, relationships, promises, inventory, knowledge, checks, or branch structure.
|
|
|
|
## Prerequisites
|
|
|
|
Checkpoint persistence starts only after these contracts are represented in code:
|
|
|
|
1. The selected LAPP model's `context_window` and `max_output_tokens` reach the Runtime provider.
|
|
2. The stable prompt prefix has a deterministic resource fingerprint.
|
|
3. The narrative source path has a canonical ordered hash that covers committed public story
|
|
content, not only `RuntimeState`.
|
|
4. `Regenerate` excludes the replaced node from narrative history, constrains the replacement with
|
|
the target node's committed post-state, creates a sibling on a new branch, and reuses existing
|
|
hidden-check outcomes.
|
|
|
|
## Prompt budget
|
|
|
|
The provider computes one explicit budget before every model request:
|
|
|
|
```text
|
|
hard input budget =
|
|
model context window
|
|
- requested output tokens
|
|
- system prompt and tool schema
|
|
- message framing overhead
|
|
- reserved hidden-check continuation overhead
|
|
- safety margin
|
|
```
|
|
|
|
- Requested output is `min(model.max_output_tokens ?? 4096, 4096)`.
|
|
- A missing context window uses a conservative 16,384-token V1 fallback, but the resulting
|
|
`BudgetSource::Assumed` diagnostic must remain visible to the connection/settings layer. The
|
|
fallback must not be silent.
|
|
- Cross-provider V1 estimation treats every serialized UTF-8 byte as at most one token. This may
|
|
compact early but must not optimistically overfill a model window.
|
|
- Compression starts at 70% of the hard input budget and compacts back below a lower watermark.
|
|
- The complete dynamic tail is budgeted first. If it does not fit by itself, the request fails with
|
|
`DynamicTailTooLarge`.
|
|
- At least one recent committed node remains verbatim. A single oversized node fails with
|
|
`HistoryEntryTooLarge`; beat text is never truncated.
|
|
- Every initial and hidden-check continuation `ChatInput` must remain within budget.
|
|
|
|
## Prompt layout
|
|
|
|
Prompt schema v3 will replace the current v2 history array with one production checkpoint-aware
|
|
encoder:
|
|
|
|
```text
|
|
prompt_schema_version
|
|
stable_prefix
|
|
branch_context
|
|
checkpoint?
|
|
raw_tail[]
|
|
dynamic_tail
|
|
```
|
|
|
|
`stable_prefix` remains byte-identical while the bound resources do not change. `dynamic_tail`
|
|
always contains the current input and the complete safe state projection. A checkpoint replaces
|
|
only a continuous oldest prefix of `raw_tail`.
|
|
|
|
## Checkpoint record
|
|
|
|
SQLite schema v3 adds a cache table keyed by the immutable host node, not by branch:
|
|
|
|
```sql
|
|
CREATE TABLE context_checkpoints (
|
|
story_id TEXT NOT NULL,
|
|
at_node_id TEXT NOT NULL,
|
|
covered_through_node_id TEXT NOT NULL,
|
|
retained_from_node_id TEXT,
|
|
checkpoint_schema_version INTEGER NOT NULL,
|
|
prompt_schema_version INTEGER NOT NULL,
|
|
stable_prefix_hash TEXT NOT NULL,
|
|
summary_json TEXT NOT NULL,
|
|
source_hash TEXT NOT NULL,
|
|
|
|
PRIMARY KEY (story_id, at_node_id),
|
|
FOREIGN KEY (story_id, at_node_id)
|
|
REFERENCES nodes (story_id, node_id) ON DELETE CASCADE,
|
|
FOREIGN KEY (story_id, covered_through_node_id)
|
|
REFERENCES nodes (story_id, node_id) ON DELETE CASCADE,
|
|
FOREIGN KEY (story_id, retained_from_node_id)
|
|
REFERENCES nodes (story_id, node_id) ON DELETE CASCADE
|
|
);
|
|
```
|
|
|
|
- The nearest checkpoint is found only by walking the current node's `parent_id` chain.
|
|
- Shared ancestors naturally share a checkpoint; sibling-only descendants are unreachable.
|
|
- The summary is self-contained. A new checkpoint replaces the old summary instead of nesting a
|
|
chain of summaries in the prompt.
|
|
- Successful checkpoint generation may be persisted independently on the existing host node.
|
|
Failure of the later story-generation call may leave this harmless cache in place.
|
|
- Deleting every checkpoint must leave nodes, branch heads, materialized state, and `PlayerView`
|
|
byte-identical.
|
|
|
|
## Source and resource hashes
|
|
|
|
The model never supplies either hash.
|
|
|
|
`stable_prefix_hash` covers the exact canonical stable-prefix encoding, including character,
|
|
Persona, resource provenance, and prompt safety/version fields.
|
|
|
|
`source_hash` covers:
|
|
|
|
```text
|
|
checkpoint source schema version
|
|
prompt schema version
|
|
stable_prefix_hash
|
|
story id
|
|
ordered root-to-covered sequence of:
|
|
node id
|
|
parent id
|
|
player input
|
|
public scene
|
|
public character visual state
|
|
committed beats
|
|
```
|
|
|
|
Unselected suggestions, state delta, exact relationship values, checks, NPC private inventory,
|
|
untriggered world-book content, provider responses, credentials, and chain-of-thought never enter
|
|
the source manifest or compression request.
|
|
|
|
## Regenerate boundary
|
|
|
|
`Regenerate` is a full alternative rendering of one committed player choice:
|
|
|
|
- the replaced node is not included in the new prompt;
|
|
- model history ends at the replaced node's parent, while the target node's authoritative
|
|
post-state supplies only the already-committed facts and fixed qualitative check outcomes;
|
|
- the replacement creates a sibling on a new runtime-generated branch;
|
|
- hidden checks from the original action are supplied as qualitative fixed outcomes and are not
|
|
rolled again;
|
|
- a model cannot submit forged `RecordCheck` operations;
|
|
- old nodes, descendants, and checkpoints remain immutable and become naturally unreachable from
|
|
the new branch unless they are shared ancestors.
|
|
|
|
The sibling keeps the original player action identity, authoritative delta, terminal state, and
|
|
exact hidden-check records. Those immutable records intentionally retain their original source-node
|
|
provenance; the new branch is an alternative presentation of that same committed action, not a new
|
|
roll or a new state transition.
|
|
|
|
## Summary safety
|
|
|
|
The summary may retain public causal order, actual player choices, public NPC actions, revealed
|
|
facts with their certainty, unresolved conflicts, shared goals, and observable emotional residue.
|
|
It may not become a trigger or rules input.
|
|
|
|
The summary must not contain:
|
|
|
|
- exact dice mechanics or relationship numbers;
|
|
- private NPC knowledge, private inventory, or unrevealed item provenance;
|
|
- hidden flags, clocks, event conditions, or untriggered world-book entries;
|
|
- unselected suggestions, cancelled output, sibling-branch content, or inferred player thoughts;
|
|
- credentials, request headers, raw provider bodies, or chain-of-thought.
|
|
|
|
The current player inventory projection must not be fed to the summarizer until ownership and
|
|
acquisition fields have their own player-knowledge visibility boundary.
|
|
|
|
## Failure semantics
|
|
|
|
- No valid checkpoint and history above the high watermark returns `NeedsCompaction`; history is
|
|
never silently shortened.
|
|
- Invalid schema, source hash, stable-prefix hash, range, or summary size makes a checkpoint
|
|
unusable and rebuildable.
|
|
- Broken authoritative ancestry or state remains a hard store error and is not downgraded to a
|
|
cache miss.
|
|
- Cancelled, timed-out, rate-limited, or malformed compression produces no story node and moves no
|
|
branch head.
|
|
- A first load of a very long legacy branch uses bounded rolling chunks and keeps only the final
|
|
self-contained summary.
|
|
|
|
## Required gates
|
|
|
|
- Exact window boundary, one-token overflow, unknown-model-limit fallback, and smaller-model switch.
|
|
- Every normal and hidden-check request stays within the computed budget.
|
|
- 500-node first compaction and incremental compaction from an existing checkpoint.
|
|
- v2-to-v3 migration, rollback, restart recovery, cache deletion, and corruption rejection.
|
|
- Root, shared-ancestor, pre/post-checkpoint fork, regenerate, and sibling-canary isolation.
|
|
- Stable-prefix cache identity and one cache break only when a checkpoint rotates.
|
|
- Hidden canaries for checks, exact relationships, NPC facts/items, untriggered resources,
|
|
unselected suggestions, credentials, and provider bodies.
|