Flip considering-state to done+merged+live-confirmed. Strike the streaming
item — the considering-state reframes the wait and is tested/live; real
streaming's transport rework + mid-stream failure surface isn't worth it for
the POC. Streaming spec retained as a record (banner SHELVED). Includes the
human's folded-in further-down roadmap ideas.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Considering-state + transport timeout (M2 anti-freeze; streaming parked).
While a model call is in flight the client rotates authored in-character
'the Master considers…' lines (§13); HttpTransport now sets a request timeout
(35s, rejects <=0) so a hung call degrades to the fallback instead of spinning
forever. Human-confirmed working live, including the degrade path (API down →
fallback). 5 tasks, whole-branch review MERGE AS-IS. Rotation tuned to 3.0s.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
By-hand cold-start test showed 3 phrases before the response; 1.5x the
cadence so each line stays on screen a touch longer.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5 TDD tasks: ConsideringPhrases (pure) + authored considering.json;
ProxyConfig.request_timeout_seconds (default 35s, reject <=0); HttpTransport
applies it (closes the infinite-hang gap); ConsideringIndicator node shim
(Timer rotates a Label); wire both harnesses around the await.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Pivot M2 anti-freeze from streaming to a lighter considering-state: a rotating
in-character 'the Master considers…' indicator (authored content, §13 spirit)
on the proven non-streaming loop, plus the HttpTransport request-timeout fix so
a hung call yields to the fallback instead of spinning forever. Meets §14's
anti-freeze goal by reframing the wait; near-zero new failure surface. Streaming
design PARKED (banner + roadmap ○), revisit if playtest shows the wait hurts.
Conscious §14 deviation recorded per §18.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Real token streaming to hide the ~2s call (§14). Proxy emits its own minimal
NDJSON (delta/done/error); /dm/narrate becomes a 200 stream (422 stays sync,
model errors become an in-stream frame). Client reworks to an HTTPClient
poll-loop StreamTransport behind a testable seam; hold-back tag filter;
keep-partial mid-stream degrade; folds in the HttpTransport timeout fix.
Narrator first; NPC fast-follow.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NPC harness built a canon log with an empty player.luck_descriptor, which the
schema rejects (minLength 1) → /npc/speak 422. Build the player through
LogPlayer with a non-empty §7 fortune line. Human-confirmed working live.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The throwaway harness hand-built a minimal canon log and left luck_descriptor
as the default "", which fails the canon-log schema (minLength 1) → /npc/speak
422s and the harness silently degrades to fallback. Build the player through
LogPlayer with a non-empty §7 fortune line. Verified: request now 200s and Fenn
answers in-voice.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bounded NPC conversation (/npc/speak), M2 core experiment. Server role is a
thin drop-in on the M1 pipeline (persona+knowledge server-side); client adds a
pure MoveValidator + MoveApplier over the proven DmService loop — validates
moves against live state, drops invalid, keeps prose (§6). Live-proven vs
qwen3.5 (Fenn in-voice, grounded in knowledge, no Luck leak). Live smoke caught
+ fixed two TagExtractor tag-format drifts. 18 tasks, all reviewed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
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.
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.
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>
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>
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>
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>