Compare commits

...
2 Commits
Author SHA1 Message Date
JesseMarkowitz 717670afe0 Update planning package after Phase 0B 2026-09-01 20:41:23 -04:00
JesseMarkowitzandClaude Opus 5 ba737de9b4 Add Phase 0B local validation findings and recommendation
Validates the three finalists by clone, build, test run and live local
Ollama inference, then answers the fork question with measurements rather
than static review.

Recommendation: fork AI-DnD, confidence high. The Phase 0A call holds, but
it was wrong that AI-DnD's undo is non-destructive — retry preserves the
replaced take, undo hard-deletes it. A follow-up spike fixed that in 3
files (+130/-31): undo now moves a head cursor, redo round-trips, writing
below a moved-back head forks and keeps the abandoned line, branch-scoped
memory isolation survives, suite 627/632 with all 5 failures asserting the
deleted-row behaviour that was replaced.

Findings that change the plan:
- AI-DnD cannot take a turn air-gapped as shipped; tiktoken fetches its
  encoding from a CDN. Proven on an internal Docker network, proven fixed
  by vendoring the file.
- ai-adventure needs zero code for Ollama — two config lines — and its
  turn/head/checkpoint schema is the target model to build to.
- Open Dungeon has zero automated tests and a positional summary
  watermark, making its branch retrofit larger than Phase 0A costed.
- The world-state referee takes relative deltas; a 3B model sent absolute
  values under full context, so a wounded player ended at full health.
  Validation cannot catch this, so prefer ai-adventure's typed-event
  vocabulary when generalising narrative state.
- Export/import recomputes head depth, so a round-trip silently undoes an
  undo. Must be fixed alongside the undo work.

Docs only; no production code. Working tree from the runs stays untracked
under phase0b/.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015gUPLuxLs8wypxZPEmccJu
2026-09-01 16:11:38 -04:00
41 changed files with 3453 additions and 1203 deletions
+35 -40
View File
@@ -1,6 +1,6 @@
# Adventure Storyteller — Browser UX Specification
**Status:** Draft v0.1
**Status:** v1.0 UX target — selected base is AI-DnD
**Purpose:** Define the browser-based user experience for v1, including primary storytelling flow, history controls, campaign management, state inspection, imported knowledge, prompt inspection, and future media extension points.
## 1. UX Goal
@@ -1288,39 +1288,38 @@ If a fork contains:
remove or hide from v1 rather than exposing confusing advanced controls.
## 94. Candidate Reuse — AI-DnD
## 94. Selected Base Reuse — AI-DnD
Evaluate:
- transcript UX,
- alternate take controls,
- Insights/prompt inspection,
- story-tree UI coupling,
- state panels.
Retain/adapt:
- React/Vite browser shell,
- transcript/streaming foundation,
- alternate-take controls where useful,
- Insights/prompt inspection concepts,
- existing story-history controls as the starting point.
Goal:
- retain strong inspection/rollback behavior,
- simplify RPG-heavy surfaces.
Production UX must simplify RPG-heavy surfaces and hide branch/tree implementation details behind Undo/Redo/Retry/Save Point behavior.
## 95. Candidate Reuse — Open Dungeon
## 95. Reference Reuse — Open Dungeon
Evaluate:
- main story layout,
- interaction modes,
- Retry/Edit UX,
- image placement,
- visual continuity controls.
Use as a design reference for:
- main story reading layout,
- simple interaction feel,
- Retry/Edit presentation,
- inline image placement,
- visual continuity concepts.
Goal:
- borrow product feel without adopting destructive history semantics.
Do not port its destructive history semantics or treat its Next.js UI as a drop-in component source for the React/Vite fork.
## 96. Candidate Reuse — ai-adventure
## 96. Reference Reuse — ai-adventure
Primarily architectural, not UX.
Potential useful concepts:
Useful concepts:
- explicit state transparency,
- checkpoint terminology,
- deterministic operation.
- checkpoint/head semantics,
- deterministic operation and auditability.
The browser experience remains owned by the AI-DnD-based application.
## 97. V1 Navigation Map
@@ -1463,23 +1462,19 @@ User:
3. identifies bad source,
4. disables/corrects it.
## 103. Phase 0B UX Validation Questions
## 103. Phase 0B UX Findings Applied
Codex should answer:
Phase 0B closed the fork-level UX questions:
1. Which AI-DnD UI components are reusable without RPG mechanics?
2. Can its tree/history complexity be hidden behind simple Undo/Retry UX?
3. How mature is its Insights/context inspector?
4. Which Open Dungeon components provide a cleaner reading/play experience?
5. How tightly is Open Dungeon UI coupled to destructive history APIs?
6. Can its image UI be separated cleanly?
7. Is a right-side state/context panel practical in the chosen frontend?
8. Which advanced surfaces can be deferred without losing inspectability?
9. Can local-only status/model connection be made obvious?
10. Can campaign/knowledge/export management remain simple enough for a single-user app?
11. Can the input area reserve a clean extension point for future local STT without coupling microphone/transcription logic to story-state commits?
- AI-DnD provides the browser shell and prompt/Insights foundation worth retaining.
- Its tree/history complexity can be hidden behind a simple head-cursor Undo/Redo model.
- The frontend needs an explicit Redo control and Save Point workflow.
- RPG-specific presentation must be removed/generalized.
- Open Dungeon remains the stronger visual reference for a focused story-reading experience and future inline media, but its UI is not directly portable and is coupled to destructive history assumptions.
- ai-adventure contributes state/checkpoint concepts rather than browser components.
- the input area should reserve a future local STT affordance, but transcription remains editable draft input and is not implemented in v1.
## 104. Current Recommendation
## 104. Selected UX Direction
Build the browser experience around one uncluttered story screen:
@@ -1497,6 +1492,6 @@ Save Points
Settings
```
The user should be able to play for an hour without ever seeing a branch graph, database concept, embedding control, or developer diagnostic.
The user should be able to play for an hour without seeing a branch graph, database concept, embedding control, or developer diagnostic.
But when something goes wrong, the system should make the underlying state and context inspectable enough to explain and correct it.
When something goes wrong, the system must make state, provenance, and context inspectable enough to explain and correct it.
+513 -125
View File
@@ -1,163 +1,551 @@
# Adventure Storyteller — Build Milestones
# Adventure Storyteller — Production Build Milestones
**Status:** Placeholder / intentionally incomplete
**Do not use for production implementation yet.**
**Status:** Planning-ready; implementation not yet authorized
**Base:** AI-DnD `d72f7c1bda0f34fccd84afb7a25c34eb01c901de`
## 1. Purpose
The detailed production implementation plan will be created at the end of **Phase 0 — Research, Validation & Architecture**.
This document defines the production implementation sequence after Phase 0.
A precise plan cannot responsibly be written before the project has selected:
It is intentionally a milestone plan, **not a Codex execution prompt**. Each milestone should be converted into a separate coding brief only after the previous milestone has been reviewed and accepted.
- the base repository or build strategy,
- the final persistence/story-tree design,
- the final browser architecture,
- the Ollama integration model,
- the memory/retrieval strategy,
- the local-only hardening approach,
- the migration/reuse plan for inherited code.
The milestone order prioritizes load-bearing correctness and offline safety before broad feature work.
## 2. Why This Document Is Deliberately Limited
## 2. Global Rules for Every Milestone
Different fork choices create fundamentally different engineering work.
Each implementation milestone must:
Example:
- preserve upstream provenance and licensing notices,
- keep the application runnable whenever practical,
- add or update automated tests for changed behavior,
- avoid unrelated refactors,
- update affected documentation,
- record schema/migration changes,
- preserve local-only defaults,
- stop at the milestone boundary for review,
- avoid implementing later milestones opportunistically.
### If Open Dungeon is selected
Early milestones may require:
The product specification and acceptance tests outrank convenience inherited from AI-DnD.
- adding immutable story-tree persistence,
- adding branch-aware state restoration,
- introducing structured narrative state,
- adding long-term semantic memory.
## 3. End-User Capability Progression
### If AI-DnD is selected
Early milestones may instead require:
The application is expected to remain runnable throughout the sequence. **M8 does not create the browser UI from scratch**; AI-DnD already provides a browser interface from M1. M8 is where that inherited/adapted interface is completed and simplified into the intended v1 storyteller experience.
- removing RPG mechanics,
- removing cloud providers,
- removing account/hosted assumptions,
- simplifying world state while preserving story-tree behavior.
- **M1 — First playable baseline:** The user can open the inherited browser UI, start or resume a story, send prompts to Ollama, receive streamed narration, and persist the story across restart. The interface may still look and behave substantially like AI-DnD, and advanced history/state features are not yet converted to final semantics.
- **M2 — Clean local storyteller baseline:** Normal story play still works, but cloud/hosted/account/scripting surfaces are removed and Ollama can be either same-host or on an explicitly configured trusted-LAN machine. From the user's perspective this is still basic play, but the environment now matches the intended privacy/deployment model.
- **M3 — Safe history controls:** The user can play normally with production-grade Undo, Redo, Retry, alternate takes, and divergence without deleting old story history. Export/import also preserves the exact current position even when the user has undone several turns.
- **M4 — Save Points:** The user can name a point in the story, continue playing, restart, return to that Save Point, and take a different path without losing the later story. This is the first milestone where deliberate long-form experimentation/recovery should feel comfortable.
- **M5 — Reliable story state:** Characters, locations, possessions, relationships, facts, threads, and current scene become application-owned genre-neutral state rather than RPG-stat machinery. The user can begin inspecting/correcting what the storyteller believes, although the final polished state UI comes later.
- **M6 — Long-story memory:** The storyteller becomes much better suited to long campaigns because summaries and retrieved older memories remain branch-safe and inspectable. The user should be able to return to old people, promises, clues, and events without abandoned paths contaminating the active story.
- **M7 — Local knowledge library:** The user can import `.txt`/`.md` material as Canon, Reference, or Inspiration and have it retrieved locally with provenance. The functionality is usable, but some management/inspection surfaces may still be utilitarian until M8.
- **M8 — Finished v1 browser experience:** The existing browser UI is reorganized and polished around storytelling: streamlined campaign setup, transcript/input, Undo/Redo/Retry, Save Points, state, knowledge, context inspection, and model status. This milestone makes the product feel like the intended storyteller rather than an adapted AI-DnD application.
- **M9 — Portable/recoverable campaigns:** The user can reliably export, back up, import, and recover complete campaigns including history, Save Points, state, knowledge, and active position. This is where moving or restoring a campaign becomes a supported user workflow rather than merely an underlying capability.
- **M10 — Media-ready, still text-first:** Little or nothing visibly changes for normal play. The story/scene data and provider boundaries are prepared so image/video/audio/TTS/STT can be added later without redesigning the core application.
- **M11 — Release-quality v1:** The user experience should be functionally complete; this milestone proves it stays correct through long campaigns, repeated history operations, offline use, failures, export/import, and multiple genres. It is the release-validation milestone rather than a major new feature milestone.
### If ai-adventure is selected
Early milestones may instead require:
---
- adding an Ollama adapter,
- adding a browser API,
- building the browser UI,
- extending lore retrieval beyond current behavior.
# M1 — Establish Production Fork and Offline Baseline
A single detailed build plan written now would therefore contain false precision.
## Objective
## 3. Expected High-Level Production Phases
Create the production fork from the pinned AI-DnD commit and make ordinary local story operation genuinely offline-capable before larger changes.
These are directional only and must be rewritten after Phase 0.
## Scope
### Phase 1 — Production Foundation
- establish production fork/repository,
- preserve upstream provenance,
- remove or isolate unwanted functionality,
- establish development/test environment,
- confirm local Ollama integration.
- establish production repository/fork lineage,
- record upstream commit and license provenance,
- establish reproducible dev/test environment,
- vendor/cache/replace the `tiktoken` first-use encoding dependency so story generation works without Internet,
- eliminate runtime Google Fonts/remote font dependency,
- tighten CSP for local runtime assets,
- verify same-host Ollama story generation with outbound Internet blocked,
- verify explicitly configured trusted-LAN Ollama story generation while the storyteller UI/API remains loopback-bound,
- establish baseline regression/test report.
### Phase 2 — Authoritative Story Persistence
- immutable/recoverable turn history,
- branch parentage,
- checkpoints,
- restore,
- retry/edit semantics,
- transactional commits.
## Explicit Non-Scope
### Phase 3 — Narrative State
- generic entities,
- no history rewrite yet,
- no RPG-state redesign,
- no imported knowledge,
- no UI redesign,
- no media generation.
## Tests / Acceptance
Must demonstrate:
- A01-A06 as applicable to the inherited base, including the separate-host trusted-LAN inference path,
- H01-H03 and H11,
- no unexpected DNS/HTTP requests during ordinary story generation after setup,
- no remote font request,
- no tokenizer/BPE download on first production turn,
- existing relevant AI-DnD tests remain green except documented upstream/environment exceptions.
## Definition of Done
A clean production build can start, open the browser UI, generate and persist story turns through either same-host Ollama or an explicitly configured trusted-LAN Ollama host, restart, and resume with outbound Internet blocked. The storyteller UI/API remains loopback-bound by default.
---
# M2 — Remove Hosted, Cloud, Scripting, and Unneeded Deployment Surface
## Objective
Reduce AI-DnD to the intended single-user local product trust boundary without destabilizing the story/tree/memory foundation.
## Scope
Remove or isolate as appropriate:
- multi-user/account/guest/auth flows,
- demo API-key behavior,
- hosted analytics,
- Render/Neon deployment paths,
- Postgres/`psycopg` support,
- cloud providers not required by v1,
- arbitrary remote model-provider UI/configuration,
- QuickJS/campaign scripting,
- AI-Dungeon compatibility code that exists primarily to support scripting/hosted behavior,
- hosted-only rate-limit/account infrastructure.
Add:
- explicit Ollama endpoint policy: same-host loopback default plus user-configured trusted-LAN inference,
- clear local-model connection diagnostics,
- migration/test instrumentation replacements for any tests that depended on JS hooks or removed hosted paths.
## Explicit Non-Scope
- do not redesign story history,
- do not replace RPG state yet,
- do not build knowledge/media systems.
## Tests / Acceptance
- local story play still works,
- existing tree/retry/memory/context behavior remains intact,
- application requires no cloud API keys,
- approved trusted-LAN Ollama endpoints work while arbitrary public/Internet provider endpoints are rejected or absent from normal production configuration,
- removed provider/auth/analytics/scripting paths are no longer reachable from normal production configuration.
## Definition of Done
The codebase has a narrow single-user/local-only surface and the inherited story foundation still passes its relevant regression suite.
---
# M3 — Production Non-Destructive History, Redo, and Active-Head Export
## Objective
Replace destructive Undo with the demonstrated head-cursor model and make Undo/Redo/divergence/export/import production-safe.
## Scope
Promote the Phase 0B spike concept into maintainable production code:
- active head can move behind retained tip,
- Undo moves head and deletes zero accepted turns,
- Redo follows the retained continuation,
- new write behind tip forks on first write,
- retry/add-take/edit paths use the same safe fork/head rules,
- prevent Undo from walking before the campaign opening/root semantics,
- mark displaced/inactive futures/takes as retained/disposable using implementation-appropriate metadata,
- keep ordinary Redo invalidated after divergence,
- add browser Redo control and correct disabled/enabled states,
- keep branch complexity out of normal UI,
- export active head coordinate/depth as well as active branch,
- import honors exported active head,
- preserve backward compatibility for older bundles that lack the new field.
## Explicit Non-Scope
- named checkpoints belong in M4,
- no general branch-tree UI,
- no abandoned-history cleanup feature.
## Tests / Acceptance
Must cover:
- D01-D10,
- D02 minimum-five Undo,
- D03 practical unlimited Undo behavior across retained history,
- D04-D05 Redo semantics,
- E01-E04 lineage isolation,
- I01-I03 and I07 (undone-head round trip),
- L01-L02,
- branch-scoped memory remains isolated under Undo/Redo/divergence,
- row counts demonstrate Undo deletes zero accepted turns.
## Definition of Done
History operations are non-destructive, Redo works, divergence preserves old futures, and export/import reopens at the exact active head.
---
# M4 — Named Save Points / Checkpoints
## Objective
Add durable user-facing Save Points on top of the active-head model.
## Scope
- named checkpoint storage,
- checkpoint create/list/rename/delete,
- restore by moving the active head,
- later history retained rather than deleted,
- fork only on first new continuation after restore,
- checkpoint persistence across restart,
- checkpoint export/import,
- browser Save Point UX per `BROWSER-UX-SPEC.md`.
## Explicit Non-Scope
- no branch merge,
- no automatic discarded-history cleanup,
- no complex branch explorer required.
## Tests / Acceptance
- D11-D14,
- E lineage tests after checkpoint restore/divergence,
- I04,
- L03.
## Definition of Done
The user can create a named Save Point, continue, restart, restore it, and continue differently without losing later history.
---
# M5 — Genre-Neutral Authoritative Narrative State
## Objective
Replace/generalize AI-DnD's RPG-specific relative-delta state system with the approved genre-neutral typed-event model.
## Scope
Establish generic authoritative state for:
- entities,
- characters/locations/organizations/items/vehicles as descriptive entity categories,
- facts,
- relationships,
- possession/location/status/conditions,
- story threads,
- state extraction/validation,
- state inspector.
- scene state,
- manual state/canon corrections,
- state proposal provenance.
### Phase 4 — Long-Term Memory
- summaries,
- older-turn retrieval,
- token budgeting,
- provenance,
- continuity handling.
Implement:
### Phase 5 — Local Knowledge Library
- local file imports,
- Canon / Reference / Inspiration classes,
- chunking,
- local indexing,
- optional local embeddings,
- retrieval inspection.
- explicit typed state proposal schema,
- absolute/unambiguous event semantics,
- event allowlist,
- validation,
- atomic accepted-event commit,
- current/historical snapshot/cache,
- rollback/reconstruction tied to active lineage,
- browser current-state inspector foundation.
### Phase 6 — Browser UX Completion
- campaign management,
- transcript,
- branching visualization,
- checkpoints,
- state/editor,
- library management,
- prompt inspection,
- responsive local UI.
Remove or demote:
### Phase 7 — Local-Only Hardening
- remove remote providers,
- remove telemetry/analytics,
- remove runtime CDN dependencies,
- enforce/validate local endpoints,
- network tests,
- offline operation tests.
- D&D-specific stats/bands/cooldowns/mechanics from the core product model,
- relative-delta semantics as the generic state protocol.
### Phase 8 — Export, Backup, and Recovery
- campaign export,
- import,
- backups,
- migration,
- corruption/error recovery.
## Explicit Non-Scope
### Phase 9 — Future-Media Hooks
- scene snapshots,
- visual character/location descriptors,
- asset schema,
- media-provider interfaces,
- no required image/video implementation.
- no optional RPG module,
- no imported knowledge yet,
- no final rich state-editing UX if a simpler inspector is enough for this milestone.
### Phase 10 — v1 Validation and Release
- regression testing,
- long-story testing,
- rollback/branch tests,
- offline test,
- migration test,
- documentation,
- release packaging.
## Tests / Acceptance
## 4. Gate Before This Plan Becomes Active
- C01-C04 and C06,
- D/E tests verifying state follows head movement,
- H05 invalid state event rejection,
- L01-L02,
- malformed JSON/state proposal handling,
- realistic-context tests against representative local models,
- fantasy and science-fiction state fixtures use the same schema.
Do not convert the high-level phases above into Codex implementation prompts until all of the following exist:
## Definition of Done
- `SPECIFICATION.md` v1.0,
- `TECHNICAL-DESIGN.md` v1.0,
- completed Phase 0 research reports,
- approved fork/build ADR,
- approved licensing/reuse review.
Accepted story state is genre-neutral, auditable, reconstructable, and no longer depends on ambiguous relative deltas.
## 5. Required Format for the Final Build Plan
---
When rewritten after Phase 0, every production milestone should contain:
# M6 — Branch-Safe Context, Summaries, and Long-Term Story Memory
- objective,
- scope,
- explicit non-scope,
- prerequisite milestones,
- files/components expected to change,
- implementation tasks,
- data/schema changes,
- tests required,
- security/privacy checks,
- acceptance criteria,
- rollback/migration notes,
- documentation updates,
- definition of done.
## Objective
The final document should be suitable for handing directly to Codex one milestone at a time.
Align inherited AI-DnD memory/context behavior with the final authority and history model.
## Scope
- preserve recent active-lineage history selection,
- ensure summaries are anchored to source turn ranges/lineage rather than positional list assumptions,
- preserve branch-scoped memory isolation,
- classify memory authority (accepted vs heuristic/inferred),
- maintain explicit token budgets,
- preserve/extend prompt-context inspection,
- ensure memory/summary failures do not corrupt accepted story state,
- test memory behavior after Undo/Redo/checkpoint/divergence,
- validate realistic-context prompt construction.
## Explicit Non-Scope
- imported document knowledge belongs in M7,
- no remote embeddings/vector DB.
## Tests / Acceptance
- F01-F08,
- E lineage safety,
- no abandoned-future term appears in active prompt after divergence,
- negative control proves eligible memory returns when the relevant lineage is active,
- summary lineage survives head movement correctly,
- prompt inspector identifies included memories/summaries and token costs.
## Definition of Done
Long-running story context is lineage-safe, authority-aware, local, inspectable, and bounded.
---
# M7 — First-Class Imported Knowledge Library
## Objective
Implement the separate local knowledge subsystem required by the specification rather than overloading AI-DnD Story Cards.
## Scope
- `.txt` and `.md` import,
- source validation and local copy/storage,
- Canon / Reference / Inspiration classification,
- enable/disable/delete,
- content hash and provenance,
- heading/paragraph-aware chunking,
- SQLite FTS5 lexical index,
- local Ollama embeddings where semantic retrieval is enabled,
- hybrid retrieval/reranking,
- authority-aware context insertion,
- campaign scoping,
- source/chunk inspector,
- prompt retrieval provenance,
- export/import preservation,
- no automatic URL/image fetch,
- prompt-injection framing as untrusted data.
## Explicit Non-Scope
- PDF/DOCX/EPUB import,
- remote URL ingestion,
- remote vector stores,
- general plugin/tool framework.
## Tests / Acceptance
- G01-G10,
- C05 canon beats reference/inspiration,
- hidden/abandoned story facts cannot leak through lineage-derived knowledge,
- H06-H09 as applicable to rendering/import/archive handling,
- I05 knowledge provenance survives export/import.
## Definition of Done
A campaign can import local Canon/Reference/Inspiration files, retrieve them locally with provenance, and maintain authority boundaries.
---
# M8 — Browser UX Completion for v1 Story Operations
## Objective
Turn the adapted AI-DnD interface into the focused interactive-story workspace defined by `BROWSER-UX-SPEC.md`.
## Scope
- campaign library/setup streamlined for non-RPG stories,
- story transcript and streaming polish,
- one primary natural-language input flow plus story-direction affordance,
- Undo/Redo/Retry/alternate-take controls,
- Save Points UI,
- edit user input/narrator output with safe lineage semantics,
- current scene/state side panel,
- knowledge panel,
- context inspector,
- local Ollama status/model selection,
- no cloud-provider UI,
- reserve microphone/STT affordance without implementing STT.
## Explicit Non-Scope
- no full branch-management UI,
- no image/video/TTS/STT implementation,
- no mobile-native app.
## Tests / Acceptance
- B01-B04,
- D user-facing behavior,
- relevant UX acceptance checks,
- browser state remains consistent after restart/Undo/Redo/checkpoint/failed generation.
## Definition of Done
Normal story creation and play feels like a focused local storyteller rather than an RPG or developer console.
---
# M9 — Export, Backup, Recovery, and Migration Hardening
## Objective
Make campaigns portable and recoverable without losing lineage, state, knowledge, or the active head.
## Scope
- finalize documented campaign bundle format/versioning,
- preserve active branch/head, retained history, retries, checkpoints,
- state events/snapshots,
- prompt/retrieval provenance as selected,
- knowledge source metadata/content/index rebuild information,
- scene/media metadata placeholders,
- safe SQLite backup behavior,
- import validation,
- backward-compatible migration rules,
- corruption/failure handling where practical.
## Tests / Acceptance
- I01-I06,
- L01-L04,
- undone-head round trip,
- branched campaign round trip,
- checkpoint round trip,
- knowledge provenance round trip,
- derived indexes can be rebuilt.
## Definition of Done
A campaign can be safely exported, imported into a clean data directory, and reopened at the exact intended active position with authoritative history/state intact.
---
# M10 — Future Media Extension Hooks Only
## Objective
Preserve the approved future media interfaces without adding a media-generation dependency to v1.
## Scope
- scene snapshots/packets suitable for future providers,
- optional visual character/location/item descriptors,
- provider-neutral media request/job/asset types or reserved schema as justified,
- branch/lineage association for scenes/assets,
- local-only endpoint contract for future providers,
- STT contract: local transcription -> editable draft -> normal submission,
- no core story-engine dependency on media availability.
## Explicit Non-Scope
Do not implement:
- image generation,
- video generation,
- TTS,
- STT,
- ambience/audio generation,
- ComfyUI/FLUX integration.
## Tests / Acceptance
- K01-K04 architecture/media-readiness tests,
- no v1 story flow requires a media service,
- scene data follows active lineage after Undo/restore/divergence.
## Definition of Done
Future media providers can be added through defined local interfaces without redesigning core story authority/history.
---
# M11 — v1 Security, Long-Run, and Release Validation
## Objective
Validate the full product against the release contract after all functional milestones are integrated.
## Scope
- full outbound-network blocked run,
- dependency/runtime audit,
- restrictive browser/CORS/CSP checks,
- failed-model-call recovery,
- long-running 100-turn campaign,
- repeated Undo/Redo/Retry/checkpoint cycles,
- branch/memory/summary leakage checks,
- realistic-context state extraction across selected recommended models,
- fantasy and science-fiction fixtures,
- export/import/recovery tests,
- migration tests,
- documentation and packaging.
## Tests / Acceptance
Release gate in `V1-ACCEPTANCE-TESTS.md`:
- all REQUIRED FOR V1 tests pass,
- approved exceptions documented in ADRs,
- 100-turn test passes,
- offline operation passes,
- branch/memory isolation passes,
- export/import recovery passes,
- both genre fixtures pass.
## Definition of Done
The build meets the v1 black-box acceptance contract and can be packaged as the first production release.
---
## 4. Milestone Dependency Summary
```text
M1 Production/offline foundation
-> M2 Local-only surface reduction
-> M3 Non-destructive history + export head
-> M4 Save Points
-> M5 Narrative state
-> M6 Context/memory
-> M7 Imported knowledge
-> M8 Browser UX completion
-> M9 Export/recovery hardening
-> M10 Future-media hooks
-> M11 v1 validation/release
```
Some implementation work may overlap internally, but milestone acceptance should remain sequential so architectural regressions are discovered early.
## 4. Prompting Rule
When implementation begins, prepare **one Codex prompt per milestone**.
Do not hand the entire plan to Codex as one production task.
Each prompt should contain only:
- milestone objective,
- relevant source documents,
- scope/non-scope,
- specific acceptance tests,
- stop condition and required report/diff.
No implementation prompt is included in the current planning revision.
+9 -4
View File
@@ -1,9 +1,14 @@
# Codex Handoff Note
For the initial Phase 0B validation round, use:
**Status:** Phase 0B handoff complete; historical only.
`PHASE-0B-CODEX-BRIEF.md`
The earlier Phase 0B execution prompts are retained for audit/history:
This is the current concise execution brief.
- `PHASE-0B-CODEX-BRIEF.md`
- `PHASE-0B-CODEX-HANDOFF.md`
`PHASE-0B-CODEX-HANDOFF.md` is retained as a more detailed reference/appendix and should not be treated as the primary execution prompt unless specifically needed.
Do **not** execute either as the next production task.
Phase 0B has been completed and the resulting recommendation reviewed. AI-DnD is now the selected production base, and the planning package has been revised accordingly.
The next production Codex prompt has **not** been prepared. It should be created only after the current planning-package revision is reviewed and approved. When authorized, the first implementation prompt should be derived from Production Milestone M1 in `BUILD-MILESTONES.md`, not from the Phase 0B validation briefs.
+27 -25
View File
@@ -1,6 +1,6 @@
# Adventure Storyteller — Context and Memory
**Status:** Draft v0.1
**Status:** v1.0 — aligned to Phase 0B findings
**Purpose:** Define what information is supplied to the narrator on each turn, how long-term memory works, and how authority, lineage, retrieval, summaries, and imported knowledge interact.
## 1. Design Goal
@@ -420,7 +420,7 @@ Reason:
- lexical retrieval is transparent and precise for names/terms,
- semantic retrieval is useful for conceptually related old events.
Phase 0B should determine what the selected base already supports.
Phase 0B confirmed useful local semantic memory in AI-DnD and deterministic lexical lore in ai-adventure. The selected production direction is hybrid local retrieval, implemented incrementally with a lexical path that remains usable when semantic embeddings are unavailable.
## 17. Embeddings
@@ -1083,25 +1083,25 @@ If the narrator makes an unexpected choice, the system should support questions
This is why context provenance is a first-class requirement.
## 57. Phase 0B Validation Questions
## 57. Phase 0B Findings Applied
Codex should answer:
The selected AI-DnD base was exercised with real local Ollama embeddings and demonstrated useful lineage behavior:
1. How does AI-DnD currently rank and retrieve memories?
2. Are AI-DnD memories branch-aware?
3. Can abandoned-branch memories leak into active context?
4. How are Story Cards selected and injected?
5. Can Story Cards be generalized into Canon / Reference / Inspiration classes?
6. What exact embedding provider does local AI-DnD use with Ollama?
7. Can memory retrieval work fully offline?
8. What context components are visible in AI-DnD's Insights view?
9. How does Open Dungeon decide when to summarize old history?
10. Can ai-adventure's FTS lore layer be retained as a deterministic lexical retrieval component?
11. How difficult would hybrid lexical + semantic retrieval be in the selected base?
12. Can current context budgeting preserve hard canon under pressure?
13. Are summaries tied explicitly to source lineage?
14. Can memory extraction failure occur without blocking a successful turn?
15. Can prompt/context snapshots be retained without excessive database growth?
- branch-scoped memory retrieval worked,
- a memory from a later/abandoned depth was excluded after moving the active head backward,
- the same memory became eligible again after Redo/return to the applicable lineage,
- switching to a different branch prevented abandoned-branch terms from appearing in the assembled prompt,
- the context/Insights path exposes labeled prompt sections and token costs.
Planning consequences:
1. Preserve AI-DnD's common lineage filtering/chokepoint rather than replacing memory from scratch.
2. Make summaries explicitly lineage/turn-range anchored; never rely on a positional message watermark.
3. Keep accepted story memory distinct from heuristic/inferred memory.
4. Imported knowledge remains a separate subsystem; do not promote Story Cards into the knowledge store merely because they are prompt-injection primitives.
5. Use local Ollama embeddings for semantic story memory where enabled; retain a lexical/deterministic path for imported knowledge.
6. Context/state extraction must be tested at realistic prompt length because Phase 0B showed model protocol adherence can degrade under full application context.
7. Derived memory/summary failure must not corrupt or roll back an otherwise valid authoritative story commit.
## 58. Acceptance Criteria
@@ -1117,6 +1117,8 @@ The final implementation must satisfy:
- retrieval works locally,
- no remote embeddings/search are required,
- abandoned branch memories do not leak,
- moving the active head backward excludes memories derived after that head,
- Redo/returning to the valid lineage can make those memories eligible again,
- context remains bounded,
- output space is reserved,
- prompt composition is inspectable,
@@ -1124,7 +1126,7 @@ The final implementation must satisfy:
- summaries are lineage-safe,
- memory failures do not corrupt authoritative story state.
## 59. Current Recommendation
## 59. Selected Context / Memory Design
Use a layered, authority-aware context builder:
@@ -1141,13 +1143,13 @@ Use a layered, authority-aware context builder:
v
----------------
+
Story Summary
Lineage-Anchored Summary
+
Relevant Story Memories
+
Relevant Local Knowledge
+
Recent History
Recent Active-Lineage Turns
+
Current Input
|
@@ -1155,14 +1157,14 @@ Use a layered, authority-aware context builder:
OLLAMA
```
Retrieval should be:
Retrieval must be:
```text
local
+ lineage-aware
+ lineage-aware where derived from story history
+ provenance-preserving
+ authority-aware
+ token-bounded
```
The application should treat context construction as a deterministic subsystem that can be inspected and tested independently of prose generation.
The application treats context construction as a deterministic, independently testable subsystem. AI-DnD's lineage-aware memory implementation is the starting point; the project's own authority and imported-knowledge rules define the target behavior.
+61 -42
View File
@@ -1,11 +1,11 @@
# Adventure Storyteller — Data Model
**Status:** Draft v0.1
**Status:** v1.0 conceptual model aligned to Phase 0B decisions
**Purpose:** Define the persistent information the application must represent, independent of the final fork or database implementation.
## 1. Design Goals
The data model must support persistent interactive stories, complete authoritative history, non-destructive branching, checkpoints and rollback, genre-independent narrative state, long-term memory, imported local knowledge, prompt/context provenance, future image/video generation, export/restore, and local-only operation.
The data model must support persistent interactive stories, complete authoritative history, non-destructive branching, checkpoints and rollback, genre-independent narrative state, long-term memory, imported local knowledge, prompt/context provenance, future image/video/audio/TTS/STT generation, export/restore, and local-only operation.
The same core schema should work for fantasy, science fiction, mystery, horror, historical fiction, westerns, and other narrative genres.
@@ -62,6 +62,7 @@ campaign:
created_at: timestamp
updated_at: timestamp
active_branch_id: uuid
active_head_turn_id: optional uuid
status: active | archived
story_profile:
@@ -95,14 +96,18 @@ branch:
created_at: timestamp
created_from_branch_id: optional uuid
fork_turn_id: optional uuid
head_turn_id: optional uuid
tip_turn_id: optional uuid
disposition: active | retained | disposable
status: active | archived
```
Rules:
- branches may share ancestral turns,
- shared history should not be duplicated unnecessarily,
- creating a branch must not modify the source branch.
- creating a branch must not modify the source branch,
- the campaign active head may sit behind the retained branch tip after Undo,
- Redo moves the active head forward while the prior continuation remains selected,
- a new write below the retained tip creates a new continuation and leaves the old future retained/disposable.
Detailed behavior will be defined separately in `STORY-BRANCH-SEMANTICS.md`.
@@ -162,7 +167,7 @@ take:
selected: boolean
```
Whether `Take` becomes a separate table or sibling turn nodes will be decided after Phase 0B.
Physical representation remains implementation-specific. The selected AI-DnD base already models alternate takes within its lineage machinery; production should retain that approach if it satisfies the required Retry/select/retention semantics without forcing a separate table.
## 8. Checkpoint
@@ -176,7 +181,7 @@ checkpoint:
created_at: timestamp
```
A checkpoint is a named pointer to a recoverable story position. It should normally remain tied to the turn where it was created.
A checkpoint is a named pointer to a recoverable story position. It should normally remain tied to the turn where it was created. Restoring it moves the campaign active head; it does not delete later retained history. A new branch is created on the first divergent write after restore, not merely because the checkpoint was opened.
## 9. Narrative Entity
@@ -386,7 +391,7 @@ Excellent auditability, but requires replay.
### C. Hybrid
Validated events plus periodic/current snapshots.
**Current preference: Hybrid**, pending Phase 0B.
**Selected for v1: Hybrid.** Store validated authoritative events plus efficient state snapshots/cache for normal reads and restore.
## 18. State Change Event
@@ -404,18 +409,21 @@ state_event:
Potential event types:
- entity_created,
- entity_updated,
- set_entity_status,
- set_entity_attribute,
- fact_added,
- fact_invalidated,
- relationship_added,
- relationship_ended,
- thread_opened,
- thread_resolved,
- location_changed,
- possession_changed,
- scene_changed.
- current_location_set,
- possession_set,
- scene_set.
Events must be validated before commit.
Event semantics must be explicit and typed. Prefer unambiguous absolute assignments for mutable values. If incremental operations are ever needed, encode the operation explicitly (for example `increment_value`) rather than relying on one numeric field whose interpretation is implicit.
Events must be schema-validated, semantically checked where deterministic rules exist, and accepted by the application before commit.
## 19. State Proposal
@@ -431,7 +439,7 @@ state_proposal:
validation_status: accepted | partially_accepted | rejected | repair_required
```
The model must never write directly to authoritative state tables.
The model must never write directly to authoritative state tables. The production protocol must not depend on AI-DnD-style ambiguous relative deltas; see ADR 010.
## 20. Scene Snapshot
@@ -598,7 +606,7 @@ media_job:
scene_id: optional uuid
source_turn_start_id: optional uuid
source_turn_end_id: optional uuid
type: image | video | audio
type: image | video | audio | tts | stt
provider: string
model: string
status: queued | running | completed | failed
@@ -615,7 +623,7 @@ media_asset:
campaign_id: uuid
media_job_id: optional uuid
scene_id: optional uuid
type: image | video | audio
type: image | video | audio | tts | stt
file_path: string
metadata: object
created_at: timestamp
@@ -645,7 +653,7 @@ A campaign export should be capable of preserving:
- media metadata,
- media files if selected.
Exact format remains open. A ZIP containing a database plus manifest is a strong candidate.
The physical container format remains an implementation choice, but the export must preserve the exact active branch **and active head position**, even when the head is behind a retained tip after Undo. A ZIP containing a database plus manifest remains a strong candidate.
## 30. Deletion vs Archival
@@ -692,54 +700,65 @@ Potential provenance:
- imported inspiration,
- derived inference.
## 33. Open Questions for Phase 0B
## 33. Phase 0B Decisions Applied
1. Does AI-DnD already model alternate takes separately from branch nodes in a reusable way?
2. Can its state snapshots hold generic narrative JSON without major redesign?
3. Is its branch lineage compatible with immutable turns?
4. Should checkpoints be branch-independent pointers to turns?
5. Should memories be physically branch-scoped or lineage-filtered at query time?
6. Should knowledge sources be reusable across campaigns in v1?
7. Should media tables physically exist in v1 or only interfaces/types?
8. How should manual edits to canon/state be versioned?
9. Which state needs full historical reconstruction versus only current-state storage?
10. Can ai-adventure's event/replay discipline be adopted without overcomplicating AI-DnD?
Phase 0B resolved the foundational data-model questions:
- AI-DnD story lineage/alternate-take machinery is the production starting point.
- The active head is distinct from retained tip history.
- Undo/Redo use head movement; destructive deletion is not part of ordinary history operations.
- Named checkpoints are durable pointers to recoverable head positions.
- State uses a hybrid event + snapshot/cache model.
- State proposals use explicit typed operations with unambiguous value semantics.
- Memories/summaries must be lineage-filtered or lineage-anchored.
- Imported knowledge requires separate source/chunk/index tables rather than overloading Story Cards.
- Scene/media records remain optional derived extensions and must carry source lineage.
- Export/import must preserve active head position as well as the retained history graph.
## 34. Acceptance Criteria
The final v1 data model must support all of these without destructive hacks:
- close/restart/resume exact story,
- Undo/Redo without deleting accepted turns,
- active head behind retained tip,
- branch from an earlier turn while retaining the original future,
- mark abandoned futures/takes retained/disposable,
- create and restore named checkpoints,
- know current characters/locations/relationships/story threads,
- reconstruct earlier authoritative state,
- validate typed state proposals before committing events,
- retrieve old events outside the active context window,
- identify which imported passages informed a turn,
- reconstruct what was sent to Ollama,
- export/import an undone campaign without silently redoing it,
- run fantasy and science-fiction campaigns without schema changes,
- attach future image/video assets to scenes or turn ranges.
- attach future image/video/audio/TTS/STT metadata to scenes or turn ranges without making media authoritative.
## 35. Current Recommendation
The target conceptual model should be:
## 35. Selected Conceptual Model
```text
Immutable Turn Graph
Retained Turn/Take Lineage
|
+--> validated state events
+--> Active branch + movable active head
|
+--> state snapshot/cache
+--> Validated typed state events
| |
| +--> state snapshot/cache
|
+--> lineage-safe summaries/memories
|
+--> prompt/retrieval provenance
|
+--> scene snapshot
|
+--> prompt/retrieval provenance
+--> future optional media records
Separate campaign knowledge subsystem
+--> sources
+--> chunks
+--> FTS/local embeddings
+--> authority/provenance
```
This combines the strongest observed concepts from:
- AI-DnD's story tree and snapshots,
- ai-adventure's append-only event/replay discipline,
- Open Dungeon's simple story UX and visual continuity.
The physical implementation remains provisional until Phase 0B validates the preferred production base.
This combines AI-DnD's retained story lineage and snapshot infrastructure with ai-adventure-style explicit event/commit discipline while preserving the project's own specification as the authority.
+5 -2
View File
@@ -4,11 +4,11 @@
## Decision
v1 will target local Ollama inference.
v1 will target Ollama inference running on user-controlled local infrastructure. The default endpoint is same-host loopback, but v1 must also support an explicitly configured Ollama instance on a trusted local-area network.
## Context
The intended deployment already has a local Ollama inference engine. The project prioritizes local control, privacy, and predictable integration.
The intended production deployment can eventually run the storyteller and Ollama on one machine, but development and testing may place Ollama on a separate machine on the user's LAN. The project prioritizes local control, privacy, predictable integration, and no dependency on Internet/cloud inference.
## Alternatives Considered
@@ -26,4 +26,7 @@ Ollama is already available locally, provides a simple local API, supports both
- candidate forks supporting multiple cloud providers should be simplified or hardened,
- candidate projects using another local API need an adapter,
- the storyteller must support both same-host Ollama and an explicitly configured trusted-LAN Ollama endpoint,
- LAN inference does not imply LAN exposure of the storyteller UI/API; the storyteller should still bind to loopback by default,
- arbitrary public Internet/cloud model endpoints remain outside normal v1 configuration,
- future backend abstraction may be added, but v1 should not be delayed to support it.
@@ -1,14 +1,14 @@
# ADR 004 — Local-Only Production Default
**Status:** Accepted
**Status:** Accepted; Phase 0B hardening requirements identified
## Decision
The production application will be designed to operate without Internet access.
The production application will operate without Internet access for ordinary v1 story use.
## Context
The project requires control over story data, imported material, prompts, and model outputs, with no unintended disclosure to outside services.
The project requires control over story data, imported material, prompts, model outputs, memories, embeddings, and future generated media, with no unintended disclosure to outside services.
## Alternatives Considered
@@ -18,18 +18,34 @@ The project requires control over story data, imported material, prompts, and mo
## Reason
Local-only operation best matches the privacy and control requirements.
Local-only operation best matches the privacy and control requirements. For this project, "local-only" means operation on user-controlled local infrastructure without requiring Internet or cloud services; it does not require every component to run on the same physical machine.
## Phase 0B Evidence
The selected AI-DnD base does **not** satisfy this requirement unchanged:
- `tiktoken` attempted a first-use download of its encoding data,
- the browser requested Google Fonts at runtime,
- hosted/cloud/auth/analytics/Postgres/provider paths remain present upstream,
- inherited endpoint guarding is oriented toward hosted deployment rather than enforcing the project's approved-local-infrastructure model boundary.
These are bounded production-hardening tasks rather than reasons to reject the fork.
## Consequences
The production application should avoid:
The production application must avoid or remove:
- telemetry,
- analytics,
- cloud inference,
- hosted authentication/accounts,
- remote vector stores,
- automatic web retrieval,
- runtime CDN dependencies,
- remote fonts/assets.
- remote fonts/assets,
- first-use runtime tokenizer/model-support downloads,
- arbitrary remote model-provider configuration in normal v1 UI.
All inherited network behavior from a fork must be inventoried during Phase 0.
Production packaging must contain all runtime assets required for ordinary story use after the user has installed the intended local Ollama models.
The storyteller application should bind to loopback by default. Ollama should default to same-host loopback but may be explicitly configured to an approved trusted-LAN endpoint for v1. This LAN inference path does not authorize LAN exposure of the storyteller UI/API. Arbitrary public/Internet inference endpoints remain prohibited in normal v1 configuration.
@@ -1,29 +1,44 @@
# ADR 005 — Branch-Preserving Story History
**Status:** Accepted in principle; implementation pending Phase 0
**Status:** Accepted; implementation direction validated in Phase 0B
## Decision
Returning to an earlier story point should preserve abandoned future history as another branch rather than destructively erasing it.
Returning to an earlier story point preserves abandoned future history rather than destructively erasing it.
The selected implementation model uses a **movable active head over retained lineage**:
- Undo moves the head backward,
- Redo moves it forward along the retained continuation,
- accepted turns are not deleted by ordinary Undo,
- a new write after moving backward creates a new continuation on first divergent write,
- the displaced future remains retained/disposable,
- normal UI presents Undo/Redo/Retry/Save Point rather than branch-management concepts.
## Context
The user must be able to recover from unwanted story developments and explore alternatives while retaining prior work.
Phase 0B found that AI-DnD's shipped Undo was destructive even though its tree/take infrastructure was otherwise strong. A disposable spike demonstrated non-destructive head-cursor Undo/Redo using existing lineage chokepoints and branch creation while preserving branch-scoped memory isolation.
## Alternatives Considered
- destructive undo,
- overwrite-in-place editing,
- complete copy of campaigns for every retry,
- branch-preserving turn graph.
- branch-preserving turn graph with movable active head.
## Reason
A branch-preserving history provides recovery, experimentation, and auditability without unnecessary campaign duplication.
The selected model provides recovery, experimentation, auditability, and Redo without unnecessary campaign duplication or a complicated user-facing branch workflow.
## Consequences
- turn identity/parentage must be first-class,
- state restore must be branch-aware,
- retry/edit semantics must be explicitly defined,
- the selected candidate repository must either support this or be adaptable to it.
- turn identity/parentage remains first-class,
- active head and retained tip are distinct concepts,
- state restore is lineage-aware,
- retry/edit/add-take must honor fork-if-behind-head behavior,
- summaries/memories must respect active lineage/head,
- named checkpoints point to durable story positions,
- abandoned history is marked disposable but is not automatically cleaned up in v1,
- export/import must preserve the active head coordinate as well as the retained history graph.
@@ -1,14 +1,14 @@
# ADR 008 — Complete Phase 0 Before Detailed Build Planning
**Status:** Accepted
**Status:** Accepted and completed
## Decision
The project will complete repository research, validation, architecture selection, and critical prototypes before writing the detailed production implementation milestone plan.
The project completed repository research, validation, architecture selection, and critical prototypes before finalizing the detailed production implementation milestone plan.
## Context
Multiple candidate open-source projects already implement overlapping parts of the desired system. The correct build sequence depends heavily on which codebase is selected.
Multiple candidate open-source projects implemented overlapping parts of the desired system. The correct build sequence depended on which codebase survived local validation.
## Alternatives Considered
@@ -18,10 +18,15 @@ Multiple candidate open-source projects already implement overlapping parts of t
## Reason
The third approach reduces speculative planning and prevents large amounts of rework.
The bounded Phase 0 prevented false assumptions from becoming production architecture. In particular, runtime testing discovered destructive AI-DnD Undo, an offline tokenizer fetch, remote fonts, Open Dungeon's lack of tests and destructive summary/history assumptions, and the relative-delta state-protocol failure mode.
## Completion Record
Phase 0 selected AI-DnD as the production base, validated the non-destructive head-cursor approach, selected explicit typed narrative-state events, established local-only hardening requirements, and produced the production `BUILD-MILESTONES.md`.
## Consequences
- `BUILD-MILESTONES.md` remains intentionally high level during Phase 0,
- production coding should not begin unless explicitly authorized,
- Phase 0 ends with the final build plan.
- the placeholder build plan has been replaced by a production milestone sequence,
- `SPECIFICATION.md` and `TECHNICAL-DESIGN.md` are the v1.0 planning baseline,
- production coding still requires explicit authorization and a milestone-specific prompt,
- the current review intentionally stops before preparing that prompt.
@@ -0,0 +1,93 @@
# ADR 009 — AI-DnD Is the Production Base
**Status:** Accepted
**Date:** 2026-09-01
## Decision
Use AI-DnD as the production fork/base, pinned initially to upstream commit:
```text
d72f7c1bda0f34fccd84afb7a25c34eb01c901de
```
AI-DnD is the ownership center for the production codebase.
Other candidate projects remain implementation references only unless a later explicit decision authorizes compatible code reuse.
## Context
Phase 0A favored AI-DnD because it appeared to contain the most difficult correctness infrastructure. Phase 0B then cloned, built, tested, and exercised the three finalists with local Ollama and targeted experiments.
Phase 0B corrected one major assumption: AI-DnD's shipped Undo hard-deletes history and there is no Redo. A disposable follow-up spike demonstrated that the architecture can support non-destructive head-cursor Undo/Redo with a bounded change while retaining alternate history and branch-scoped memory isolation.
AI-DnD also demonstrated:
- browser UI and FastAPI backend,
- SQLite persistence,
- story-tree/lineage machinery,
- alternate takes,
- branch switching,
- per-node state snapshots,
- local Ollama operation,
- branch-scoped memory with local embeddings,
- prompt/context inspection,
- streaming,
- export/import foundation,
- substantial automated regression coverage,
- ability to run without RPG scenario state.
Open Dungeon would require a foundational history/persistence/summary rewrite with no existing automated test foundation. ai-adventure has the strongest state/privacy core but would require building most of the browser product around it.
## Alternatives Considered
- fork Open Dungeon,
- use ai-adventure as the core and build browser/API layers,
- build a new application shell,
- continue repository-selection research.
## Reason
AI-DnD minimizes the amount of high-risk correctness infrastructure that must be invented while providing the browser/service/test foundation the target product needs.
The remaining work is substantial but is more bounded and testable than the alternatives.
## Consequences
Retain or adapt from AI-DnD:
- React/Vite browser application,
- FastAPI service boundary,
- SQLite and migration foundation,
- story tree and lineage queries,
- alternate takes,
- state snapshot pattern,
- local Ollama integration,
- Memory Bank concepts and branch scoping,
- Insights/context snapshots,
- SSE streaming,
- export/import framework,
- relevant automated tests.
Remove or replace:
- hosted/multi-user/auth/demo functionality,
- analytics,
- Postgres/Neon/Render paths,
- cloud model providers,
- QuickJS/campaign scripting,
- AI-Dungeon compatibility not needed by the product,
- RPG-specific presentation and relative-delta state mechanics,
- runtime remote fonts/assets,
- first-use remote tokenizer dependency.
Reimplement selected patterns from references:
- ai-adventure: typed state events, validation/commit discipline, checkpoints/head movement, replay/privacy patterns,
- Open Dungeon: focused story UX and future local-media interaction ideas,
- Chronicler/IFF: authority/trust concepts,
- Gamentic: provider-neutral optional media boundary.
## Non-Decision
This ADR does not authorize production coding by itself. Implementation begins only after the revised planning package is approved and a milestone-specific prompt is prepared.
@@ -0,0 +1,100 @@
# ADR 010 — Use Explicit Typed Narrative-State Events
**Status:** Accepted
**Date:** 2026-09-01
## Decision
The production narrative-state engine will use an explicit, typed event/proposal vocabulary with unambiguous values, preferably absolute assignments for mutable values, rather than AI-DnD's relative-delta world-state protocol.
The application remains authoritative:
```text
Narration/model output
|
v
Typed state proposal
|
v
Schema + semantic validation
|
v
Accepted state events
|
+--> state snapshot/cache
+--> provenance/audit record
```
## Context
Phase 0B exercised AI-DnD's existing world-state referee against local models under realistic application context.
The protocol requested relative deltas, but the model sometimes emitted absolute values. An emitted value could be syntactically legal as a delta while semantically representing the wrong operation. The referee could therefore accept a valid-looking proposal that caused authoritative state to diverge from the narration.
The failure class is architectural: validation cannot reliably distinguish a legitimate large delta from an absolute value mistakenly expressed in a delta field.
ai-adventure uses a more explicit typed-event approach in which the requested operation and value semantics are directly represented.
## Alternatives Considered
- retain AI-DnD's relative-delta protocol,
- infer whether a number is absolute or relative,
- rely on a larger model to follow delta instructions,
- use explicit typed events with clear value semantics.
## Reason
Explicit event semantics remove the relative-versus-absolute ambiguity and make proposals easier to validate, test, audit, and replay.
This does **not** make model proposals infallible. A model can still propose an incorrect absolute value or incorrect event. The application must still perform:
- schema validation,
- allowlisted event-type validation,
- referential-integrity checks,
- domain/consistency checks where deterministic rules exist,
- provenance recording,
- safe failure/repair behavior.
## Event Style
Illustrative event forms:
```yaml
- type: set_entity_status
entity_id: mara
value: injured
- type: set_current_location
entity_id: aldric
location_id: tavern-cellar
- type: set_possession
item_id: silver-key
owner_id: aldric
- type: add_fact
subject_id: mara
predicate: knows
object_id: silver-key-origin
authority: accepted_story
- type: open_story_thread
thread_id: investigate-cellar-door
```
For numeric or bounded values introduced by optional future modules, prefer explicit operations such as:
```text
set_value
increment_value
```
rather than one ambiguous numeric field whose interpretation depends on prompt instructions.
## Consequences
- AI-DnD's existing RPG state/referee implementation is a source to replace/generalize, not preserve as the production narrative-state protocol.
- Narrative state should remain genre-neutral: entities, facts, relationships, locations, conditions, possessions, threads, scenes, and similar generic concepts.
- Accepted events and resulting state must be committed atomically with the accepted story turn where practical.
- State snapshots/cache should make normal reads and restore fast; event history preserves audit/reconstruction value.
- Tests must exercise state extraction at realistic context length against realistic local models, not only isolated prompts.
+54 -35
View File
@@ -1,6 +1,6 @@
# Adventure Storyteller — Imported Knowledge Design
**Status:** Draft v0.1
**Status:** v1.0 — selected implementation direction after Phase 0B
**Purpose:** Define how local user-supplied knowledge is imported, classified, indexed, retrieved, inspected, disabled, deleted, and kept separate from executable instructions.
## 1. Design Goal
@@ -949,16 +949,11 @@ For v1, plain metadata is sufficient.
## 65. Campaign-Wide vs Shared Library
Open question:
V1 decision:
Should a source belong to:
- one campaign only,
or
- reusable global library?
> **Knowledge sources are campaign-scoped.**
Current recommendation for v1:
> Campaign-scoped knowledge first.
A reusable global/shareable library may be considered later, but it is not part of the v1 storage or authority model.
Reasons:
- simpler privacy model,
@@ -1082,31 +1077,52 @@ These should be used to test:
- disable/delete,
- export/import.
## 73. Candidate Evaluation Questions
## 73. Phase 0B Integration Decision
Codex should answer for AI-DnD:
The production base is AI-DnD, but its Story Cards are **not** the production imported-knowledge store.
1. Can Story Cards map cleanly to Canon/Reference/Inspiration?
2. Are cards campaign-scoped?
3. How are cards chunked/retrieved?
4. Can remote/provider dependencies be removed?
5. Can provenance be shown per retrieved card/chunk?
6. Can hidden canon be represented?
7. Can local embeddings work fully offline?
Phase 0B found that Story Cards do not carry the lineage/provenance structure required for a general imported-knowledge system and do not directly provide the required source classification, chunking, local FTS, semantic indexing, source lifecycle, and inspection model.
For Open Dungeon:
Implement imported knowledge as separate first-class tables/services.
1. Does it currently support imported documents beyond built-in story data?
2. What new storage/index layer would be required?
3. Can its local image/story architecture remain separate from knowledge retrieval?
4. What is the simplest local FTS/embedding integration?
Recommended conceptual records:
For ai-adventure:
```text
knowledge_source
knowledge_source_version (optional if v1 keeps simpler version metadata)
knowledge_chunk
knowledge_embedding / vector representation
knowledge_retrieval_record
```
1. Can the FTS lore system represent classifications?
2. Can lore entries retain source provenance?
3. How difficult is semantic retrieval addition?
4. Is campaign isolation already strong?
Every source/chunk must retain enough metadata for:
- campaign scope,
- Canon / Reference / Inspiration class,
- source provenance/hash,
- enable/disable/delete,
- chunk identity,
- lexical/semantic retrieval,
- prompt inspection,
- export/import.
Normal imported files are campaign-level source material and need not inherit story-branch lineage merely because the story branches. If a future knowledge source or chunk is **derived from story history**, it must carry source turn/lineage coordinates so abandoned-path material cannot leak into active context.
Story Cards may remain as an inherited authored-rule/lore primitive during migration if useful, but they must not become an alternate untracked path around the new knowledge authority/provenance rules.
### Retrieval implementation direction
Use:
```text
SQLite FTS5 lexical retrieval
+
local Ollama semantic embeddings where enabled
+
authority/relevance reranking
```
Lexical retrieval remains available even if embeddings fail or are disabled.
## 74. V1 Acceptance Criteria
@@ -1123,16 +1139,19 @@ The final v1 must support:
- bounded retrieval,
- canon precedence,
- no automatic URL fetch,
- no remote image fetch,
- no script execution,
- prompt-injection framing as untrusted data,
- export/import preservation.
Strongly preferred:
Strongly preferred and planned for v1:
- lexical + semantic hybrid retrieval,
- hidden/narrator-only canon,
- source inspector,
- prompt retrieval inspector.
## 75. Current Recommendation
## 75. Selected Implementation
Implement imported knowledge as a first-class local subsystem:
@@ -1140,17 +1159,17 @@ Implement imported knowledge as a first-class local subsystem:
Local File
|
v
Validate
Validate / Copy Locally / Hash
|
v
Classify
|
v
Chunk
Chunk + Provenance
|
+--> SQLite FTS
+--> SQLite FTS5
|
+--> Local Embeddings
+--> Local Ollama Embeddings
|
v
Hybrid Retrieval
@@ -1162,7 +1181,7 @@ Authority Filter / Rerank
Bounded Prompt Context
```
And preserve this separation:
Preserve this separation:
```text
Story authority
+28 -52
View File
@@ -1,6 +1,6 @@
# Adventure Storyteller — Media Extension Contract
**Status:** Draft v0.1
**Status:** v1.0 architecture contract — providers remain future/optional
**Purpose:** Define the stable interfaces and data boundaries needed to add local image, video, and audio generation later without coupling media generation to the core story engine.
## 1. Design Goal
@@ -1287,13 +1287,9 @@ Open Dungeon is especially relevant for:
- character visual continuity,
- image workflow UX.
During Phase 0B, inspect:
- how scene prompts are built,
- how images are attached to story,
- provider coupling,
- whether media can be separated from destructive history model.
Phase 0B confirmed Open Dungeon should remain a media/UX reference rather than the production base. Study its local image worker protocol, scene/image attachment, and character visual-continuity ideas during the future media implementation stage if useful.
Do not copy its persistence limitations into the core story architecture.
Do not copy its linear/destructive persistence assumptions into the core story architecture.
## 80. Gamentic Reuse
@@ -1313,23 +1309,18 @@ Corvus may be useful for:
Again, use concepts selectively.
## 82. V1 Physical Schema Decision
## 82. V1 Physical Schema Direction
Open question:
The v1 requirement is architectural compatibility, not media generation.
Should media tables physically exist in v1?
Required now:
- scene snapshots,
- stable optional visual profiles,
- provider-neutral request/result types or equivalent interface contract.
Recommended:
A physical media job/asset table may be introduced in the dedicated future-media-hooks milestone if it is inexpensive and useful for schema stability. Its physical presence is an implementation detail, not a prerequisite for story functionality.
> Include minimal media-ready schema/interfaces if inexpensive, but do not build provider implementation solely to justify them.
Minimum useful v1 fields:
- scene snapshot,
- stable visual profiles,
- media provider interface/types,
- optional media asset table.
Phase 0B should determine cost.
Do not implement a provider merely to justify a table.
## 83. V1 Required Media Readiness
@@ -1383,20 +1374,14 @@ Pass if:
- abandoned-branch events are not included,
- media generation does not mutate story.
## 87. Phase 0B Validation Questions
## 87. Phase 0B Findings Applied
Codex should answer:
1. How does Open Dungeon attach generated images to story messages/scenes?
2. Is image generation coupled to linear/destructive message history?
3. Can its visual character continuity data be reused independently?
4. What provider assumptions are hardcoded?
5. Can ComfyUI/local provider calls run fully offline?
6. Does AI-DnD already have scene-like structured state suitable for media extraction?
7. Can scene snapshots be added without RPG schema coupling?
8. What parts of Gamentic's provider abstraction are worth reimplementing?
9. Should media asset tables exist in v1 or be deferred?
10. Can media generation remain entirely optional without special-casing core story logic?
- Open Dungeon has useful local image-generation and visual-continuity concepts, but they are not a reason to use it as the production fork.
- The production base has no required media subsystem today; this is acceptable for v1.
- Media must remain derived from accepted scene/story state and branch-aware.
- Provider portability for Open Dungeon's image worker is deferred until image generation is actually scheduled.
- Scene snapshots and visual continuity fields are sufficient near-term architecture commitments.
- TTS/STT/video remain future providers behind the same local optional boundary.
## 88. Acceptance Criteria for Architecture
@@ -1410,11 +1395,11 @@ The architecture passes if:
- provider-specific syntax stays outside Story Engine,
- local providers can be substituted,
- future image/video/audio/TTS types fit the output job/asset model,
- future STT fits the same provider architecture while feeding editable draft input rather than story state,
- future STT fits the provider architecture while feeding editable draft input rather than story state,
- generated media never automatically becomes canon,
- abandoned-history media remains recoverable but inactive.
## 89. Current Recommendation
## 89. Selected Contract
Use this conceptual contract:
@@ -1422,25 +1407,16 @@ Use this conceptual contract:
Authoritative Story
|
v
Scene Snapshot
Scene Snapshot / Scene Packet
|
v
Scene Packet
Optional Media Coordinator
|
v
Media Coordinator
|
+--> Local Image Provider
+--> Local Video Provider
+--> Local Audio Provider
+--> Local TTS Provider
|
v
Media Asset + Provenance
+--> Image Provider
+--> Video Provider
+--> Audio Provider
+--> TTS Provider
+--> STT Provider (draft input path)
```
The media subsystem should depend on the story engine.
The story engine should not depend on the media subsystem.
That one-way dependency is the most important architectural requirement in this document.
No production media provider is required for v1.
+1 -1
View File
@@ -1,6 +1,6 @@
# Phase 0B — Codex Initial Validation Brief
**Status:** Ready for execution
**Status:** COMPLETE / HISTORICAL — do not execute as a production prompt
**Purpose:** Run a focused first round of local validation on the three finalist repositories and return a recommendation based on what was actually learned.
## 1. Goal
+1 -1
View File
@@ -1,6 +1,6 @@
# Phase 0B — Codex Local Validation Handoff
**Status:** Ready for execution
**Status:** COMPLETE / HISTORICAL — do not execute as a production prompt
**Purpose:** Validate the Phase 0A recommendation using local builds, tests, offline runtime observation, and tightly scoped experiments.
**Stop rule:** Do not begin production implementation.
+502
View File
@@ -0,0 +1,502 @@
> **Planning review note (2026-09-01):** This is the coding agent's Phase 0B evidence report. It is retained substantially as delivered. Some recommendations/open questions in the report were superseded during planning review: ai-adventure is an implementation reference rather than the project specification; no automatic abandoned-history cleanup is required in v1; Open Dungeon media portability is deferred; and the architecture decision is closed in ADR 009/010 and `TECHNICAL-DESIGN.md`.
# Phase 0B — Local Validation Findings and Recommendation
**Date:** 2026-09-01
**Status:** Complete, and updated after the follow-up undo/redo spike.
Stop condition reached; no production work started.
**Method:** Clean clones, pinned SHAs, documented installs, full test runs, real
local Ollama inference, and targeted disposable experiments.
> **Update — the recommended spike was run and it passed.** Section E previously
> recommended proving non-destructive Undo/Redo in AI-DnD before committing to
> the fork. That spike is now complete: 3 files, +130/-31 lines, undo deletes
> zero rows, redo round-trips exactly, writing below a moved-back head forks and
> preserves the abandoned line, branch-scoped memory isolation survives, and the
> suite goes 627/632 with all 5 failures asserting the deleted-row behavior that
> was deliberately replaced. Section E has been rewritten accordingly, and the
> open questions it resolved are marked in section F. Full detail:
> `reports/PHASE-0B-UNDO-SPIKE.md`.
>
> **Update 2 — four follow-up checks run.** The world-state referee was exercised
> against local models for the first time, and export/import, story-card lineage
> safety and Postgres removability were settled. One new defect found (a
> round-trip silently undoes an undo) and one design conclusion that changes what
> we should build (§H). Detail: `reports/PHASE-0B-FOLLOWUP-CHECKS.md`.
Pinned commits (cloned 2026-09-01):
| Repo | SHA | Last commit |
|---|---|---|
| AI-DnD | `d72f7c1bda0f34fccd84afb7a25c34eb01c901de` | 2026-08-31 |
| Open Dungeon | `b0a79f96bf852be7b4e53908dff6a7f7c179da23` | 2026-07-06 |
| ai-adventure | `873ea9180d5b611576cddb155921fc16a17ae88b` | 2026-07-21 |
Inference used for every live test: Ollama in Docker, `qwen2.5:0.5b` and
`qwen2.5:3b-instruct` for narration, `nomic-embed-text` for embeddings.
CPU only, no GPU.
---
## A. Executive Recommendation
**Fork AI-DnD as the production base. Confidence: high.**
**The Phase 0A recommendation held up, but for partly different reasons than it
gave, and it was wrong about one important thing.**
What held: AI-DnD really does implement the expensive correctness work, and it
survived every entanglement test Phase 0A worried about. The RPG machinery is
optional, the scripting sandbox is removable, and the branch-scoped memory
isolation — the single hardest requirement in the specification — works
correctly against real local embeddings.
What did not hold: **Phase 0A's claim that AI-DnD's undo is non-destructive is
false.** `POST /undo` hard-deletes rows. Retry is non-destructive; undo is not.
There is no Redo. This is a direct conflict with `DECISIONS/005-branch-preserving-history.md`
and specification §4.
That discovery does not change the decision, because the comparison is relative:
AI-DnD needs one bounded change at a chokepoint that every read already passes
through. Open Dungeon needs the entire non-destructive history subsystem built
from nothing, under a 3,991-line component, with **zero automated tests**.
**That "one bounded change" is no longer an estimate.** The follow-up spike
implemented it in 3 files and +130/-31 lines, and it works: undo deletes nothing,
redo restores exactly, the abandoned tail becomes an ordinary branch, and memory
isolation holds. Confidence in the fork decision moves from "high on the evidence"
to "high and demonstrated".
The decision gate from `reports/PRELIMINARY-RECOMMENDATION.md` said to select
AI-DnD unless a blocker is confirmed. All four were tested and none is a blocker:
| Phase 0A blocker | Result |
|---|---|
| Branching/state inseparable from RPG mechanics | **No.** A scenario-less adventure with empty world state played, branched, and retrieved memories correctly. |
| Removing hosted/scripting destabilizes many tests | **No.** 19 of 632 (3%), and every one fails for instrumentation reasons, not coupling. |
| Local-only still needs external services | **Partly — but both are packaging fixes.** `tiktoken` downloads its encoding from a Microsoft CDN; the SPA fetches Google Fonts. |
| Dependency/security burden worse than expected | **No.** 80 packages total vs Open Dungeon's 335 with 6 high-severity advisories. |
---
## B. What We Learned About Each Candidate
### AI-DnD — recommended base
**What worked**
- `632 passed` in 236s, first try, no fixes needed.
- Docker image builds clean; `docker compose` path is real.
- Default model endpoint is already `http://localhost:11434/v1`. Connection test
and model discovery against Ollama worked with no code change.
- Streamed a full multi-turn story over SSE against local Ollama.
- **Genre-neutrality is real.** An adventure created with `scenario_id: null`
gets an empty world state and no RPG scaffolding, and plays normally. Noir
prose, no fantasy code paths, no stats.
- **Retry is genuinely non-destructive.** Retrying kept the replaced attempt as a
sibling take at the same coordinate (rows 6 and 7 both present; both listed by
`/variants`).
- **Forking is genuinely non-destructive.** Adding an alternate take at a passed
turn created branch 2 while every row of branch 1 survived, with
`parent_branch_id` and `fork_depth` recorded.
- **Branch-scoped memory isolation is correct.** Two memories were written on
branch 1 (depths 5 and 11) from real `nomic-embed-text` embeddings. On branch 2,
retrieval returned `used: []` and none of `brass key`, `revolver`, `lockbox`,
`ashtray` or `bullets` appeared anywhere in the assembled prompt. The negative
control passed: switching back to branch 1 returned both memories with
similarity scores (0.8087) and the abandoned facts reappeared. Retrieval is
working *and* correctly scoped.
- **Prompt transparency is already there.** The context endpoint returns labeled
sections with token costs: `narrator 57, ai_instructions 9, persona 15,
plot_essentials 24, history 988, used_memories 143, length_hint 46`.
**What failed**
- **Undo hard-deletes.** Measured: rows 7 → 4, deleting ids 5, 6 **and 7** — that
is, both the player action, the live AI take, *and the alternate take the
earlier retry had preserved*. `delete_turn` also calls `memorybank.forget_node`.
A retry's preserved alternative does not survive a later undo. There is no Redo
endpoint. **Resolved in the follow-up spike** — see section G.
- **Fails offline on a clean install.** First turn in a network-isolated container
died with `NameResolutionError` for
`openaipublic.blob.core.windows.net/encodings/cl100k_base.tiktoken`. `tiktoken`
fetches its BPE encoding on first use. Seeding the 1.7 MB file into the image
made a full turn generate with no internet at all. Packaging fix, not
architecture — but a hard blocker until fixed, and static review missed it.
- **Runtime remote asset.** The built SPA still requests Google Fonts; the CSP
explicitly allows `fonts.googleapis.com` and `fonts.gstatic.com`.
**Strongest reusable pieces**
Immutable parent-linked story tree with lineage entries; alternate takes; branch
fork/switch/rename/delete; per-node world-state and script-state snapshots;
branch-scoped memories with local embeddings; the Insights prompt snapshot;
`ai-dnd-adventure-v2` whole-tree export; the 632-test suite; SSE streaming.
**Major architectural problems**
Destructive undo and no redo. No named checkpoints (branch names are the nearest
thing). Multi-user/hosted concerns are spread across ten modules. `netguard.py`
is the *inverse* of what we need — it blocks non-public endpoints in hosted mode
and does nothing locally; we want a warning on non-loopback endpoints.
**Adaptation required**
Bounded and mostly subtractive. The one genuinely additive change is
non-destructive undo/redo, now measured at 3 files and +130/-31 lines (section G).
### Open Dungeon — not recommended as base
**What worked**
Installed and ran on first try. Plays a real story against Ollama through its
"custom" OpenAI-compatible provider. Clean browser UX. No runtime Google Fonts
(`next/font` self-hosts at build time). Local SQLite, local image generation,
character portrait continuity.
**What failed**
- **Every history operation is destructive, confirmed by measurement.**
- Edit is an in-place `UPDATE messages SET content = ?`. After editing, the
original text `brass key from the ashtray` was **gone from every column of
every table**.
- Retry and Erase both call `DELETE /messages/{id}?after=1`, which runs
`DELETE FROM messages WHERE chat_id = ? AND (created_at > ? OR ...)`. Measured
rows 5 → 4, with no archive anywhere.
- The database has exactly four tables — `chats`, `messages`, `characters`,
`app_settings`. No parent pointer, no branch, no take, no checkpoint.
- **Zero automated tests.** No `test` script, and no test framework in
`devDependencies` at all. Phase 0A listed this as "BUILD/VERIFY"; it is zero.
- **The local provider hard-codes a five-model Gemma 4 allowlist**
(`src/lib/text-models.ts`). Arbitrary local Ollama models are only reachable
through the "custom" OpenAI-compatible path.
- Next.js telemetry is on by default and printed its notice on first start.
- 335 npm packages, 6 high-severity advisories.
**Cost of the branch retrofit — the Experiment B answer**
Larger than Phase 0A estimated, because of a dependency Phase 0A did not identify:
1. `messages` needs `parent_id`, `branch_id`, `depth`, and a live/take flag.
2. Of 20 exported `db.ts` functions, the four history ones need rewriting, and
`getChat` must stop returning a flat list and become a lineage query.
3. `story-prompt.ts` windows history by **array index** (`messages.slice(dropped)`,
`messages.length - 1`). All of it becomes an ancestry walk.
4. **The rolling summary watermark is positional.** `chats.story_summary_count`
means "how many of the chat's oldest messages the summary already covers", and
`route.ts` compares `evicted.length > stored.coveredCount`. On a branch, "the
first N messages" has no meaning. Summaries must be re-anchored to a turn id
and made per-lineage. **Phase 0A did not identify this.**
5. `page.tsx` is a single 3,991-line component holding retry/erase/edit as array
slicing, and there is no take stepper, branch panel, or tree view to extend.
6. All of the above lands with no test suite underneath it.
**Worth reusing as reference:** the reading UI, inline scene images, the
character-portrait-as-reference-image continuity idea, and the scene/media
architecture. None of it ports directly — Open Dungeon is Next.js and AI-DnD is
React + FastAPI.
### ai-adventure — not the base, but promote it to the architectural reference
**What worked — this is the strongest result of the round**
- `Ran 76 tests ... OK` in 1.9s.
- **The Ollama "adapter" is zero code.** `LMStudioBackend` is a generic
OpenAI-compatible client hitting `/v1/chat/completions` and `/v1/models`. I
changed two lines of `world.toml` — `base_url` and model `name` — and the
doctor reported `[PASS] LM Studio reachable: http://127.0.0.1:11434` and
`[PASS] Configured model visible: qwen2.5:3b-instruct`. A real turn then
committed to the database with `status='committed'`, zero validation errors,
and a `model_calls` row recording backend, model and attempt. **Phase 0A's
"MODIFY — LM Studio primary provider" was wrong; it is a config change.**
- **Its history model is exactly what the specification asks for.** Verified
independently, not just by trusting its tests:
- Undo deleted **0 rows**, rolled `has_brass_key` back from `False` to `True`,
and the abandoned turn was still in the database.
- Branching from the restored point produced `gate_open` on the branch and
**did not leak it into the original session**.
- Checkpoint restore rolled state back with the later turn still present.
- Total turns in the database never decreased.
- Schema is the target data model: `turns.parent_turn_id`, `status`,
append-only `state_events`, `state_cache` keyed by `head_turn_id`,
`model_calls` with request/response hashes, `named_checkpoints`, `summaries`
anchored by `through_turn_id`, and lore in FTS5.
- **The cleanest privacy posture by a wide margin.** Exactly one outbound call
site in the entire codebase (`lm_studio.py`'s `urlopen`), no hardcoded remote
host anywhere, and **one** third-party dependency (`pydantic`). It is the only
candidate that already warns when `model.base_url` is not loopback — which is
specification §12, implemented.
**What failed**
- With `qwen2.5:0.5b` the turn failed validation and **nothing was saved**. That
is the model-proposes/app-validates discipline working correctly, but it means
the typed-event contract needs a reasonably capable model; the freeform-prose
candidates tolerate a weak one. `qwen2.5:3b-instruct` succeeded.
- Product distance is real and large: terminal only, worlds authored as
hand-written TOML and Markdown files rather than set up in-app, no streaming,
no embeddings/semantic memory, no media, no import of user documents.
**Adaptation required:** building essentially the entire product on top of a
correct core.
---
## C. Key Technical Findings
| Requirement | AI-DnD | Open Dungeon | ai-adventure |
|---|---|---|---|
| History/undo model | Tree + takes; **undo deletes**, no redo (both fixed in the spike, §G) | Flat list; **retry/edit/erase all destroy** | **Parent-linked, head pointer, undo deletes nothing** |
| State rollback | Per-node snapshots, verified | None | **Event replay, verified** |
| Memory isolation across branches | **Verified correct, with negative control** | N/A (no branches) | **Verified correct** |
| Local Ollama | Default endpoint; verified streaming | Verified via "custom" provider; local provider allowlists 5 Gemma builds | **Verified, zero code change** |
| Offline | **Blocked by tiktoken CDN fetch** until vendored; then fully offline | Runtime clean; build needs network; Next telemetry on by default | **Verifiably clean — 1 outbound call site, 1 dependency** |
| Browser suitability | React SPA + FastAPI, already there | Next.js, already there | **None** |
| Prompt inspection | **Per-action snapshot with labeled sections and token costs** | Not exposed | Prompt hashes; `store_prompts` off by default |
| Imported knowledge | Story cards (keyword-triggered) — closest starting point | None | **Local FTS5 lore, deterministic** |
| Named checkpoints | Absent (branch names only) | Absent | **Present** |
| Future media | Absent | **Present and working** | Absent |
| Tests | **632** | **0** | 76 |
---
## D. Important Surprises
Ordered by how much they should change the plan.
1. **AI-DnD's undo is destructive, and it destroys retry's preserved
alternatives too.** `reports/PRELIMINARY-RECOMMENDATION.md` and
`REUSE-MATRIX.md` both credit AI-DnD with non-destructive rollback. Retry
earns that; undo does not. Measured: undo deleted the player action, the live
take, and the sibling take a prior retry had kept. The follow-up spike has
since replaced it with a head-cursor undo and added Redo (§G), so this is a
corrected planning assumption rather than an outstanding defect.
2. **AI-DnD cannot take a single turn on an air-gapped machine as shipped.**
`tiktoken` fetches `cl100k_base` from a Microsoft CDN on first use. Proven by
running it on a Docker `--internal` network, and proven fixed by seeding the
file.
3. **ai-adventure needs no Ollama adapter at all.** Two config lines. The
"LM Studio" name is a misnomer for a generic OpenAI-compatible client.
4. **Open Dungeon has zero automated tests** — not few, none, and no framework
installed.
5. **Open Dungeon's summary watermark is positional**, which adds a summary
redesign to its branch retrofit that Phase 0A did not cost.
6. **Open Dungeon's "local" provider only offers five Gemma 4 QAT builds.** A
genre-agnostic app that lets the user pick any installed Ollama model needs
that lifted.
7. **AI-DnD fetches Google Fonts at runtime** and its CSP is written to allow it,
violating specification §12's "no remote fonts".
8. **AI-DnD's `netguard` is the opposite of our requirement** — it protects a
hosted deployment from SSRF and is inert locally. ai-adventure has the
loopback warning we actually want.
9. Repo maturity differs sharply: AI-DnD is 172 commits / 3 authors, Open Dungeon
45 / 1, ai-adventure 20 / 1. All three are young. Since we fork, this matters
less than it would for a dependency, but there is no upstream to lean on.
---
## E. Recommendation for Next Step
**Fork AI-DnD and begin the controlled strip-down.** The blocking uncertainty is
gone: the spike this section previously asked for has been run and passed
(section G).
Recommended order, cheapest and most load-bearing first:
1. **Close the two offline blockers.** Vendor the `tiktoken` encoding and
self-host the three font families, then re-run the air-gapped test. Until this
is done the application does not satisfy specification §2, and it is a day's
work.
2. **Strip the hosted surface.** Remove multi-user/auth, the demo key, analytics,
`render.yaml`, the Postgres path, and the quickjs sandbox. Re-instrument the
~19 tests that use JS hooks to observe state, moving them onto the
`world_state_after` snapshots that already exist.
3. **Land the undo/redo work properly**, promoting the spike from a proof to a
feature: fix the four rough edges it left (undo floor at the opening node,
retry/`add_take` while behind the head, marking abandoned turns disposable,
and the frontend Redo control and branch labelling) — **and add `headDepth` to
the export bundle in the same change**, or a round-trip silently undoes every
undo (§H).
4. **Add named checkpoints**, using ai-adventure's
`named_checkpoints(session_id, turn_id, name)` as the model. With an explicit
head that can sit anywhere, a checkpoint is now just a named head position.
5. **Add the loopback-endpoint warning**, modeled on ai-adventure's
`_endpoint_warnings`.
6. **Then** imported knowledge, and only after that, media.
Treat **ai-adventure as the written specification for state and history
semantics** throughout. Its schema is the target shape and its 76 tests are the
behavioral contract worth copying — the spike already reproduced its central
property (undo that moves a head instead of deleting) and benefited from having
that reference to aim at. §H extends this: when generalizing AI-DnD's world state
into genre-neutral narrative state, take ai-adventure's **typed-event vocabulary**
rather than AI-DnD's relative-delta vocabulary, because the delta protocol has a
failure mode that validation cannot catch and that worsens as context grows.
One addition to the ordering above: whatever narrative-state engine we build must
be **exercised at realistic context length against the models users will actually
run**, not only in a clean harness. That is where the referee's weakness showed
up, and it would not have appeared in a unit test.
Do not merge repositories. Open Dungeon remains a UX and media reference only.
## F. Open Questions
**Resolved by the undo/redo spike (§G):**
- ~~Does capping `Path.clause` by head depth break the memory and summary
cursors?~~ **No.** `test_memory_settling`, `test_memory_nodes`,
`test_memory_retrieval`, `test_memory_rewrite` and `test_history_window` all
passed unchanged, and memory retrieval was verified end-to-end against real
embeddings with a control.
- ~~How should named checkpoints sit on top of takes and branches?~~ **Largely
answered.** The spike introduces a head that can sit behind the tip, so a
checkpoint becomes a named `(branch_id, depth)` position. Still needs a
decision on whether restoring one forks immediately or on first write — the
spike chose "on first write" for undo, and consistency argues for the same.
**Resolved by the follow-up checks (§H):**
- ~~What is the model capability floor?~~ **Characterized, not a blocker.** The
delta protocol is within a 3B model's reach in a clean prompt (3/3 correct) but
degraded to absolute values under the full application context. `0.5b` cannot
do it at all. See §H for the design consequence.
- ~~Can `psycopg`/Postgres be dropped cleanly?~~ **Yes.** Three places, no
blockers: one branch in `database.py`, two migration helpers, one import inside
analytics.
- ~~Do story cards become imported knowledge, or is a new table needed?~~
**A new table.** `StoryCard` carries no `branch_id`/`depth`, so it is not
lineage-scoped and inherits neither branch nor undo isolation.
- ~~Does the export format survive the history rework?~~ **No — and this is now a
known defect to fix, not a question.** See §H.
**Still open:**
1. **When does abandoned history get cleaned up?** Nothing marks it disposable
yet, and undone turns now accumulate rather than being deleted. This is the
intended trade, but it needs a retention story.
2. **Is Open Dungeon's image path portable at all?** It is Next.js calling a local
FLUX worker over HTTP. The worker protocol may be reusable even though none of
the UI is.
3. **What is the real model recommendation for users?** The probe establishes a
floor and a failure mode, not a ranking. A proper capability matrix across the
models a user would actually run belongs in the build phase, measured at
realistic context length.
4. **Not tested in any round:** import/export round-trip *behavior* beyond the
head-depth defect found by reading, multi-hour long-story context behavior, or
any concurrency beyond the single-turn lock.
## G. Follow-Up Spike: Non-Destructive Undo/Redo — Passed
Run after the main round, on a disposable copy of AI-DnD `d72f7c1b`. Full detail
in `reports/PHASE-0B-UNDO-SPIKE.md`.
**The change is three files, +130 / -31 lines**, roughly a fifth of it comments:
| File | Change | What |
|---|---|---|
| `context/lineage.py` | +26 / -3 | Read an uncapped lineage entry as capped at `self.tip` (the head), in `clause` and `contains`; add `uncapped()`. |
| `routers/adventures/takes.py` | +74 / -27 | `undo_turn` moves `head_depth` instead of deleting; new `redo_turn`. |
| `routers/adventures/turns.py` | +30 / -1 | `fork_if_behind_head` calls the existing `tree.branch_at` when a write lands below the head. |
It is this small because the pieces already existed: every read funnels through
`lineage.path_of()`, `adventures.head_depth` was already a stored head, and
`tree.branch_at` already created a branch that leaves a path at a depth without
touching the line it leaves.
**Results against the success criteria:**
| Criterion | Result |
|---|---|
| Undo deletes zero rows | **Pass.** 6 → 6 rows across three undos; separately, 11 consecutive undos on a 23-action story with 23 actions still present. Clears the "at least five, unlimited preferred" requirement. |
| Abandoned tail stays reachable as a branch | **Pass.** Undo-then-write forked branch 2 at fork depth 3; branch 1 kept all six of its rows, live and readable, and the branch switcher reaches it. |
| Redo restores | **Pass.** Three redos returned the head from -1 to 5 and the transcript to all six actions, exactly. |
| Branch-scoped memory isolation holds | **Pass, with control.** An embedded memory at depth 5 was retrieved at the tip (similarity 0.689), returned `used: []` once the head moved to depth 3, and became eligible again on redo — without being deleted. None of seven terms unique to the hidden turns appeared in the assembled prompt. |
| Suite stays green apart from delete-behavior tests | **Pass.** `627 passed, 5 failed` (0.8%). All five assert deleted rows or pruned memories. The state assertions inside those same tests still pass — `assert adv.script_state == {"gold": 0}` succeeds in both `test_state_revert` failures, and the fork-boundary guard in `test_undo_stops_at_the_fork` is untouched. |
**Rough edges left for the real implementation** (none architectural): undo can
walk past a user-written opening to an empty transcript; retry and `add_take`
were not routed through the fork check; abandoned turns are not yet marked
disposable; and the frontend has no Redo control.
**What this changes about the recommendation:** nothing about the choice, and the
one thing about the plan — the strip-down can start now rather than after another
investigation.
## H. Follow-Up Checks — Referee, Export, Cards, Postgres
Run after the spike. Detail in `reports/PHASE-0B-FOLLOWUP-CHECKS.md`.
### The finding that changes what we build
AI-DnD's world-state referee asks the model for **relative deltas**
(`new = old + delta`, then clamped). Playing the seeded RPG scenario with
`qwen2.5:3b-instruct`, the model sent **absolute values** — `{"player.hp": 92}`
meaning "hp is now 92". The engine read `+92`, clamped to `+35`, then to the
ceiling of 100:
```
player.hp 100 -> 100 "did not move. It is already at its maximum of 100"
```
**The player took an arrow to the shoulder and finished the turn at full health.**
The referee did its job — nothing was corrupted, and it fed corrective text back.
But no validator can catch this, because `+92` is a legal proposal. The
application stayed authoritative while the authoritative state silently stopped
tracking the narration. That is the exact divergence specification §2 exists to
prevent.
An isolated probe using AI-DnD's own `EMIT_RULE` and parser shows the protocol is
**not** beyond a small model — `qwen2.5:3b-instruct` and `qwen2.5:7b-instruct`
both got 3/3 correct relative deltas, and `qwen2.5:0.5b` emitted no block at all.
The difference is context load: in a short prompt the 3B model follows the rule,
and under the full application prompt it drifts to absolutes.
**Consequence for the design:** when we generalize AI-DnD's world state into
genre-neutral narrative state, take **ai-adventure's typed-event vocabulary**
(`set_flag key=… value=…` — explicit and absolute) rather than AI-DnD's delta
vocabulary. The delta protocol has a failure mode that is invisible to validation
and worsens with context length; the event protocol cannot express the mistake.
This is the second time in this round that ai-adventure's core has turned out to
be the right specification to build to.
### One new defect
**Export/import silently redoes an undone story.** `bundle.py:_point_the_head`
recomputes `head_depth = max(depths)` on import, and the bundle format carries
`headBranch` but not the head depth. That was correct when undo deleted rows; with
the spike's non-destructive undo, a round-trip restores every abandoned turn. Fix
is one optional `headDepth` field plus honouring it on import, falling back to
`max(depths)` so existing `v2` files still load. It must land with the undo work,
not after it.
### Two smaller answers
- **Story cards are not lineage-scoped.** `StoryCard` has no `branch_id` or
`depth`, unlike `Memory`. Harmless today because cards are authored rather than
derived — but any imported-knowledge source that can be created from story
content must carry those coordinates and be read through `lineage.Path.clause`,
or it inherits neither branch nor undo isolation. Argues for a new table rather
than extending `story_cards`.
- **Postgres is cleanly removable.** One branch in `database.py`, two migration
helpers, one import inside analytics. `psycopg[binary]` then drops out.
## Supporting Evidence
- `reports/PHASE-0B-BASELINE.md` — installs, test runs, ports, storage, versions.
- `reports/PHASE-0B-AI-DND-EXPERIMENT.md` — branch/undo/retry measurements,
memory isolation with negative control, scripting removal.
- `reports/PHASE-0B-OPEN-DUNGEON-HISTORY.md` — destructive-history proof and
retrofit cost map.
- `reports/PHASE-0B-AI-ADVENTURE-OLLAMA.md` — zero-change Ollama run and the
fixture checks.
- `reports/PHASE-0B-OFFLINE-NETWORK.md` — offline runs and egress inventory.
- `reports/PHASE-0B-UNDO-SPIKE.md` — the non-destructive undo/redo spike: the
diff, the measurements, and the rough edges it left.
- `reports/PHASE-0B-FOLLOWUP-CHECKS.md` — the world-state referee under local
models, the export/import head defect, story-card lineage safety, Postgres.
Working tree, scripts and databases from both rounds are under `phase0b/`
(untracked scratch, safe to delete). The spike itself is `phase0b/spike/`, a
disposable copy of the AI-DnD backend — it is a proof, not a branch to merge.
+49
View File
@@ -0,0 +1,49 @@
# Planning Update Summary — Post Phase 0B Review
**Date:** 2026-09-01
**Purpose:** Review aid. This file summarizes planning changes made after accepting the ten architecture decisions from the Phase 0B review.
## New Decisions Recorded
1. AI-DnD is the production base at pinned Phase 0B commit `d72f7c1b...`.
2. AI-DnD's browser/service/story-tree/memory/context foundation is retained as the ownership center.
3. The non-destructive head-cursor Undo/Redo spike is promoted into the production design, but the disposable spike is not treated as merge-ready production code.
4. Narrative state will use explicit typed events/absolute assignments inspired by ai-adventure, not AI-DnD's relative-delta protocol.
5. ai-adventure is an implementation reference, not the product specification.
6. Open Dungeon is a UX/media reference only.
7. Abandoned history is retained and marked disposable; no automatic cleanup is required in v1.
8. Export/import must preserve active head position as part of the history work.
9. Imported knowledge will be a separate first-class subsystem rather than an extension of Story Cards.
10. Future image/video/audio/TTS/STT extension contracts remain, but no media generation is required for v1.
11. Local-only inference may span user-controlled machines: same-host Ollama is the default, but an explicitly configured trusted-LAN Ollama host is supported in v1 without exposing the storyteller UI/API to the LAN.
## Documents Materially Revised
- `README.md`
- `SPECIFICATION.md`
- `TECHNICAL-DESIGN.md`
- `DATA-MODEL.md`
- `STORY-BRANCH-SEMANTICS.md`
- `CONTEXT-AND-MEMORY.md`
- `IMPORTED-KNOWLEDGE-DESIGN.md`
- `SECURITY-THREAT-MODEL.md`
- `V1-ACCEPTANCE-TESTS.md`
- `BUILD-MILESTONES.md`
- `RESEARCH-PLAN.md`
- `CODEX-HANDOFF-NOTE.md`
- `004-local-only-production.md`
- `005-branch-preserving-history.md`
- `008-phase0-before-build-plan.md`
## New ADRs
- `009-ai-dnd-production-base.md`
- `010-explicit-typed-narrative-state-events.md`
## Historical Evidence Intentionally Preserved
The Phase 0A candidate reports and the coding agent's `PHASE-0B-RECOMMENDATION.md` remain research evidence. They may contain assumptions that are superseded by the current planning documents. They are not silently rewritten to make the historical analysis appear as if it had reached later conclusions originally.
## Coding Prompt Status
No next Codex implementation prompt is included in this revision.
+140 -122
View File
@@ -1,155 +1,173 @@
# Adventure Storyteller Planning Package
This package contains the current product requirements, provisional architecture, Phase 0 research, detailed subsystem designs, acceptance tests, and the Codex Phase 0B validation handoff for the local-only interactive-story project.
**Status:** Phase 0 complete; architecture selected; planning revision ready for review.
**Production coding:** Not yet authorized. Review this package before preparing the first implementation prompt.
## Current Status
This package contains the current product requirements, final Phase 0 architecture decisions, detailed subsystem designs, acceptance tests, research evidence, and the production milestone plan for the local-only interactive-story project.
Phase 0A static research is complete.
## Current Decision
The project is now ready for **Phase 0B local validation** of the three finalists:
Phase 0A static research and Phase 0B local validation are complete.
1. AI-DnD
2. Open Dungeon
3. ai-adventure
The production starting point is:
**Do not begin production implementation yet.**
> **Fork AI-DnD at upstream commit `d72f7c1bda0f34fccd84afb7a25c34eb01c901de`.**
The purpose of Phase 0B is to validate the fork/base decision and resolve the remaining architecture questions with real builds, tests, offline runs, and tightly scoped experiments.
The selection is based on measured Phase 0B behavior, not feature count. AI-DnD already contains the highest-value structural machinery: browser UI, FastAPI service boundary, SQLite persistence, parent-linked story history, alternate takes, branch-aware state snapshots, local Ollama operation, branch-scoped memory, prompt/context inspection, streaming, export/import, and a substantial automated test suite.
The selected composition of ideas is:
```text
AI-DnD production base
+ ai-adventure state/event/commit/checkpoint/privacy patterns
+ Open Dungeon story-reading and future-media UX patterns
+ Chronicler memory-authority concepts
+ Interactive Fiction Framework canon/validation concepts
+ Gamentic provider-neutral media concepts
```
This is **not** a repository merge. AI-DnD is the ownership center. Other projects are implementation references only unless a later milestone explicitly reimplements a compatible idea.
## Phase 0B Findings That Changed the Plan
Phase 0B confirmed the fork choice while correcting several Phase 0A assumptions:
- AI-DnD's shipped Undo was destructive and had no Redo.
- A disposable spike proved non-destructive head-cursor Undo/Redo in three backend files while preserving branch-scoped memory isolation.
- A new continuation written after Undo can fork from the moved-back head while retaining the abandoned future.
- AI-DnD's current relative-delta world-state protocol can produce semantically wrong state under realistic context even when the proposal is syntactically valid.
- Production narrative state will therefore use explicit typed events/absolute assignments inspired by ai-adventure rather than AI-DnD's relative-delta protocol.
- AI-DnD requires offline hardening: `tiktoken` attempts a first-use CDN fetch and the SPA requests Google Fonts at runtime.
- AI-DnD's export format must preserve the active head position; otherwise export/import can silently redo an undone story.
- AI-DnD Story Cards are not a sufficient imported-knowledge store because they are not designed for the required classification, provenance, chunking, and lineage semantics.
- Open Dungeon remains useful for UX/media ideas but is no longer a serious production-fork candidate.
- ai-adventure is not the production base but is the strongest implementation reference for authoritative typed state events, head movement, checkpoints, replay, and narrow local-only behavior.
See `PHASE-0B-RECOMMENDATION.md` for the coding agent's evidence. That report is retained as research evidence; the planning documents in this package record the decisions made after reviewing it.
## Document Authority
Use the documents in this order when requirements appear to conflict:
1. `SPECIFICATION.md` — product requirements and desired behavior.
2. Detailed design/behavior documents listed below — elaborations of the specification.
3. `V1-ACCEPTANCE-TESTS.md` — observable pass/fail interpretation of v1 requirements.
4. `TECHNICAL-DESIGN.md` — provisional implementation direction, subject to Phase 0B findings.
5. Phase 0A reports — research evidence and candidate analysis.
6. `BUILD-MILESTONES.md` — intentionally incomplete until the fork/architecture decision is made.
1. `SPECIFICATION.md` — product requirements and required behavior.
2. Detailed behavior/design documents:
- `STORY-BRANCH-SEMANTICS.md`
- `CONTEXT-AND-MEMORY.md`
- `IMPORTED-KNOWLEDGE-DESIGN.md`
- `SECURITY-THREAT-MODEL.md`
- `MEDIA-EXTENSION-CONTRACT.md`
- `BROWSER-UX-SPEC.md`
- `DATA-MODEL.md`
3. `V1-ACCEPTANCE-TESTS.md` — observable pass/fail contract.
4. `TECHNICAL-DESIGN.md` — selected implementation architecture.
5. Foundational ADRs (`001-...md` through the current ADR set).
6. `BUILD-MILESTONES.md` — implementation sequence; it does not override product behavior.
7. Phase 0 research reports — evidence and historical findings.
The detailed design documents describe target behavior; they do not force a particular repository schema when an equivalent implementation satisfies the behavior.
Candidate repositories and research reports are **not** specifications. In particular, ai-adventure is an implementation reference for selected patterns; its behavior does not override this package.
## Recommended Reading Order for Codex
## Foundational Decisions
### A. Product and architectural intent
1. `SPECIFICATION.md`
2. `DATA-MODEL.md`
3. `STORY-BRANCH-SEMANTICS.md`
4. `CONTEXT-AND-MEMORY.md`
5. `IMPORTED-KNOWLEDGE-DESIGN.md`
6. `SECURITY-THREAT-MODEL.md`
7. `MEDIA-EXTENSION-CONTRACT.md`
8. `BROWSER-UX-SPEC.md`
### B. Test contract
9. `TEST-CAMPAIGN-FIXTURE.md`
10. `V1-ACCEPTANCE-TESTS.md`
### C. Provisional architecture and research
11. `TECHNICAL-DESIGN.md`
12. `RESEARCH-PLAN.md`
13. `reports/PHASE-0A-STATUS.md`
14. `reports/PRELIMINARY-RECOMMENDATION.md`
15. `reports/REUSE-MATRIX.md`
16. Candidate-specific reports in `reports/`
### D. Execute
17. `PHASE-0B-CODEX-HANDOFF.md`
## Core Product Decisions Already Settled
The Phase 0B investigation should treat these as requirements rather than questions:
The following are settled for v1:
- browser-first UI,
- local-only v1 runtime,
- local Ollama inference,
- application-owned authoritative story state,
- complete retained transcript,
- local-only runtime across user-controlled local infrastructure,
- Ollama inference with same-host loopback as default and explicitly configured trusted-LAN inference supported,
- single-user deployment,
- AI-DnD production base at the pinned Phase 0B commit,
- application-owned authoritative state,
- complete retained transcript/history,
- simple user-facing Undo/Redo/Retry/Save Point semantics,
- non-destructive internal lineage,
- abandoned history retained but marked disposable; cleanup later,
- at least five Undo operations; unlimited preferred if technically straightforward,
- Redo and Retry supported,
- named checkpoints retained until explicitly deleted,
- genre-agnostic core schema,
- imported knowledge classes: Canon / Reference / Inspiration,
- local retrieval and embeddings,
- non-destructive head-cursor history internally,
- new write after moving backward creates a new continuation while retaining the old future,
- abandoned history is retained and marked disposable; **no automatic cleanup is required in v1**,
- named checkpoints remain until explicitly deleted,
- genre-agnostic core state,
- explicit typed narrative-state events/absolute assignments rather than ambiguous relative deltas,
- hybrid state model: validated events plus state snapshots/cache,
- branch/lineage-safe summaries and memories,
- imported knowledge is a separate first-class subsystem rather than an extension of AI-DnD Story Cards,
- knowledge classes: Canon / Reference / Inspiration,
- local lexical retrieval plus local semantic retrieval where practical,
- prompt/context provenance and inspection,
- no cloud inference, telemetry, automatic web retrieval, remote runtime assets, shell/MCP/general plugin execution,
- future local image/video/TTS/STT capability must remain possible without coupling it to the core story engine.
- export/import must preserve active branch **and active head position**, including an undone position,
- no cloud/Internet inference, telemetry, automatic web retrieval, remote runtime assets, shell/MCP/general plugin execution,
- LAN inference is distinct from LAN exposure of the storyteller UI/API; the latter is not required for v1,
- future local image/video/audio/TTS/STT support remains optional and decoupled from the story engine.
## Detailed Documents
## Phase 0 Status
- `DATA-MODEL.md` — conceptual target data model and authority/state structures.
- `STORY-BRANCH-SEMANTICS.md` — exact Undo, Redo, Retry, Edit, checkpoint, restore, and disposable-history behavior.
- `CONTEXT-AND-MEMORY.md` — context construction, authority hierarchy, summaries, memory, retrieval, provenance, token budgeting.
- `IMPORTED-KNOWLEDGE-DESIGN.md` — import, classification, chunking, local indexing, retrieval, provenance, isolation, and prompt-injection handling.
- `SECURITY-THREAT-MODEL.md` — local trust boundary, network policy, untrusted input handling, browser security, and offline acceptance.
- `MEDIA-EXTENSION-CONTRACT.md` — future image, video, audio, TTS, and STT extension boundaries. Media remains optional and derived from story state.
- `BROWSER-UX-SPEC.md` — user-facing browser workflow and advanced inspection surfaces.
- `TEST-CAMPAIGN-FIXTURE.md` — deterministic campaign fixture for comparing candidates and later regression testing.
- `V1-ACCEPTANCE-TESTS.md` — black-box requirements and release gate.
### Complete
## Phase 0A Research
- candidate discovery and triage,
- static architecture/privacy/licensing review,
- local clone/build/test validation,
- real Ollama testing,
- offline/network observation,
- AI-DnD strip-down/entanglement checks,
- Open Dungeon history-retrofit analysis,
- ai-adventure Ollama/service-boundary checks,
- AI-DnD non-destructive Undo/Redo spike,
- referee/state-protocol follow-up,
- export/import head-position follow-up,
- Story Card lineage review,
- Postgres removability review,
- production fork decision,
- production architecture decision.
Static repository research was completed on 2026-09-01.
### Deferred to implementation/release validation
Key reports:
These do not block the architecture decision:
- `reports/PHASE-0A-STATUS.md`
- `reports/PRELIMINARY-RECOMMENDATION.md`
- `reports/REUSE-MATRIX.md`
- `reports/AI-DND-ANALYSIS.md`
- `reports/OPEN-DUNGEON-ANALYSIS.md`
- `reports/AI-ADVENTURE-ANALYSIS.md`
- `reports/PRIVACY-STATIC-ANALYSIS.md`
- `reports/LICENSING-REUSE.md`
- `reports/SOURCE-INDEX.md`
- comparative recommendation of narrator/state models for real users,
- multi-hour/100-turn long-run behavior,
- detailed concurrency behavior beyond the single-user turn lock,
- actual future image/video/TTS/STT provider integration,
- abandoned-history cleanup UI/policy (not required in v1).
Current preliminary architecture hypothesis:
## Recommended Reading Order for the Next Implementation Stage
Do not convert this into a coding prompt until the package review is approved.
When implementation planning resumes, read:
1. `SPECIFICATION.md`
2. `TECHNICAL-DESIGN.md`
3. `BUILD-MILESTONES.md`
4. `STORY-BRANCH-SEMANTICS.md`
5. `DATA-MODEL.md`
6. `CONTEXT-AND-MEMORY.md`
7. `IMPORTED-KNOWLEDGE-DESIGN.md`
8. `SECURITY-THREAT-MODEL.md`
9. `BROWSER-UX-SPEC.md`
10. `V1-ACCEPTANCE-TESTS.md`
11. ADRs, especially the production-base and narrative-state-event decisions
12. Phase 0B reports only as supporting evidence
## Workflow From Here
```text
AI-DnD production base
+ ai-adventure trust/commit/privacy rules
+ Open Dungeon scene/media UX patterns
+ Chronicler memory authority tiers
+ Interactive Fiction Framework Story Bible authority/validation
+ Gamentic media-provider abstraction
Phase 0 research and spikes COMPLETE
|
v
Architecture/fork decision COMPLETE
|
v
Planning package revision CURRENT REVIEW
|
v
Approve planning package
|
v
Prepare one implementation prompt
for Production Milestone 1
|
v
Implement and review milestone-by-milestone
```
This is a hypothesis to test, not a fork decision.
## Stop Rule
## Overall Workflow
**Do not begin production coding from this package yet.**
```text
Specification + detailed behavioral designs
|
v
Phase 0A static research
|
v
Phase 0B local validation
|
v
Fork / architecture decision
|
v
SPECIFICATION v1.0
TECHNICAL-DESIGN v1.0
|
v
Detailed BUILD-MILESTONES.md
|
v
Production implementation
```
## Important Stop Rule
Phase 0B ends with evidence and a recommendation.
Codex should **not** begin production coding, repo conversion, or broad feature implementation until the Phase 0B results have been reviewed and the production base has been selected.
The current action is to review the planning changes. No replacement Codex prompt has been prepared in this revision.
+42 -2
View File
@@ -1,9 +1,11 @@
# Adventure Storyteller — Phase 0 Research Plan
**Status:** Ready for execution
**Status:** Complete — Phase 0 closed 2026-09-01
**Phase:** 0 — Research, Validation & Architecture
**Goal:** Determine what to build, what to fork/reuse, and finalize the technical design before production implementation begins.
**Outcome:** AI-DnD selected as the production base; non-destructive head-cursor history and explicit typed narrative-state events selected; production milestones now defined in `BUILD-MILESTONES.md`.
## 1. Why Phase 0 Exists
The project has several promising open-source starting points. They differ substantially in:
@@ -28,6 +30,26 @@ Phase 0 therefore ends when we can answer:
No production feature work should begin before that decision unless explicitly authorized.
## 1A. Phase 0 Completion Summary
Phase 0A static research and Phase 0B local validation are complete.
Final dispositions:
- AI-DnD — production fork/base at `d72f7c1bda0f34fccd84afb7a25c34eb01c901de`.
- ai-adventure — primary implementation reference for authoritative typed state events, checkpoint/head/replay semantics, and narrow local-only behavior.
- Open Dungeon — UX and future-media reference only.
Critical Phase 0B prototype results:
- non-destructive Undo/Redo with a movable active head was demonstrated on AI-DnD without deleting history and without breaking branch-scoped memory isolation,
- export/import must preserve active head position,
- AI-DnD's relative-delta state protocol should not be retained as the generic narrative-state contract,
- imported knowledge should be a separate subsystem rather than Story Cards,
- runtime offline hardening is required for tokenizer data and fonts.
The original milestone text below is retained as the research execution record.
## 2. Candidate Repositories
Initial candidates:
@@ -575,4 +597,22 @@ Phase 0 is complete only when:
- the technical design is v1.0,
- the actual implementation milestone plan has been written.
At that point, production implementation can begin.
At that point, production implementation becomes eligible to begin, but still requires explicit approval and a milestone-specific execution prompt.
## 17. Final Phase 0 Closure Record
Phase 0 definition of done is satisfied for architecture/planning purposes:
- finalists cloned and run,
- test suites measured,
- Ollama exercised locally,
- offline/network behavior investigated,
- critical history/state uncertainties prototyped,
- fork/build strategy selected,
- specification revised to v1.0,
- technical design revised to v1.0,
- production build milestones written,
- foundational ADRs updated/added.
No production implementation prompt is part of this research plan.
+78 -32
View File
@@ -1,6 +1,6 @@
# Adventure Storyteller — Security Threat Model
**Status:** Draft v0.1
**Status:** v1.0 — local-only hardening requirements informed by Phase 0B
**Purpose:** Define the security and privacy boundaries for a local-only interactive storytelling application.
## 1. Security Objective
@@ -79,7 +79,9 @@ Trusted to receive:
- retrieved local knowledge,
- user input.
For v1, Ollama should be accessed through loopback unless the user explicitly configures otherwise in a future release.
For v1, Ollama may run either on the storyteller machine or on an explicitly configured machine on the user's trusted LAN. Same-host loopback remains the default. When Ollama is on another LAN host, story prompt/context data necessarily crosses the local network to that approved inference machine.
The inference host should restrict access to Ollama using host firewall/network controls appropriate to the local environment. LAN inference does not authorize LAN exposure of the storyteller UI/API.
### 4.3 Local browser
@@ -124,10 +126,11 @@ Preferred allowed v1 network paths:
```text
Browser -> local storyteller application
Storyteller -> 127.0.0.1 Ollama
OR -> explicitly approved trusted-LAN Ollama host
Storyteller -> optional explicitly configured local media service
```
Everything else should be denied or absent.
Everything else should be denied or absent. An approved LAN Ollama host is inside the v1 local trust boundary; arbitrary Internet/cloud inference is not.
## 7. Default Bind Addresses
@@ -139,10 +142,13 @@ Preferred defaults:
```
### Ollama
Same-host default:
```text
127.0.0.1
```
A separate inference machine may listen on an explicitly chosen LAN interface/address as required for the storyteller to reach it. That host should use firewall/network policy to limit access to trusted clients.
### Media services
```text
127.0.0.1
@@ -156,7 +162,7 @@ Do not bind to:
by default.
LAN exposure may be considered later as a separate explicit feature.
LAN exposure of the **storyteller UI/API** may be considered later as a separate explicit feature. This does not prohibit the v1 storyteller backend from connecting outbound to an approved LAN Ollama host.
## 8. Forbidden v1 Network Behavior
@@ -209,17 +215,21 @@ creates a data-exfiltration path.
For v1:
- prefer a fixed local Ollama endpoint,
- or allow only loopback endpoints.
- default to same-host loopback Ollama,
- allow an explicitly configured trusted-LAN Ollama endpoint,
- surface the effective destination clearly in configuration/diagnostics,
- reject or keep arbitrary public Internet endpoints outside normal v1 configuration.
Possible allowed forms:
Examples of allowed forms include:
```text
http://127.0.0.1:11434
http://localhost:11434
http://192.168.1.50:11434
http://inferencebox.local:11434
```
Anything else should be rejected unless a future advanced configuration explicitly enables it.
The LAN hostname/address must be an intentional user configuration. Do not infer that every non-loopback endpoint is trusted merely because it resolves.
## 11. Imported Files Must Be Data Only
@@ -716,15 +726,9 @@ No automatic runtime update check is required.
## 48. Dependency Risk
Phase 0B should inventory:
- Python dependencies,
- npm dependencies,
- native modules,
- optional cloud SDKs,
- abandoned packages,
- packages with install/postinstall scripts.
Phase 0B inventoried the major inherited surfaces in the selected base and identified concrete removals and runtime leaks. Production milestones must continue dependency review as code is stripped and upgraded, including Python/npm/native modules, optional cloud SDKs, install/postinstall behavior, and security advisories.
Prefer removing dependencies that only support unwanted cloud features.
Prefer removing dependencies that only support unwanted cloud/hosted features.
## 49. Supply Chain
@@ -766,22 +770,22 @@ The application should only have permissions needed to:
It should not need broad system access.
## 53. LAN Mode — Future Only
## 53. Storyteller LAN Access — Future Only
If LAN access is added later, it must be a distinct security mode.
LAN **inference** is supported in v1: the loopback-bound storyteller may connect outbound to an explicitly configured trusted-LAN Ollama machine.
It should require:
LAN access to the **storyteller browser UI/API** is different and remains a future security mode. If added later, it should require:
- explicit enablement,
- authentication,
- TLS or trusted local network assumptions,
- host/firewall documentation,
- session protection.
Do not accidentally inherit LAN exposure because a candidate project binds to all interfaces.
Do not accidentally inherit storyteller LAN exposure because a candidate project binds to all interfaces.
## 54. Tailscale / VPN Access
Same as LAN mode.
Treat remote access to the storyteller UI/API like storyteller LAN access.
Useful later, but not a v1 requirement.
@@ -1064,6 +1068,35 @@ Classify each hit:
- development-only,
- false positive.
## 71A. Phase 0B Confirmed Risks in the Selected AI-DnD Base
Phase 0B runtime validation found specific inherited behaviors that production must remove or package differently:
1. **First-use tokenizer download**
- `tiktoken` attempted to fetch `cl100k_base` from a Microsoft-hosted endpoint on the first isolated turn.
- Production must bundle/cache the required encoding or replace that path so ordinary story use never depends on Internet access.
2. **Runtime Google Fonts**
- the SPA requested Google-hosted font assets and the existing CSP permits those hosts.
- Production must self-host required fonts or use local/system fonts and remove the remote CSP allowances.
3. **Unneeded cloud/hosted surface**
- hosted auth/multi-user/demo/analytics/Render/Neon/Postgres/cloud-provider paths are outside the v1 trust model.
- Remove these paths rather than simply hiding them when practical.
4. **QuickJS/campaign scripting**
- executable campaign scripting is outside the v1 trust boundary.
- Remove/disable the engine and replace any test-only instrumentation that depended on it.
5. **Endpoint policy mismatch**
- inherited network guarding is aimed at hosted deployment behavior, not at preventing accidental story-data exfiltration.
- Production should default to loopback Ollama, explicitly support a configured trusted-LAN Ollama host, and reject/avoid arbitrary public Internet inference endpoints.
6. **Postgres is removable**
- Phase 0B found no architectural blocker to dropping Postgres support; SQLite remains the v1 store.
These are production hardening requirements, not optional polish.
## 72. Phase 0B Runtime Network Test
After all dependencies/models are preinstalled:
@@ -1083,13 +1116,13 @@ After all dependencies/models are preinstalled:
13. generate local image in Open Dungeon if evaluated,
14. monitor sockets/DNS.
Record every non-loopback attempt.
Record every non-loopback attempt and classify it as approved trusted-LAN inference/media traffic or unexpected traffic.
## 73. Runtime Pass Condition
For ordinary v1 story operation:
> No story content or imported content leaves loopback or explicitly approved local endpoints.
> No story content or imported content leaves explicitly approved local infrastructure. Same-host loopback and explicitly configured trusted-LAN Ollama/media endpoints are permitted; Internet/cloud destinations are not.
Unexpected DNS/HTTP attempts must be explained and removed or disabled.
@@ -1098,7 +1131,7 @@ Unexpected DNS/HTTP attempts must be explained and removed or disabled.
Minimum v1 acceptance:
- app works with outbound Internet blocked,
- Ollama connection remains local,
- Ollama connection remains within explicitly approved local infrastructure,
- no cloud API key required,
- no external telemetry,
- imported Markdown does not execute script,
@@ -1139,30 +1172,43 @@ Potential later improvements:
These are not required for initial v1 unless Phase 0B reveals a specific need.
## 77. Current Security Recommendation
## 77. Selected Security Posture
The production architecture should intentionally be narrow:
The production architecture is intentionally narrow:
```text
NO:
cloud providers
hosted auth/accounts
web tools
general plugins
MCP
shell execution
QuickJS/campaign scripting
remote document fetch
runtime CDN/fonts/assets
telemetry
analytics
remote embeddings/vector stores
YES:
local browser
local app
local FastAPI app
local SQLite/files
local Ollama
local retrieval
optional local media services
local-infrastructure Ollama (same-host or approved trusted-LAN)
local lexical/semantic retrieval
optional explicitly configured local media services in the future
```
The safest implementation is not the one with the most configurable providers.
Required production defaults:
It is the one with the fewest ways story data can leave the machine accidentally.
- storyteller binds loopback by default,
- Ollama endpoint is same-host loopback by default,
- explicitly configured trusted-LAN Ollama endpoints are supported,
- arbitrary public/Internet inference endpoints are rejected or absent from normal v1 configuration,
- tokenizer assets required for runtime are packaged locally,
- browser assets/fonts are local,
- no cloud API-key UI exists in v1,
- outbound-network-blocked acceptance testing is part of release gating.
The safest implementation is the one with the fewest accidental paths for story data to leave the machine.
+54 -40
View File
@@ -1,6 +1,6 @@
# Adventure Storyteller — Specification
**Status:** Draft v0.1
**Status:** v1.0 — approved after Phase 0B architecture selection
**Purpose:** Define what the system must do, independent of implementation choice.
## 1. Product Goal
@@ -17,7 +17,7 @@ The application must preserve story continuity, authoritative world state, long-
1. **Local first**
- Primary operation must not require Internet access.
- AI inference must use a local Ollama instance for v1.
- AI inference must use Ollama on user-controlled local infrastructure for v1; same-host loopback is the default, and an explicitly configured trusted-LAN Ollama host is supported.
- Imported story/reference material must remain local.
- No telemetry, analytics, remote fonts, remote assets, or automatic external content retrieval in the production configuration.
- No cloud inference providers in v1.
@@ -36,6 +36,8 @@ The application must preserve story continuity, authoritative world state, long-
- Every accepted turn should be recoverable.
- Users must be able to return to an earlier point.
- Restoring an earlier point should preserve abandoned future history as an alternate branch rather than destructively erasing it.
- Moving backward must move the active story head without deleting retained turns.
- If the user continues differently from a moved-back head, the prior future becomes retained/disposable history rather than being overwritten.
5. **Closed-corpus authority**
- Story canon may come from:
@@ -241,7 +243,7 @@ Potential mechanisms:
- local embeddings generated through Ollama,
- hybrid lexical/vector retrieval.
The final mechanism will be selected during Phase 0 research.
Selected v1 direction: local hybrid retrieval using a deterministic lexical index plus local Ollama semantic embeddings where enabled. Lexical retrieval must remain usable if embeddings fail or are disabled.
Requirements:
- no remote vector database,
@@ -267,11 +269,15 @@ This should include, directly or indirectly:
## 12. Local-Only Security Requirements
For v1, **local-only** means the storyteller, storage, inference, and retrieval remain on user-controlled local infrastructure and require no Internet/cloud service. Components may run on more than one machine on a trusted LAN.
Production defaults must:
- bind the application to loopback unless intentionally configured otherwise,
- connect to Ollama through a local/approved endpoint,
- reject or warn on non-local model endpoints,
- bind the storyteller application/UI/API to loopback unless a later explicit storyteller-LAN mode is enabled,
- default Ollama to same-host loopback,
- allow an explicitly configured trusted-LAN Ollama endpoint for narration, state extraction, summarization, and local embeddings,
- make the configured inference destination visible/inspectable,
- reject or explicitly gate arbitrary public/Internet model endpoints,
- include no telemetry,
- include no analytics,
- avoid remote fonts and CDN-delivered runtime dependencies,
@@ -282,7 +288,7 @@ Production defaults must:
- document all outbound network behavior,
- allow operation with the machine disconnected from the Internet.
A future LAN-access mode may be considered separately.
LAN inference is supported in v1. LAN access to the storyteller web UI/API is a separate feature and may be considered later; enabling one must not implicitly enable the other.
## 13. Browser-First Interface
@@ -293,12 +299,13 @@ Expected areas include:
- campaign selection,
- story transcript,
- input composer,
- checkpoint/story-tree navigation,
- Save Point/history navigation,
- narrative state inspector,
- knowledge/library management,
- settings,
- prompt/context inspection,
- future media gallery.
- future media gallery,
- reserved local speech-to-text input affordance where appropriate.
Terminal tooling may exist for administration, migration, diagnostics, or development, but must not be the primary user experience.
@@ -332,7 +339,9 @@ The system should reserve a generic media abstraction for future:
- storyboards,
- recap images,
- multi-turn video clips,
- audio/voice.
- audio/ambience,
- text-to-speech,
- speech-to-text draft input.
Media generation must remain optional and separable from the core story engine.
@@ -344,19 +353,23 @@ The core application should eventually be able to call provider adapters such as
Media Provider
├── Image Provider
├── Video Provider
└── Audio Provider
├── Audio Provider
├── TTS Provider
└── STT Provider
```
The story engine must not depend on a specific image or video backend.
Potential local media systems can be evaluated later.
Potential local media systems can be evaluated later. Speech-to-text output must remain editable draft user input and must enter the story through the normal submission/commit path.
## 16. Export and Backup
A campaign export should eventually be capable of including:
A campaign export must preserve enough information to restore the exact active story position, including an active head that is behind the retained tip after Undo. The export should be capable of including:
- transcript,
- branches,
- active branch and active head position,
- retained/disposable alternate history,
- checkpoints,
- structured state,
- campaign configuration,
@@ -366,11 +379,11 @@ A campaign export should eventually be capable of including:
- generated media metadata,
- optionally generated media files.
The export format should be portable and documented.
The export format should be portable and documented. Importing a campaign must not silently advance the head to the newest retained turn when the exported campaign was intentionally positioned earlier.
## 17. Explicit Non-Goals for v1
Unless Phase 0 changes the decision, v1 should not require:
v1 does not require:
- D&D or other RPG rules,
- dice,
@@ -383,38 +396,39 @@ Unless Phase 0 changes the decision, v1 should not require:
- automatic online content downloading,
- image generation,
- video generation,
- text-to-speech,
- speech-to-text,
- mobile-native apps,
- hosted SaaS deployment.
## 18. Candidate Starting Projects
## 18. Phase 0 Candidate Outcome
Phase 0 will evaluate at minimum:
Phase 0 evaluated the principal candidates and selected AI-DnD as the production base.
- Open Dungeon — `newideas99/open-dungeon`
- AI-DnD — `parththakkar106/AI-DnD`
- Local Adventure Engine / ai-adventure — `CaoRuiming/ai-adventure`
- aiMultiFool
- additional credible candidates discovered during research
Disposition:
Reference-only projects may include:
- **AI-DnD** — production fork/base.
- **ai-adventure / Local Adventure Engine** — primary implementation reference for typed authoritative state events, head/checkpoint/replay semantics, and narrow local-only behavior.
- **Open Dungeon** — UX and future local-media reference only.
- **Chronicler, Interactive Fiction Framework, Gamentic, and other reviewed projects** — concept/reference sources only as documented in the research reports.
- SillyTavern,
- RisuAI,
- KoboldAI,
- Chronicler,
- other local interactive-fiction or long-memory systems.
The project will not mechanically merge candidate repositories.
## 19. Acceptance Criteria for Specification v1.0
## 19. Phase 0 Outcome and Specification v1.0 Status
Before implementation planning begins, the project must have:
Phase 0 satisfied the architecture-selection prerequisites for this specification:
- a selected base/fork strategy,
- confirmed licensing compatibility,
- confirmed local-only security approach,
- confirmed persistence/story-tree model,
- confirmed memory/retrieval strategy,
- confirmed browser architecture,
- confirmed Ollama integration model,
- confirmed campaign export/backup strategy,
- identified future media extension points,
- technical risks and tradeoffs documented.
- production base selected: AI-DnD at the pinned Phase 0B commit,
- licensing path confirmed as permissive for the selected base,
- local-only hardening requirements identified,
- story-history approach confirmed as non-destructive active-head movement over retained lineage,
- state authority confirmed as application-owned with validated proposals,
- narrative-state implementation direction selected: explicit typed events plus snapshots/cache,
- memory/retrieval direction confirmed as local, lineage-aware, provenance-preserving, and authority-aware,
- imported knowledge confirmed as a separate first-class subsystem,
- browser architecture confirmed as React/Vite + FastAPI from the selected base,
- Ollama integration validated locally,
- export/import requirement expanded to preserve active head position,
- future media extension points preserved without making media a v1 dependency.
The implementation details are defined in `TECHNICAL-DESIGN.md`. Candidate-project behavior does not override this specification.
+46 -30
View File
@@ -1,6 +1,6 @@
# Adventure Storyteller — Story Branch Semantics
**Status:** Draft v0.1
**Status:** v1.0 — behavior confirmed after Phase 0B
**Purpose:** Define exactly how Undo, Redo, Retry, Edit, Restore, checkpoints, and abandoned history should behave.
## 1. Design Goal
@@ -121,9 +121,7 @@ If the selected base architecture makes unlimited Undo substantially harder or u
> At least five consecutive Undo operations.
Technical validation during Phase 0B should determine whether unlimited Undo is straightforward.
The final implementation should prefer unlimited Undo unless there is a concrete technical reason not to.
Phase 0B demonstrated repeated non-destructive Undo well beyond the minimum five-step requirement. The selected head-cursor design should therefore support Undo across retained active-lineage history up to the root unless a later implementation defect forces a documented exception.
## 6. State Restoration on Undo
@@ -402,6 +400,8 @@ Restoring a checkpoint:
4. restores compatible summary/memory lineage,
5. prepares the story to continue from that position.
Restore itself does not need to create a new branch immediately. The existing continuation remains the Redo/retained path until the user creates a different continuation; the first divergent write then creates the new continuation.
The original later story remains retained as abandoned/disposable history.
## 21. Restore Does Not Delete
@@ -613,6 +613,19 @@ Every generated narrator take should preserve enough information to determine:
This remains true even for abandoned/disposable history until it is explicitly pruned.
## 36A. Export / Import Must Preserve the Active Head
Non-destructive Undo means retained history may extend beyond the current active head.
Export must therefore preserve both:
- the retained history/branch graph, and
- the exact active branch/head position.
Import must reopen the campaign at that active head. It must not infer that the newest retained turn is current merely because later history still exists.
Backward compatibility may treat the retained tip as the head only for older export formats that contain no explicit head coordinate.
## 37. Failure During Retry / Edit / Continue
If generation fails:
@@ -688,7 +701,7 @@ Preferred:
- unlimited Undo across retained history.
Phase 0B should determine whether the preferred behavior is already practical in the selected base.
Phase 0B demonstrated that the preferred behavior is practical in the selected base. The production implementation should retain that capability while enforcing the correct campaign-root floor.
## 42. Example: Simple Mistake
@@ -791,45 +804,46 @@ as authoritative.
If desired, the user may also edit the narration, but that is a separate operation.
## 46. Phase 0B Validation Questions
## 46. Phase 0B Findings Applied
Codex should answer:
Phase 0B established the following implementation facts for the selected AI-DnD base:
1. Does AI-DnD already support unlimited practical Undo through its lineage model?
2. How does AI-DnD distinguish retry takes from full branches?
3. Can retry/edit preserve prior futures without exposing a complex branch UI?
4. Can summaries and memories be reliably lineage-filtered after divergence?
5. Does AI-DnD state rollback restore generic state independently of RPG mechanics?
6. How difficult would it be to mark abandoned paths disposable without deleting them?
7. In Open Dungeon, what exact modules assume destructive tail-deletion semantics?
8. Can checkpoints be implemented as durable turn pointers without duplicating state?
9. What is the cost of retaining all disposable text/state history in SQLite?
10. Does any finalist currently leak abandoned branch memories into active retrieval?
- shipped Undo was destructive and therefore did not satisfy this document,
- alternate-take Retry and branch lineage were genuinely non-destructive,
- a disposable spike demonstrated head-cursor Undo/Redo without deleting accepted turns,
- writes below a moved-back head can fork through existing branch machinery,
- branch-scoped memory isolation remained correct with real local embeddings,
- practical repeated Undo across retained history worked beyond the minimum five-step requirement,
- retry/add-take paths still need to be routed through the production fork-if-behind-head rule,
- abandoned history still needs explicit disposable/inactive marking,
- export/import must carry the active head coordinate or it silently redoes an undone story.
These findings select an implementation direction; the disposable spike itself is not production code to merge unchanged.
## 47. Acceptance Criteria
The final implementation must satisfy:
- Undo restores transcript and state together.
- At least five Undo steps are available; unlimited is preferred.
- Undo restores transcript and state together by moving the active head, not deleting accepted turns.
- At least five Undo steps are guaranteed; the selected architecture should support Undo across all retained active-lineage history up to the root.
- Redo works until a new continuation is created.
- Retry preserves alternate narrator takes.
- Editing old user input creates a safe new continuation.
- Editing narrator output re-evaluates state.
- Named checkpoints remain until explicitly deleted.
- Restoring a checkpoint does not delete later history.
- Checkpoint restore may reuse the existing continuation until the first divergent write.
- Abandoned history is retained and marked disposable.
- No automatic abandoned-history cleanup is required initially.
- No automatic abandoned-history cleanup is required in v1.
- Abandoned history does not influence active summaries, memories, state, or prompts.
- Manual state/canon corrections are auditable.
- Export/import preserves the exact active head even when retained history exists after it.
- Normal UI does not require branch management.
- A future discarded-history recovery screen remains possible without schema redesign.
- A future discarded-history recovery/cleanup screen remains possible without schema redesign.
## 48. Current Recommendation
## 48. Selected Implementation Model
Use a simple linear user experience backed by non-destructive lineage.
Conceptually:
Use a simple linear user experience backed by retained lineage and a movable active head.
```text
USER EXPERIENCE
@@ -838,19 +852,21 @@ Undo
Redo
Retry
Edit
Checkpoint
Save Point
Restore
↓
INTERNAL MODEL
Parent-linked history
Parent-linked retained history
Alternate takes
State snapshots/events
Active head
Active branch + active head
Retained branch tip/future
Typed state events + snapshots/cache
Disposable abandoned history
Lineage-aware summaries/memory
Exported active-head coordinate
```
This provides recovery and correctness without forcing the user to manage a story tree.
The selected AI-DnD base supplies most of the lineage infrastructure; production work replaces destructive Undo, adds Redo/checkpoints/disposable marking, and preserves the head through export/import.
File diff suppressed because it is too large Load Diff
+96 -6
View File
@@ -1,6 +1,6 @@
# Adventure Storyteller — V1 Acceptance Tests
**Status:** Draft v0.1
**Status:** v1.0 planning/release contract — updated after Phase 0B
**Purpose:** Define black-box acceptance tests for finalist evaluation during Phase 0B and for the eventual v1 release.
## 1. Test Philosophy
@@ -229,18 +229,18 @@ Application requires:
---
## A02 — Loopback Default
## A02 — Storyteller Loopback Default
**Priority:** REQUIRED FOR V1
### Steps
Inspect application and model listener addresses.
Inspect the storyteller web/API listener address.
### Pass
Default services bind to loopback or another explicitly approved local-only address.
The storyteller UI/API binds to loopback by default.
### Fail
Application exposes privileged storyteller APIs on `0.0.0.0` by default without explicit configuration.
Application exposes privileged storyteller APIs on `0.0.0.0` or the LAN by default without explicit storyteller-LAN configuration.
---
@@ -290,6 +290,34 @@ Transcript and authoritative current state are restored.
---
## A06 — Trusted-LAN Ollama Inference
**Priority:** REQUIRED FOR V1
### Preconditions
- storyteller and browser run on machine A,
- Ollama runs on a separate user-controlled machine B on the trusted LAN,
- required models are already installed,
- outbound Internet access is blocked.
### Steps
1. Keep the storyteller UI/API bound to loopback on machine A.
2. Configure the storyteller's Ollama endpoint to machine B.
3. Verify model discovery/connection diagnostics.
4. Generate at least three story turns.
5. Trigger state extraction and embeddings/memory retrieval if enabled.
6. Restart the storyteller and resume the campaign.
7. Observe network destinations.
### Pass
- story operation succeeds through the explicitly configured LAN Ollama host,
- no cloud API key or Internet access is required,
- inference/model traffic goes only to the approved LAN host,
- storyteller UI/API remains loopback-bound,
- prompts, state, retrieved knowledge, and embedding inputs do not go to any unapproved destination.
---
# B. Basic Story Interaction
## B01 — Natural Language Action
@@ -431,6 +459,26 @@ Narrator follows campaign canon rather than imported lower-authority text.
---
## C06 — Structured State Matches Accepted Narrative Consequence
**Priority:** REQUIRED FOR V1
### Purpose
Catch semantically valid-looking state proposals that do not represent the accepted narration.
### Steps
1. Use a fixture action with an unambiguous state consequence, such as moving an item, changing location, or applying a known condition.
2. Run the turn under a realistic application context, not an isolated extraction prompt.
3. Inspect accepted state events and resulting state.
### Pass
- accepted state reflects the narration's intended consequence,
- event semantics are explicit and unambiguous,
- malformed or contradictory proposals are rejected/repaired rather than silently accepted,
- the implementation does not rely on one ambiguous numeric value being interpreted as either an absolute value or a relative delta.
---
# D. Undo, Redo, Retry, and Checkpoints
## D01 — Undo One Turn
@@ -1082,6 +1130,27 @@ Privileged local APIs do not allow arbitrary wildcard cross-origin writes.
---
## H11 — No First-Use Runtime Asset Download
**Priority:** REQUIRED FOR V1
### Preconditions
- application installed,
- Ollama models installed,
- fresh application data/cache where practical,
- outbound Internet blocked.
### Steps
1. Start the application.
2. Open the browser UI.
3. Generate the first story turn.
4. Monitor DNS/network attempts.
### Pass
The application does not attempt to fetch tokenizer encodings, fonts, scripts, stylesheets, or other runtime assets from the Internet.
---
# I. Export, Backup, and Restore
## I01 — Export Campaign
@@ -1146,6 +1215,25 @@ No external API credentials are embedded in campaign export.
---
## I07 — Export/Import Preserves an Undone Active Head
**Priority:** REQUIRED FOR V1
### Steps
1. Create a story with at least five accepted turns.
2. Undo at least two turns without deleting the retained future.
3. Export the campaign while the active head is behind the retained tip.
4. Import into a fresh data directory.
5. Open the campaign.
### Pass
- the campaign opens at the exact exported active head,
- later retained turns are still present as retained/disposable history,
- import does not silently Redo to the newest retained turn,
- Redo/recovery behavior remains coherent after import.
---
# J. Genre Independence
## J01 — Science-Fiction Campaign
@@ -1493,8 +1581,10 @@ The release candidate should not be called v1.0 until:
- any approved exceptions are documented in an ADR,
- security offline test passes,
- 100-turn long-run test passes,
- export/import recovery passes,
- export/import recovery passes, including an undone active-head round trip,
- Undo/Redo/Retry/checkpoint behavior passes,
- explicit narrative-state event/coherence tests pass at realistic context length,
- no first-use runtime tokenizer/font/asset download occurs,
- branch/memory lineage isolation passes,
- fantasy and science-fiction fixtures both pass.
+19
View File
@@ -0,0 +1,19 @@
# Planning Package Version
**Package:** Adventure Storyteller Planning Package v2
**Revision date:** 2026-09-01
**Status:** Phase 0 complete; architecture selected; production milestone plan approved for milestone-by-milestone implementation.
This v2 package supersedes the earlier planning package produced before the final Phase 0B review and the trusted-LAN Ollama deployment clarification.
Key v2 changes include:
- AI-DnD selected as the production base at the pinned Phase 0B commit.
- Non-destructive head-cursor Undo/Redo design selected.
- Explicit typed/absolute narrative-state events selected for production state handling.
- Imported knowledge separated from AI-DnD Story Cards.
- Trusted-LAN Ollama inference supported in v1 while the storyteller UI/API remains loopback-bound by default.
- Offline first-use dependencies and runtime remote assets identified as M1 hardening work.
- Production implementation divided into milestones M1-M11.
Historical Phase 0 prompts/reports are retained as evidence and should not be treated as current implementation instructions unless a current milestone prompt explicitly refers to them.
@@ -1,5 +1,7 @@
# CaoRuiming/ai-adventure — Static Architecture Analysis
**Historical status:** Static Phase 0A analysis. Phase 0B promoted ai-adventure to the primary implementation reference for state/event/head/checkpoint semantics, but not the production base.
**Project name in repository docs:** Local Adventure Engine
**Repository:** https://github.com/CaoRuiming/ai-adventure
**Date reviewed:** 2026-09-01
+2
View File
@@ -1,5 +1,7 @@
# AI-DnD — Static Architecture Analysis
**Historical status:** Static Phase 0A analysis. Phase 0B corrected the shipped Undo assumption and confirmed AI-DnD as the production base after a successful head-cursor spike.
**Repository:** https://github.com/parththakkar106/AI-DnD
**Date reviewed:** 2026-09-01
**Disposition:** Preliminary fork recommendation / Finalist #1.
+2
View File
@@ -1,5 +1,7 @@
# Candidate Inventory and Triage
**Historical status:** Phase 0A triage. The final production disposition was selected after Phase 0B; see ADR 009.
**Phase:** 0A — Static research
**Date:** 2026-09-01
+8
View File
@@ -3,6 +3,14 @@
**Date:** 2026-09-01
**Nature:** Engineering planning summary, not legal advice.
## Production Selection Update
Phase 0B selected **AI-DnD (MIT)** as the production fork/base. The production strategy therefore remains on a permissive-license path.
The project may reimplement compatible ideas from other projects, but direct source copying must still be reviewed file-by-file and retain applicable notices. GPL/AGPL projects remain concept/reference sources unless a later explicit licensing decision changes that policy.
Maintain third-party notices from the first production milestone.
## Permissive finalists
### AI-DnD
@@ -1,5 +1,7 @@
# Open Dungeon — Static Architecture Analysis
**Historical status:** Static Phase 0A analysis. Phase 0B confirmed Open Dungeon is a UX/media reference rather than the production base.
**Repository:** https://github.com/newideas99/open-dungeon
**Date reviewed:** 2026-09-01
**Disposition:** Finalist #2; strongest product/UI/media fit, but branch persistence requires material redesign.
+2
View File
@@ -1,5 +1,7 @@
# Phase 0A Status
**Historical status:** Phase 0A complete. Phase 0B is also complete; see README, ADR 009, ADR 010, and `TECHNICAL-DESIGN.md` for current architecture.
**Completed:** 2026-09-01
## Completed statically
@@ -0,0 +1,119 @@
# Phase 0B — Experiment C: ai-adventure
## C1. Tests
```
python -m unittest discover -s tests → Ran 76 tests in 1.9s OK
```
## C2. Provider abstraction — the Ollama adapter is zero code
`llm/backend.py` defines a `ModelBackend` Protocol with a single `generate`
method plus typed `ModelRequest`/`ModelResponse`. `llm/lm_studio.py` is not
LM-Studio-specific at all: it POSTs `/v1/chat/completions` and reads `/v1/models`
— the same OpenAI-compatible surface Ollama serves. `llm/scripted.py` is a
deterministic fake used by the tests.
The only change needed was two lines of `world.toml`:
```toml
base_url = "http://127.0.0.1:11434" # was 127.0.0.1:1234
name = "qwen2.5:3b-instruct" # was "replace-with-a-local-model"
```
```
python -m local_adventure doctor --world <world>
[PASS] SQLite FTS5 available
[PASS] Database schema at version 2
[PASS] World valid: Ember Hollow
[PASS] LM Studio reachable: http://127.0.0.1:11434
[PASS] Configured model visible: qwen2.5:3b-instruct
```
A real turn then committed:
```
TURN: (30ad56d4…, 1, 'committed', 'I search the observatory for the brass k',
'You rummage through the dusty shelves, your fingers brushing away
decades of cobwebs...')
MODELCALL: ('lm_studio', 'qwen2.5:3b-instruct', attempt 1, 832 bytes, errors=False)
```
`ModelSettings.backend` is typed `Literal["lm_studio"]`, so adding a second
backend means widening one literal and one factory call in `cli.py` — the
transport itself already works. Phase 0A's "MODIFY — LM Studio primary provider"
overstated this.
**Caveat:** with `qwen2.5:0.5b` the same turn failed with "The model response
could not be validated; no turn was saved." That is the validation contract
working as designed — the engine refuses to commit an unvalidated turn — but the
typed-event contract needs a reasonably capable model, where the freeform-prose
candidates tolerate a weak one.
## C3. Undo / branch / checkpoint / replay — verified independently
Run directly against `GameService` with a temporary database, not by trusting the
project's own tests:
```
after 2 turns: turns=2 events=2 has_brass_key=False
UNDO
turns 2 → 2 (DELETED 0)
state after undo: has_brass_key=True
abandoned turn id_3 still in DB: True
BRANCH from the restored point, then play on the branch
branch state: has_brass_key=True gate_open=True
ORIGINAL session leaked 'gate_open'? False
total turns in DB: 3 (nothing destroyed)
CHECKPOINT RESTORE ("before_gate")
restored has_brass_key=True
later turn id_3 still present: True
turns in DB after restore: 3
```
This is the specification's history model, working. `undo` sets
`session.head_turn_id` to the parent; `branch` inserts a new session sharing the
same head; `restore_checkpoint` replays ancestry; `_replay_to` walks
`turns.ancestry(head)` and reduces `state_events`.
## C4. Schema — the target data model
```
sessions(session_id, ..., head_turn_id, initial_state_json)
turns(turn_id, session_id, parent_turn_id, turn_number,
player_input, narration, status CHECK IN ('committed','failed'), model_call_id)
state_events(event_id, turn_id, sequence_number, event_type, payload_json) -- append-only
state_cache(session_id, head_turn_id, state_json) -- replay cache
model_calls(..., request_hash, response_hash, parsed_response_json,
validation_errors_json, prompt_eval_count, eval_count, duration_ms)
named_checkpoints(checkpoint_id, session_id, turn_id, name, UNIQUE(session_id,name))
summaries(summary_id, session_id, through_turn_id, kind CHECK IN ('scene','campaign'))
lore_documents + lore_documents_fts (FTS5)
```
Note `summaries.through_turn_id` — anchored to a turn, not a count. That is
exactly the property Open Dungeon lacks.
## C5. Coupling to the CLI
The layering is clean: `cli.py` → `app/commands.py` → `app/game_service.py` /
`app/turn_service.py` → `storage/repositories.py`. Authoritative state lives in
`state/` (events, reducer, validator) and context assembly in `context/`. None of
that imports the CLI. A browser/API layer could sit beside `cli.py` and call the
same services without moving state logic.
What it does **not** have, and would all be new work: any HTTP layer, streaming,
in-app campaign setup (worlds are hand-authored TOML + Markdown on disk),
embeddings or semantic memory, document import, media, and prompt inspection
beyond stored hashes.
## C6. Lore / FTS — reusable
`lore/indexer.py` + `lore_documents_fts` (FTS5) with content hashing and
`modified_ns` for incremental reindex, scoped by `world_id`, and a `kind` column
already carrying a classification axis. This is a good starting shape for the
Canon/Reference/Inspiration tiers in `IMPORTED-KNOWLEDGE-DESIGN.md`, and it is
deterministic and offline.
@@ -0,0 +1,146 @@
# Phase 0B — Experiment A: AI-DnD
All results are from a live server on `127.0.0.1:8321` with a fresh SQLite
database, generating against local Ollama.
## A1. Runs with Ollama locally — yes
Default `endpoint_url` is `http://localhost:11434/v1`. After setting only the
model name:
```
POST /api/settings/test → {"ok":true,"models":["qwen2.5:0.5b"]}
```
Three streamed turns produced 101/162/113 SSE events with `player`, `chunk` and
`done` types and a coherent transcript.
## A2. RPG state can be left empty — yes
`schemas.AdventureCreate.scenario_id` is optional. With it null,
`crud.create_adventure` sets `world_state={}` and creates no story cards, no
scripts and no opening action, but still calls `tree.head_branch(...)` so the
story tree exists from the start. A noir campaign created this way played,
branched, summarized and retrieved memories normally. The RPG referee is
opt-in per scenario, not a core dependency.
## A3. Retry is non-destructive; Undo is destructive
Measured on the same adventure, reading rows straight from SQLite:
```
baseline rows: 6 ids [1,2,3,4,5,6]
RETRY → new action id 7; rows 7 ids [1..7]
/variants at the tip lists BOTH take 0 (id 6) and take 1 (id 7)
UNDO → rows 7 → 4; DELETED ids = [5, 6, 7]
```
Undo removed the player action (5), the live AI take (6) **and the alternate
take that the retry had just preserved (7)**. `nodes.delete_turn` deletes every
attempt in the group on that branch and calls `memorybank.forget_node`. There is
no redo endpoint anywhere in `routers/`.
This contradicts `reports/PRELIMINARY-RECOMMENDATION.md`, which credits AI-DnD
with "non-destructive retry" (true) and generalizes it to rollback (false).
## A4. Branching is non-destructive
Adding an alternate take at a passed turn (`POST /actions/3/takes`) forked
automatically:
```
before: ids 1-8, all branch 1, depths 0-7
after : ids 1-8 unchanged on branch 1
id 9 branch 2 depth 2 (the alternate player action)
id 10 branch 2 depth 3 (its AI continuation)
/branches → [ {id:1, parent:null, fork_depth:null, own_actions:8},
{id:2, parent:1, fork_depth:1, own_actions:2, is_head:true} ]
```
Not one row of branch 1 was touched. Note the semantics: `after_id` pointing at a
node that is *live and on the path* is a no-op, not a fork. A fork happens only
when writing below a take the story has moved past (`nodes.stand_on`). So
"restore to an arbitrary earlier turn and branch" is not directly expressible
today — it must go through creating a take at that turn.
## A5. Memory isolation across branches — correct, with negative control
Memory bank enabled, summary model `qwen2.5:0.5b`, embeddings
`nomic-embed-text`, `memory_top_k=5`. Three more turns played on branch 1
establishing possessions (brass key, silver revolver, bullets).
```
memories stored: (id 1, branch 1, depth 5)
(id 2, branch 1, depth 11)
on branch 2 (forked at depth 1):
memories retrieved = {"used": [], "error": null}
leak probes over the FULL assembled prompt:
'brass key' False | 'revolver' False | 'lockbox' False
'ashtray' False | 'bullets' False
NEGATIVE CONTROL — switch back to branch 1:
memories retrieved = 2, top similarity 0.8087
'revolver' present in prompt = True
```
The empty result on branch 2 is genuine isolation, not a dead retrieval path.
`memorybank.retrieve_memories` applies `lineage.path_of(db, adventure).clause(models.Memory)`,
and `Memory` rows carry `branch_id` plus source depths. Eviction deliberately
ignores the branch clause, which is correct — it is a capacity concern.
History is likewise lineage-scoped: branch 2's prompt contained only its own
text.
## A6. Insights / prompt transparency — already sufficient
`GET /adventures/{id}/context` returns the exact next prompt broken into labeled
sections with token costs:
```
narrator 57 | ai_instructions 9 | persona 15 | plot_essentials 24
history 988 | used_memories 143 | length_hint 46
```
Per-action snapshots are stored on `Action.context_snapshot` and served by
`GET /actions/{id}/context`. This satisfies specification §11 as-is.
## A7. Scripting/QuickJS can be removed cheaply
`quickjs` is imported in exactly one file (`scripting/engine.py`). The whole
feature is reachable through two symbols (`run_hook`, `ScriptPipeline`) across
four call sites plus `routers/scripts.py`.
Disposable experiment: made `quickjs` unimportable via a `meta_path` blocker and
replaced `run_hook` with a pass-through stub.
```
app.main imports OK with scripting stubbed and quickjs absent
19 failed, 613 passed (3.0% of the suite)
```
Every failure is in `test_turn_flow_integration`, `test_take_state`,
`test_story_tree_baseline` or `test_retry_variants`, and every one fails for the
same reason: those tests **use a JS `output_js` hook as the instrument** to make
state observable, e.g. `test_play_then_undo_reverts_gold` seeds a `GOLD_SCRIPT`
and asserts `script_state == {"gold": 10}` then `{}` after undo. The behavior
under test (per-node state rollback) is intact; only the measuring device is
gone. Re-instrumenting against `world_state_after`, which is already snapshotted
per node, is the fix.
## A8. Hosted/cloud features
`MULTI_USER` is off by default and gates ten modules
(`auth`, `limits`, `netguard`, `cleanup`, `security`, `analytics`,
`routers/{auth,analytics,debug,story_cards}`). `analytics.py` is a *self-hosted*
counter writing to two local tables — no third-party tracker and no outbound
call — so it is removable rather than dangerous. `render.yaml`, the demo-key
path and `psycopg` are the hosted leftovers.
`netguard.py` is worth flagging: it refuses endpoints that resolve to
**non-public** addresses, and only in multi-user mode. That is SSRF protection
for a hosted deployment. Our requirement is the opposite — warn on endpoints that
are **not loopback**. See ai-adventure's `_endpoint_warnings` for the shape we want.
+39
View File
@@ -0,0 +1,39 @@
# Phase 0B — Baseline Results
**Date:** 2026-09-01. Host: Linux, 4 cores, 15 GB RAM, no GPU, Python 3.12.3, Node 22.23.1.
Inference: Ollama in Docker (`ollama/ollama:latest`) on `127.0.0.1:11434`, models
`qwen2.5:0.5b`, `qwen2.5:3b-instruct`, `nomic-embed-text`.
| | AI-DnD | Open Dungeon | ai-adventure |
|---|---|---|---|
| SHA | `d72f7c1b` | `b0a79f96` | `873ea918` |
| License | MIT | MIT | Apache-2.0 |
| Repo age / commits / authors | created 2026-07-20, 172, 3 | 2026-06-12, 45, 1 | 2026-07-20, 20, 1 |
| Code size | ~30.7k LOC | ~9.1k LOC | ~4.5k LOC |
| Stack | FastAPI + SQLAlchemy + React/Vite | Next.js 16 + React 19 | Python stdlib + pydantic, CLI |
| Storage | SQLite (Postgres option via `AIDND_DATABASE_URL`) | SQLite (`better-sqlite3`) | SQLite + FTS5 |
| Install | `pip install -r requirements*.txt`, `npm install` — clean | `npm install` — clean, 7 advisories (6 high) | `pip install -r requirements.txt` — clean |
| Dependencies | 41 pip + 39 npm | 335 npm | **6 pip (1 direct: pydantic)** |
| Tests | **632 passed / 236s** | **none — no script, no framework** | **76 passed / 1.9s** |
| Ports | 8000 (prod/docker), 5173 + 8000 (dev) | 3000 (app), 7869 (FLUX worker), 8188 (ComfyUI) | none (CLI) |
| Model assumption | any OpenAI-compatible; **defaults to `http://localhost:11434/v1`** | Ollama native API, or OpenAI-compatible "custom" | OpenAI-compatible; base_url in `world.toml` |
| Docker build | `docker build` succeeds (3-stage, builds SPA) | not attempted (npm path used) | n/a |
| Runtime failures | none once running; **offline blocker, see offline report** | none | typed-event validation fails on a weak model (by design) |
| Cloud/hosted surface | `render.yaml`, multi-user auth, demo key, analytics, Postgres, OpenRouter default | OpenRouter preset, Next telemetry on by default, donation links | **none** |
## Notes
- **AI-DnD** ran its whole suite green on the first attempt with no fixes. Its
default settings already point at Ollama; `POST /api/settings/test` returned
`{"ok":true,"models":["qwen2.5:0.5b"]}` with no configuration beyond selecting
a model name.
- **Open Dungeon** has `lint` and several Windows/image smoke scripts, but no
unit or integration tests of any kind. Its `local` provider only offers five
hard-coded Gemma 4 QAT builds (`src/lib/text-models.ts`), so a non-Gemma local
model must go through the "custom" OpenAI-compatible provider; that is how it
was driven here.
- **ai-adventure** is the only candidate whose full dependency closure is one
third-party package. `python -m local_adventure doctor` is a genuine
environment self-check (Python version, writable runtime dir, SQLite version,
FTS5 availability, schema version, world validity, endpoint reachability,
model visibility).
@@ -0,0 +1,164 @@
# Phase 0B — Follow-Up Checks
Four open questions settled after the undo/redo spike. Ordered by how much each
changes the plan.
---
## 1. The world-state referee under a local model
**Why it mattered:** the referee is the closest thing AI-DnD has to
specification §2's "the model proposes, the application owns authoritative
state", and §7's structured narrative state. It was never exercised in the main
round, because the RPG path was deliberately left empty to prove genre-neutrality.
If it only works with a cloud-class model, the principle the architecture rests
on is at risk locally.
**How it works.** `EMIT_RULE` asks for a fenced ` ```state ` block of **relative
deltas**, with an example (`{"player.hp": -15, ...}`) and a recency reminder
appended at the end of the prompt. `apply.py` computes `new = old + delta`, then
clamps by `max_delta_per_turn`, then by `min`/`max`, and reports every change as
applied / clamped / rejected with corrective text fed back to the model.
### Result A — in the full application, `qwen2.5:3b-instruct` sent absolute values
Playing the seeded "Bandit Camp (RPG world state)" scenario:
```
turn 1 proposed: {"player.hp": 92, "npc.gwen.trust": 25,
"player.outfit": "...arrow in shoulder", "npc.gwen.health": 90}
applied : player.hp 100 -> 100 "did not move. It is already at its
maximum of 100 ... at most 35 per turn"
gwen.trust 20 -> 40 (clamped from +25 to +20)
gwen.health 100 -> 100 (clamped)
outfit updated correctly
turn 2 proposed: {"npc.gwen.trust": 30}
applied : gwen.trust 40 -> 60 (clamped from +30 to +20)
```
The model meant "hp is now 92". The engine read `+92`, clamped it to `+35`, then
to the ceiling of 100. **The player took an arrow to the shoulder and finished
the turn at full health**, with the prose describing a wound.
This is the failure mode that matters: it is not corruption, and no validator can
catch it, because `+92` is a perfectly legal proposal. The application stayed
authoritative — which is the point of the referee — but the authoritative state
silently stopped tracking the narration.
The free-text stat (`player.outfit`) was handled correctly, because it is marked
`(free text)` and takes a whole value rather than a delta.
### Result B — in isolation, the same model gets it right
An isolated probe using AI-DnD's own `EMIT_RULE` and `extract_delta` verbatim, so
that only the model varies. A short stat guide, live values, one wound scene,
3 samples each:
| Model | Emitted a parsable state block | Correct relative (negative) hp |
|---|---|---|
| `qwen2.5:0.5b` | 0/3 | 0/3 |
| `qwen2.5:3b-instruct` | 3/3 | **3/3** (−20, −20, −15) |
| `qwen2.5:7b-instruct` | 3/3 | **3/3** (−15, −30, −15) |
So the delta protocol is **not** beyond a 3B model. The difference between A and
B is context: the full application prompt carries scenario prose, persona, a
stat guide, live values, story cards and history, and under that load the 3B
model degraded to absolute values. In a short prompt it followed the same
instruction perfectly.
> Recorded because it nearly became a wrong finding: the first run of this probe
> reported 0/3 for both models. That was a bug in the probe, not the models —
> `extract_delta` returns `(clean_text, delta)` and the harness was reading
> element 0. The table above is the corrected run.
Note also `qwen2.5:7b-instruct` sample 2 proposing `npc.gwen.trust: +75`, which
the referee would clamp to +20. Even a capable model proposes disproportionate
values, which is precisely what the clamp is for.
### What this means for the design
1. **Not a blocker for the fork.** The referee is per-scenario and opt-in, and we
are generalizing it into narrative state rather than adopting it as-is.
2. **Prefer an unambiguous state vocabulary.** A relative-delta protocol has a
failure mode that validation cannot detect, and it degrades with context
length. ai-adventure's typed events (`set_flag key=… value=…`) are explicit
and absolute, so the same mistake is impossible to express. **This is a
concrete argument for taking ai-adventure's event vocabulary rather than
AI-DnD's delta vocabulary when generalizing to genre-neutral narrative state.**
3. **Keep the emission rule close to generation.** AI-DnD already appends
`EMIT_REMINDER` at the end of the prompt for exactly this reason; whatever we
build should keep that property and be tested at realistic context length,
not just in a clean harness.
4. **Sample sizes are small** (3 per model; 2 parsed in-app turns). Enough to
establish the failure mode exists and that it is context-dependent, not enough
to rank models. A proper capability matrix belongs in the build phase.
---
## 2. Export/import silently redoes an undone story
**Status: a real defect the spike introduces into the export path. One-field fix.**
`bundle.py:_point_the_head` sets the head on import by recomputing it:
```python
depths = [n["depth"] for n in story["nodes"] if n["branch"] == head]
if depths:
adventure.head_depth = max(depths)
```
The export format carries `headBranch` (line 120) but **not** the head depth.
With the destructive undo this was correct — undone rows did not exist, so
`max(depths)` was the head. With the spike's non-destructive undo, an adventure
exported while its head sits behind the tip comes back with every abandoned turn
restored. The undo is silently undone by a round-trip.
**Fix:** add `headDepth` to the bundle (optional field, or a `v3`), and honour it
in `_point_the_head`, falling back to `max(depths)` when absent so that existing
`ai-dnd-adventure-v2` files still import. Small, but it must land in the same
change as the non-destructive undo, not after it.
---
## 3. Story cards are not lineage-scoped
**Status: a design constraint for imported knowledge, not a present defect.**
`models.StoryCard` is keyed by `scenario_id` **or** `adventure_id`, and carries
**no `branch_id` and no `depth`** — unlike `Memory`, which carries both and is
therefore filtered by the same capped lineage clause that gives memories their
branch isolation.
`context.builder.match_cards` is pure keyword matching over the adventure's whole
card list; `routers/story_cards.py` contains no lineage reference at all.
This is harmless today, because cards are authored by the user or copied from a
scenario — nothing derives a card from story content, so an abandoned branch
cannot invent one.
It stops being harmless the moment imported knowledge or any derived-card feature
lands. **Implication for `IMPORTED-KNOWLEDGE-DESIGN.md`:** any knowledge source
that can be created or updated from story content must carry `(branch_id, depth)`
like `Memory` does, and be read through `lineage.Path.clause`. Do that and it
inherits both the branch isolation and the undo isolation for free — which is the
strongest argument for a new table rather than extending `story_cards`.
---
## 4. Postgres/psycopg is cleanly removable
**Status: resolved, low effort.**
The entire Postgres surface is three places:
- `database.py` — the `AIDND_DATABASE_URL` / `DATABASE_URL` branch and
`_normalize_url`. Deleting the branch leaves the SQLite path, which is already
the default when neither variable is set.
- `migrations.py` — two helpers that tolerate SQLite returning a raw JSON string
where psycopg returns a parsed list. They simplify rather than disappear.
- `analytics.py` — `from sqlalchemy.dialects.postgresql import insert as pg_insert`,
which goes with analytics, already scheduled for removal.
`psycopg[binary]` then drops out of `requirements.txt`. No blockers.
@@ -0,0 +1,106 @@
# Phase 0B — Offline and Network Behavior
Method: a Docker network created with `--internal` (no NAT, no DNS to the
outside). The Ollama container was attached to it so the app could reach a model
while having no path to the Internet. Isolation was verified from inside the
app container before testing:
```
blocked ('1.1.1.1', 443) OSError
blocked ('openrouter.ai', 443) gaierror
blocked ('fonts.googleapis.com', 443) gaierror
```
## AI-DnD — fails offline as shipped; fine once one file is vendored
First turn on the isolated network died. The SSE stream emitted the `player`
event and stopped. Container log:
```
requests.exceptions.ConnectionError: HTTPSConnectionPool(
host='openaipublic.blob.core.windows.net', port=443):
Max retries exceeded with url: /encodings/cl100k_base.tiktoken
(NameResolutionError ... Temporary failure in name resolution)
```
`tiktoken` downloads its BPE encoding on first use, and AI-DnD calls it for
context budgeting on every turn. On the host this was invisible, because the
file had already been cached to `/tmp/data-gym-cache/9b5ad71b…` during an
earlier online run.
After copying that 1.7 MB file into the container:
```
OFFLINE TURN GENERATED: "I am Vale, an explorer. I journey alone through the
darkened depths of the lighthouse..."
```
So: a hard blocker on a clean air-gapped install, and a packaging fix — vendor
the encoding (or pre-seed `TIKTOKEN_CACHE_DIR`, or replace the tokenizer). Worth
stressing that **static analysis could not have found this**; it took an actually
isolated run.
Other AI-DnD network surface:
- **Google Fonts at runtime.** The built SPA's `index.html` still contains
`fonts.googleapis.com/css2?family=Cinzel...&family=Crimson+Pro...&family=Inter...`,
and `main.py`'s CSP explicitly allows `fonts.googleapis.com` /
`fonts.gstatic.com`. Violates specification §12. Fix: self-host three families.
- Only other remote host in backend Python is `https://openrouter.ai/api/v1`,
used as a default endpoint constant and a header-attribution host check. Both
removable with the cloud-provider path.
- `analytics.py` is a **self-hosted** counter writing to two local tables. No
third-party script, no outbound request. It records no IP, no user agent, and
hashes the user id with HMAC. Removable, and not a telemetry leak in the
meantime.
- Connection test and turns honour the configured endpoint only; no automatic URL
retrieval or content fetching was observed.
## Open Dungeon — runtime clean, build and telemetry are not
- **Next.js telemetry is on by default** and printed its notice on first start.
Needs `NEXT_TELEMETRY_DISABLED=1` or `next telemetry disable` in the
production configuration.
- **Fonts are fine at runtime.** `layout.tsx` uses `next/font/google`, which
downloads at *build* time and self-hosts. The served page contains no
`fonts.googleapis.com` reference. The trade-off is that the build needs
network access.
- Remote hosts referenced in `src/`: `openrouter.ai` (preset provider URL and a
model list), plus `ko-fi.com` and `github.com/sponsors` donation links in the
UI. Everything else is `127.0.0.1` / `localhost` (13 occurrences).
- Local story play needs no cloud service: Ollama for text, a local FLUX worker
or the user's own ComfyUI for images.
- 335 npm packages with 6 high-severity advisories is the largest supply-chain
surface of the three.
## ai-adventure — verifiably local-only
The strongest posture by a wide margin, and the easiest to audit:
- **Exactly one outbound call site in the whole codebase**: `urlopen` in
`llm/lm_studio.py`. Nothing else in `local_adventure/` opens a socket.
- **No hardcoded remote host anywhere.** The only `http` string in the package is
the scheme check in `content/models.py`.
- **One direct dependency** (`pydantic`), six packages in the closure.
- It is the only candidate that already implements specification §12's
non-local-endpoint warning:
```python
def _endpoint_warnings(config):
hostname = urlparse(config.model.base_url).hostname
if hostname not in {"127.0.0.1", "localhost", "::1"}:
return ["model.base_url is not loopback; game prompts and content will
be sent to that endpoint. Enable API authentication."]
```
- `audit.store_prompts` defaults to `false`, with prompt *hashes* stored instead.
- Tests, world validation, session creation, replay, branching and export are all
offline by construction.
## Summary
| | Local play works offline | Cloud service required | Telemetry / remote assets | Removal difficulty |
|---|---|---|---|---|
| AI-DnD | **Only after vendoring the tiktoken encoding** | No | Google Fonts at runtime; local-only analytics tables | Low — one vendored file, three self-hosted fonts, delete analytics |
| Open Dungeon | Yes at runtime; build needs network | No | Next.js telemetry on by default | Low — one env var; fonts already self-hosted |
| ai-adventure | **Yes, unconditionally** | No | **None** | Nothing to remove |
@@ -0,0 +1,105 @@
# Phase 0B — Experiment B: Open Dungeon history model
Driven live on `127.0.0.1:3111` against Ollama through the "custom"
OpenAI-compatible provider (`http://127.0.0.1:11434/v1`, `qwen2.5:0.5b`), with
its own SQLite file.
## B1. Schema — no lineage exists
`src/lib/db.ts` creates four tables: `chats`, `messages`, `characters`,
`app_settings`. The message row is:
```sql
CREATE TABLE messages (
id TEXT PRIMARY KEY,
chat_id TEXT NOT NULL REFERENCES chats(id) ON DELETE CASCADE,
role TEXT CHECK (role IN ('user','assistant')),
content TEXT NOT NULL,
attachments_json ..., image_request_json ..., generated_image_json ...,
created_at TEXT NOT NULL
);
CREATE INDEX idx_messages_chat_created ON messages(chat_id, created_at);
```
No `parent_id`, no branch, no take, no depth, no checkpoint table. Order is
`created_at` (a TEXT timestamp), with ties broken by id.
## B2. Retry / Edit / Erase — all destructive, measured
```
BEFORE (5 rows)
a504cb34 assistant "Oh, Vale, you're just sitting in that rundown ..."
e349d8b9 user "I pocket a brass key from the ashtray."
9984692b assistant "You find a worn, faded brass key on the tabletop..."
bb1ceb3f user "I drive to the address on its tag."
a6e11115 assistant "Yes, you keep going. There, you can call your ..."
EDIT e349d8b9 in place → PATCH /api/chats/{id}/messages/{id}
original text "brass key from the ashtray" still anywhere in DB? False
DELETE a6e11115?after=1 (what BOTH Retry and Erase call)
rows 5 → 4
Scan of every column of every table for the deleted tail
or the pre-edit text: none
```
- **Edit** is `UPDATE messages SET content = ? WHERE id = ?`. The prior text is
unrecoverable.
- **Retry** (`retryLastTurn`, `page.tsx`) deletes the last assistant message and
everything after it, then regenerates. The discarded attempt is gone.
- **Erase** (`eraseLastTurn`) deletes the last exchange and the tail.
- Old future turns are therefore always deleted, never retained.
## B3. What depends on the linear model
1. **`db.ts`** — 20 exported functions. `addMessage`, `updateMessageContent`,
`deleteMessageAndAfter` and `getChat` are the history four.
`getChat` returns a flat `messages` array and must become a lineage query.
2. **`story-prompt.ts`** — windows by array index:
`for (let i = messages.length - 1; ...)`, `messages.slice(dropped)`,
`messages.slice(0, dropped)`. Becomes an ancestry walk.
3. **The summary watermark is positional — the cost Phase 0A missed.**
`chats.story_summary_count` records "how many of the chat's oldest messages
the summary already covers", and `api/story/route.ts` compares
`evicted.length > stored.coveredCount`, then summarizes
`evicted.slice(stored.coveredCount)`. "The first N messages" is meaningless on
a branch. Summaries must be re-anchored to a turn id and made per-lineage.
4. **`page.tsx` is 3,991 lines** in one component; `api/story/route.ts` is 1,196.
Retry/erase/edit live there as array slicing (`messages.slice(0, cutFrom)`).
There is no take stepper, branch panel or tree view to build on.
5. **No tests.** All of the above would be changed with no regression net.
## B4. Invasiveness estimate
To reach parent-linked turns, an active head, retained abandoned history, named
checkpoints and lineage-safe summaries, Open Dungeon needs: a schema migration,
a rewrite of its persistence read/write path, a rewrite of context assembly, a
redesign of summarization, and new branch UI — with a test suite written first
to make any of it safe. This is a from-scratch implementation of the hardest
part of the specification, not a retrofit.
By comparison AI-DnD already has all of it except a non-destructive undo, and
its reads funnel through one chokepoint (`lineage.Path.clause`).
## B5. Worth reusing as reference
- The reading experience: serif prose, prose-size control, the composer's
Do/Say/Story modes.
- Inline scene images driven by a narrator tool call (`generate_image`), with a
local FLUX worker on `127.0.0.1:7869` or a user's ComfyUI on `8188`.
- Character portraits reused as reference images for visual continuity, and fed
back to the narrator as vision context. This is the most valuable idea here for
`MEDIA-EXTENSION-CONTRACT.md`.
- The worker HTTP protocol may be portable even though none of the UI is —
Open Dungeon is Next.js, AI-DnD is React + FastAPI.
## B6. Other findings
- The `local` provider hard-codes five Gemma 4 QAT builds
(`src/lib/text-models.ts`, `LOCAL_TEXT_MODELS`) and `/api/health` reports only
those as installed. Any other local Ollama model must be reached through the
"custom" provider. A genre-agnostic app wanting "pick any installed model"
must lift this.
- Next.js telemetry is enabled by default and printed its notice on first start.
- 335 npm packages; `npm audit` reports 6 high, 1 low.
+214
View File
@@ -0,0 +1,214 @@
# Phase 0B — Spike: Non-Destructive Undo/Redo in AI-DnD
**Date:** 2026-09-01
**Base:** AI-DnD `d72f7c1b`, disposable copy at `phase0b/spike/`
**Verdict:** **Passed on every criterion.** The retrofit is small, centralized,
and costs 5 of 632 tests — all of which assert the deleted-row behavior that was
deliberately replaced.
## The question
Can undo move a head cursor backward without deleting rows, does writing below a
moved-back head fork a branch, and does Redo work — without breaking
branch-scoped memory isolation?
## What the change turned out to be
Three files, **+130 / -31 lines**, and roughly a fifth of the additions are
comments and the new endpoint's docstring.
| File | Change |
|---|---|
| `app/context/lineage.py` | +26 / -3 |
| `app/routers/adventures/takes.py` | +74 / -27 |
| `app/routers/adventures/turns.py` | +30 / -1 |
### 1. Read path — cap the lineage at the head
The stored lineage records the newest branch entry **uncapped** (`max_depth =
None`, meaning "through to the tip"), and older entries capped at their fork
depths. `Path.clause` and `Path.contains` now read an uncapped entry as capped at
`self.tip`, which is `adventure.head_depth`:
```python
def _cap(self, max_depth: int | None) -> int | None:
return self.tip if max_depth is None else max_depth
```
This is the whole read-side change. It works because **every read already funnels
through `lineage.path_of()`**, so one substitution moves the entire application —
transcript paging, context assembly, `attempts.preceding`, and memory retrieval —
onto a head that can sit behind the deepest node.
A `Path.uncapped()` helper was added for the two callers that must deliberately
look past the head (redo, and the fork check).
`prefix_covering` already computed `top = self.tip if max_depth is None else
max_depth`, so the windowing arithmetic was consistent with this reading before
the change; nothing there needed touching.
### 2. Undo — move the head instead of deleting
`undo_turn` keeps its turn lock, its "nothing to undo" guard and its
fork-boundary guard, keeps `attempts.preceding` + `attempts.restore_state` for
the state rollback, and replaces the two `delete_turn` calls plus
`tree.refresh_head` with one assignment:
```python
adventure.head_depth = (first_removed.depth or 0) - 1
```
No `db.delete`. No `memorybank.forget_node`.
### 3. Write path — fork when writing below the head
`turns.fork_if_behind_head` runs in `create_action` beside the existing
`_move_to_after`. If any live node on the path sits deeper than the head, it calls
the **already-existing** `tree.branch_at(db, adventure, adventure.head_depth)`,
which creates an empty branch leaving the path at that depth and does not touch
the branch being left. When the head is at the tip it does nothing, so a story
that is never undone forks exactly as often as before.
### 4. Redo — walk the head forward
New `POST /adventures/{id}/redo`. Reads the path *uncapped*, takes the next node
deeper than the head, and advances over the whole turn (player action + AI reply)
rather than half of it. 400 when there is nothing to redo.
## Results
### Undo deletes nothing; redo round-trips exactly
Six actions across three turns, live Ollama:
```
AFTER 3 TURNS head=(branch 1, depth 5) rows 1..6 all live
visible: [1 story, 2 ai, 3 do, 4 ai, 5 do, 6 ai]
UNDO x1 rows 6 -> 6 DELETED=0 head=(1, 3)
visible: [1 story, 2 ai, 3 do, 4 ai]
UNDO x2 more head=(1, -1)
visible: [] rows still 6
REDO x3 head=(1, 5) rows 6
visible: [1 story, 2 ai, 3 do, 4 ai, 5 do, 6 ai]
```
Elsewhere in the run, **11 consecutive undos** were performed on a 23-action
story with 0 rows deleted, which clears the specification's "at least five undo
operations, unlimited preferred" comfortably.
### Writing below a moved-back head forks, and the abandoned line survives
```
UNDO once, then write a different continuation:
1 story br1 d0 live 5 do br1 d4 live <- the abandoned tail,
2 ai br1 d1 live 6 ai br1 d5 live still live, still on br1
3 do br1 d2 live
4 ai br1 d3 live 7 do br2 d4 live <- the new line
8 ai br2 d5 live
branches: {id 1, parent null, fork_depth null, own_actions 6, is_head False}
{id 2, parent 1, fork_depth 3, own_actions 2, is_head True}
visible on the new line: [1, 2, 3, 4, 7, 8]
switch to branch 1 : [1, 2, 3, 4, 5, 6]
```
This is `DECISIONS/005-branch-preserving-history.md` behavior: the abandoned
future is retained as an alternate branch rather than erased, and it is reachable
through the existing branch switcher with no new UI concept.
### Memory isolation still holds — the important regression check
A 23-action story with a real memory embedded via `nomic-embed-text` at depth 5.
Probed with terms unique to the turns an undo hides, plus terms from the turns it
keeps:
```
AT TIP (control) head_depth=22 memories_used=1 (similarity 0.689)
hidden-turn terms leaking: ['six bullets','warehouse nine','iron stairs',
'ledger','docks','office desk','inside my coat']
(correct — nothing is hidden at the tip)
visible-turn terms present: ['rainy city','desk drawer']
11 undos, 0 rows deleted (actions still 23)
AFTER UNDO head_depth=3 memories_used=0
hidden-turn terms leaking: NONE
visible-turn terms present: ['rainy city','desk drawer']
REDO back to tip head_depth=22 memories_used=1 (similarity 0.689)
```
The memory at depth 5 stops being retrieved when the head moves behind it and
becomes eligible again on redo — **without being deleted**. That is the property
the destructive undo bought by calling `forget_node`, recovered for free, because
`Memory` rows carry `branch_id` and `depth` and retrieval already goes through
the same capped clause.
> One false alarm worth recording: an early probe reported `revolver` leaking. It
> was legitimate visible history — actions 22 and 23 sit at depths 2 and 3, at or
> below the head. A second false alarm came from probing the context *after* the
> script had already redone to the tip. Both were resolved by re-probing inside a
> single script with an explicit control, which is why the table above reports the
> control run alongside the result.
### Test suite: 627 passed, 5 failed
```
5 failed, 627 passed in 184s (0.8% of the suite)
tests/test_attempt_siblings.py::test_undo_takes_every_attempt_with_it
tests/test_branch_forking.py::test_undo_stops_at_the_fork
tests/test_state_revert.py::test_undo_reverts_state_to_before_the_turn
tests/test_state_revert.py::test_undo_of_bare_continue_uses_the_node_in_front
tests/test_state_revert.py::test_undo_prunes_memory_covering_removed_actions
```
Every one asserts that rows or memories were **deleted**:
- `assert [a.type for a in adv.actions] == ["start"]` (three of them)
- `assert len(_rows(...)) == rows_before - 1`
- `assert texts == {"k"}` — the pruned-memory set
Crucially, the *state* assertions inside those same tests still pass. In both
`test_state_revert` cases, `assert adv.script_state == {"gold": 0}` succeeds and
only the row-count line fails — state rollback is intact. And
`test_undo_stops_at_the_fork` still enforces its real subject: the guard
refusing to undo into a parent branch is untouched and still returns 400.
Also worth noting for the open question raised in the recommendation: the four
memory suites — `test_memory_settling`, `test_memory_nodes`,
`test_memory_retrieval`, `test_memory_rewrite` — and `test_history_window` all
**passed unchanged**. The cursor arithmetic did not need re-deriving.
## Rough edges found, not fixed
1. **Undo can empty the story.** The original guard refuses when the newest node
is `type == "start"`, but an adventure opened with a user-written `story`
action has no `start` node, so undo walks to `head_depth = -1` and the
transcript renders empty. Redo recovers it, but the floor should be the
opening node rather than `-1`.
2. **Retry and `add_take` were left on the old path.** The spike only routed the
ordinary write through `fork_if_behind_head`. Retrying while the head is
behind the tip is not yet defined and needs a decision — most likely the same
fork.
3. **No pruning story yet.** Abandoned turns now accumulate. That matches the
specification ("retained but marked disposable; cleanup later"), but nothing
marks them disposable and no cleanup exists.
4. **Frontend untouched.** There is no Redo button and the branch panel does not
distinguish a line abandoned by undo from one forked deliberately.
## Conclusion
The retrofit lands where the recommendation predicted: one chokepoint
(`lineage.Path.clause`), one stored head that already existed
(`adventures.head_depth`), and one branch primitive that already existed
(`tree.branch_at`). The unknown was whether the depth cap would disturb the
memory and summary cursors, and it does not.
The fork decision is sound. The remaining work on this axis is the four rough
edges above plus named checkpoints, not a redesign.
@@ -1,5 +1,7 @@
# Phase 0A Preliminary Recommendation
**Historical status:** Superseded by Phase 0B runtime validation and ADR 009. Retained as Phase 0A evidence.
**Date:** 2026-09-01
**Status:** Static recommendation; pending Phase 0B clone/build/runtime experiments.
+2
View File
@@ -1,5 +1,7 @@
# Reuse Matrix
**Historical status:** Phase 0A matrix. Some ratings were corrected by Phase 0B; use the v1 technical design and ADRs for current decisions.
**Date:** 2026-09-01
Legend:
+18 -3
View File
@@ -1,7 +1,22 @@
# Phase 0A Source Index
# Phase 0 Source Index
**Status:** Static research completed 2026-09-01
**Scope:** Public repository source/docs inspection only. No local clone/build/runtime validation has been performed yet.
**Status:** Phase 0 source inventory; static links plus Phase 0B pinned finalist commits.
**Scope:** Repository/source references used during Phase 0. Runtime findings are recorded in `PHASE-0B-RECOMMENDATION.md` and supporting Phase 0B evidence.
## Phase 0B Production Selection
Selected production base:
- AI-DnD
- pinned Phase 0B commit: `d72f7c1bda0f34fccd84afb7a25c34eb01c901de`
- license: MIT
Phase 0B also evaluated:
- Open Dungeon `b0a79f96bf852be7b4e53908dff6a7f7c179da23`
- ai-adventure `873ea9180d5b611576cddb155921fc16a17ae88b`
The source list below records the Phase 0 research set.
## Finalists