Commit Graph

225 Commits

Author SHA1 Message Date
4c0b4e41c3 feat(client): pure MoveValidator — membership is the whole test 2026-07-10 12:41:45 -05:00
1b67b3984b feat(client): NpcContent.available_moves — the legality home 2026-07-10 12:39:10 -05:00
3b71fcdcc0 feat(client): CanonLog.add_quest/has_quest for offer_quest 2026-07-10 12:36:37 -05:00
1d1ae37281 feat(client): GameState move targets — remove_item, reveal/gift tracking
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 12:34:33 -05:00
9d2f93bbf2 fix(api): package /content into the image so npc content resolves 2026-07-10 12:30:36 -05:00
a2c654c047 content: author Fenn + the NPC role prompt body 2026-07-10 12:26:04 -05:00
0c3d64d068 feat(api): wire POST /npc/speak (422 unknown npc, 502 model error) 2026-07-10 12:22:07 -05:00
004cc21724 feat(api): npc.run service — thin drop-in on the model pipeline 2026-07-10 12:18:01 -05:00
76c04b45d2 feat(api): render_npc_digest — persona/knowledge/moves/utterance 2026-07-10 12:14:34 -05:00
6cd5003930 feat(api): route the npc role to its own model 2026-07-10 12:11:54 -05:00
69729f7673 feat(api): content.load_npc + content-root resolver 2026-07-10 12:08:50 -05:00
55a2ae6f19 docs(plan): bounded NPC conversation implementation plan
18 TDD tasks: server npc role (content.load_npc, routing, npc digest,
npc.run, /npc/speak, Fenn + prompt, Docker content packaging); client
NpcService + pure MoveValidator + MoveApplier + NpcContent legality +
GameState/CanonLog move targets + NPC fallback + throwaway harness;
gated Fenn live smoke.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 12:03:58 -05:00
097ac618f4 docs(spec): bounded NPC conversation (/npc/speak) design
M2 core experiment. Server owns persona+knowledge (spoilers/IP, no DB);
client owns live state and computes available_moves. Free text straight
to the NPC prompt (no Adjudicator). Sibling NpcService + pure MoveValidator;
moves validated against state, invalid dropped, prose always kept (§6).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 11:36:44 -05:00
7db1eb8f26 docs(roadmap): M2 Client HTTP loop done + live-proven; NPC conversation next
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 11:15:38 -05:00
b949cd2164 Merge feature/client-http-loop into dev
M2 client HTTP loop: the client posts its canon log to /dm/narrate, shows
the prose, harvests [FACT] via TagExtractor, and degrades to an authored
fallback (§13) on any failure. First end-to-end vertical slice, live-proven
against qwen3.5 through Docker. 67/67 client tests, whole-branch review
MERGE AS-IS.
2026-07-10 11:13:38 -05:00
5df67dcd8a Merge fix/docker-prompts-packaging into dev
Package api/prompts into the Docker image and give the Dockerized proxy a
route to a host-run Ollama. M1's narrator pipeline was proven via venv-on-
host; these were the two Docker-path gaps the client HTTP loop surfaced.
2026-07-10 11:13:38 -05:00
004bd53a39 fix(api): let the Dockerized proxy reach a host-run Ollama
After the prompts fix, /dm/narrate reached the model call and failed with
ConnectError: Connection refused — the container's localhost is not the
host, so the default OLLAMA_BASE_URL=http://localhost:11434 hit nothing.
Ollama runs on the host (M1's venv smoke reached it because uvicorn ran on
the host too; the container never had a route).

Add extra_hosts host.docker.internal:host-gateway so the container can
reach the host, and document using http://host.docker.internal:11434 in
.env.example. Verified a container reaches host Ollama's /api/tags (200).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 11:07:44 -05:00
f18d306059 fix(api): package api/prompts into the Docker image
The narrator pipeline (M1) reads role prompts from /app/prompts, but the
Dockerfile copied only api/app and docs/schemas — never api/prompts — so
/dm/narrate 500'd with FileNotFoundError on narrator.md in any Docker run.
The M1 live smoke passed only because it ran uvicorn from the local venv,
where ../prompts resolves on the host; the container path was never
exercised until the client HTTP loop drove a real request through compose.

Dockerfile now COPYs api/prompts; compose mounts it :ro so prompt edits
reload live like the app code (prompts are source code, §16). Verified by
building the image and confirming /app/prompts/narrator.md is present.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 10:29:08 -05:00
23ccc799aa fix(client): parse HttpTransport body via JSON.new() not parse_string
Whole-branch review Minor #1: JSON.parse_string pushes an engine-level
error on a non-JSON body (e.g. a gateway HTML 502), which the project's
gutconfig promotes to a false failure — the same reason FallbackLibrary
already avoids it. Consistency fix; a non-JSON body still degrades.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 10:20:26 -05:00
89afd85bb2 feat(client): throwaway narrate harness scene + live-smoke doc
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 10:12:21 -05:00
c208d3f1c4 feat(client): HttpTransport — real HTTPRequest transport (untested shim)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 10:08:20 -05:00
513225df3f feat(client): DmService narrate loop + FakeDmTransport (all branches tested)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 10:04:33 -05:00
32fa372124 feat(client): FallbackLibrary + authored narrator fallback (§13)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 09:59:54 -05:00
18a18820fe feat(client): net primitives — DmResponse, NarrateResult, DmTransport, ProxyConfig
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 09:54:03 -05:00
b4d22f5c5f docs(plan): Client HTTP loop (M2) implementation plan
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 09:51:49 -05:00
3a85d11439 docs(spec): Client HTTP loop (M2) design
First M2 vertical slice: CanonLog -> HTTP -> /dm/narrate -> prose ->
screen -> fact-harvest, with a §13 degraded-DM fallback on any failure.
Pure DmService core behind an injectable DmTransport seam (headless-
testable); throwaway harness scene; one authored fallback line under
client/content (renderer owns the file's home).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 09:33:44 -05:00
4ae9cdbe63 docs(roadmap): flesh out sequencing into milestones (M0–M4)
Regroup the flat status list into milestones with a 'what this proves' goal, and
tag every item with its §2 side (state/text/both), dependencies, and milestone
goal. Sequencing calls locked: M3 leads with the playable dungeon
(Adjudicator→Combat→Luck) before the comedic roles (Improviser/Banter); streaming
deferred until the M2 dialogue experiment proves aliveness.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 09:10:16 -05:00
1189d2e24d docs: mark Narrator/Ollama pipeline complete (plan steps + roadmap)
Plan status → COMPLETE, all 8 tasks/44 steps checked. Roadmap: Narrator pipeline
, Client HTTP loop promoted to ▶ next.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 09:00:34 -05:00
ddc00d42da Merge feature/narrator-ollama-pipeline into dev
Narrator/Ollama pipeline (server): shared model-call pipeline (config, role→model
routing, prompt loader + curated canon-log digest, Ollama /api/chat client with one
retry, §10 JSON-lines call logging) wired through the Narrator role — /dm/narrate is
now real (200 prose | 502 model_error | 422 invalid log). 49 passed/1 skipped; live
wire proven against qwen3.5. Final-review fixes: best-effort logging, non-JSON 200 →
ModelError.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 08:52:34 -05:00
f7c51b79da fix(api): best-effort call logging + non-JSON 200 → ModelError (final-review)
- call_log._default_write: swallow any exception from the default file/
  stdout sink and report to stderr, so a log-write failure (disk full,
  bad permissions, misconfigured CALL_LOG_PATH) never turns a successful
  narration into a 500 (charter §13). Injected write= sinks (used by
  tests) are left to surface their own errors.
- ollama_client.chat: catch ValueError alongside httpx.HTTPError in the
  retry loop so a 200 response with a non-JSON body (JSONDecodeError is
  a ValueError subclass) counts as a failed attempt and falls through to
  ModelError after the one retry, instead of escaping chat() uncaught
  (charter §12 — one retry, then ModelError, nothing else escapes).
- main.py: update stale docstrings — /dm/narrate is now fully wired
  (routing + model call + logging); the other four roles remain stubs.

Regression tests added for both fixes (TDD: watched RED, then GREEN).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 08:45:39 -05:00
470ccb2935 test(api): gated live Ollama smoke (--run-live) + env docs
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 08:36:56 -05:00
45d5f0360b test(api): keep test_endpoints hermetic after /dm/narrate wiring
/dm/narrate now makes a real Ollama call (Task 7). The role-endpoint
loop tests in test_endpoints.py had no mock, so every default pytest
run was silently hitting a live Ollama instance on localhost:11434 —
breaking the hermetic-suite constraint. Add an autouse fixture that
stubs narrate.ollama_client.chat and narrate.call_log.record so all
five endpoints stay hermetic; assertions (200 on valid, 422 on
invalid) are unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 08:31:42 -05:00
5a35802075 feat(api): wire /dm/narrate — 200 prose | 502 model_error | 422 invalid log
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 08:31:38 -05:00
2027e7a9b9 feat(api): narrate service — render→route→call→log, returns raw prose
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 08:27:36 -05:00
2558e2a2a0 feat(api): author narrator system prompt (voice, [FACT] rule, no-numbers, no player agency) 2026-07-10 08:23:31 -05:00
15995c57ee feat(api): §10 JSON-lines call logging (full prompt + seed, injectable sink)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 08:20:00 -05:00
be133c4f3a feat(api): Ollama /api/chat client with one retry + typed ModelError
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 08:15:45 -05:00
4d8a19353e feat(api): prompt loader (header split) + curated canon-log digest
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 08:11:57 -05:00
42b3720721 feat(api): config + role→model routing (qwen3.5 narrator default, think off)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 08:08:56 -05:00
ea2cfafd34 docs(plan): Narrator/Ollama pipeline implementation plan
Phase 1 (config, routing, prompts+digest, ollama_client, call_log) + Phase 2
(author narrator.md, narrate service, wire /dm/narrate, gated live smoke).
Mock boundary via httpx.MockTransport; qwen3.5 default with think:false.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 16:32:08 -05:00
ec01ce5c9b docs(spec): qwen3.5 narrator default + think:false; add model probe
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 16:25:17 -05:00
959abc79eb docs(spec): Narrator/Ollama pipeline (server) design
Shared server-side model-call pipeline (Ollama client, role->model routing,
prompt loader + canon-log digest, §10 call logging, retry->typed 502) wired
through /dm/narrate as the first role. Non-streaming; server returns raw
prose, client extracts tags. Two phases: pipeline foundation, Narrator online.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 15:58:03 -05:00
b15e81e242 docs(roadmap): fill in sequencing — Narrator/Ollama pipeline next
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 15:56:42 -05:00
1f39ec709e Merge client canon log engine (Plan B)
Client-side GDScript engine that constructs the canon log at new-game
(origin + world + character creation) and maintains its invariants turn
to turn. Headless GUT-tested logic layer (48 tests): typed RefCounted
model with invariants-in-the-model, Luck-centric GameState, ContentDB
mirroring the api id-resolver, NewGame.construct, and a pure §12 tag
extractor. Binds to the Plan A canon log contract.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 15:34:41 -05:00
bc83e92f44 chore(client): Godot rewrote project.godot header on open
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 15:34:41 -05:00
63ce3efb74 fix(client): reject empty player name in NewGame.construct (contract minLength:1)
Whole-branch review flagged that NewGame.construct validated class and
origin refs but not player.name. The canon-log schema requires
player.name with minLength:1, so a missing/blank name previously
returned ok:true and produced a log the API would reject with 422.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 15:03:18 -05:00
2d52d74963 chore(client): track generated .uid for test_round_trip.gd
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 14:51:48 -05:00
2673126003 test(client): round-trip + contract-invariant integration on a constructed log
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 14:51:42 -05:00
5388979bf1 feat(client): TagExtractor — pure regex parse/strip of §12 tags
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 14:46:11 -05:00
7c5eaf3ba3 test(client): cover non-companion disposition routing (§6/§7 boundary) in NewGame
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 14:42:12 -05:00