feat(talk): real-world topics — an off-loop pool the host can mention [spec 13] - #203
Merged
Conversation
The talk and reply prompts rendered the clock bare ("It's Monday
2026-08-31, 2:28 pm.") with no word on what it was for, so the host read
the date and minute out on every beat. It now arrives with its usage:
bearings, mentioned only when the hour or day lands in what is already
being said, never as an opener or a time-check. Same source line for the
talk builders and the reply status block.
The profile block also injected the fading ledger's [seen YYYY-MM-DD] /
[stable] tags verbatim, which the host read as content ("you told me on
the 31st"). They are stripped at injection; the file keeps them, and the
recall block's dates stay — those are deliberate.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018KVNGUoCkDfWFdLwkGWsAi
… [spec 13] The self-initiated talk task had no way to learn anything: one terminal tool, no built-ins, a persona and a transcript. So it invented its topics, and a cold boot landed on the same cozy imagery every time (#44). Spec 13 gives the host real material without touching the live loop. A bounded WebSearch task (the harness gains a `builtins` seam, bounded via `tools` and pre-approved via `allowedTools`) fetches a handful of items — title, a two-to-three-sentence gist in the persona's language, a kind — into cache/rwt.json. The refresh runs the way the compactor's fold does: poked at every boundary, single-flight, unawaited, total. A roll shaped like RandomCadence decides whether a talk batch is offered one at all; anchors and the coda never are. The prompt renders it as material, not an assignment. The knob rides the settings layer (file < --no-rwt), a `rwt` field on change_settings so "stop with the news" works, and env numbers for the roll and freshness. The taste half is $MURMUR_HOME/rwt-policy.md, the music-policy shape. Language is read where the host reads its own: the override, else the persona's "speak in X" clause — the machine locale that seeded it may have changed since, and on this machine it had. Smoked at the real seam: a fetch wrote 6 entries in ~100 s; a make dev run logged rwt.offer for a batch whose beat carried the item as a thread. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018KVNGUoCkDfWFdLwkGWsAi
…not content [spec 13] The first draft forbade "a headline read out" and let the host "leave it", and the measured result was the item scrubbed to mood: a Netflix release became "a friend wanted to watch a show". That is the #44 attractor with a fig leaf — the material was there and none of it reached the air. A radio names things. The line now asks for the title, who, where, when, said in a sentence or two the way a host says it, then carried past; what it forbids is the newsreader's rundown, the "here is the news" frame, the list. The default policy asks the fetch to keep the hard nouns for the same reason. Spec 13 §2.5 records the measurement; #202's first box matches. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018KVNGUoCkDfWFdLwkGWsAi
… the wall clock The fixture records at 2026-09-01 and reopened the store on the real clock, so the 48 h recent window kept the row for two days and then aged it out — green on the day #196 landed, red from 2026-09-04 on every PR. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018KVNGUoCkDfWFdLwkGWsAi
…map; re-point line 2 at spec 13)
wine-fall
added a commit
that referenced
this pull request
Sep 4, 2026
"Say real things" shipped: the off-loop topic pool, weighted by the listener's spoken language and their timezone, landed via #201 and #203. The file's own rule is that a delivered line is deleted rather than archived, so the row and its section go, and spec 13 becomes the record — its two pointers into the deleted section are retargeted at itself. What survives the line is tracked elsewhere: #202 joins the by-ear list, and #44 closes on that issue's first box. Line 0's other half is resolved too, though not the way the file predicted. It called for the parallel clock and grounding work to be reconciled into one change; what actually happened is that #165 landed the grounding, #191 then landed only the weekday-and-date half of #162, and the play-progress half was deliberately dropped because the look-ahead makes a countdown false by the time it airs. Recorded as a decision, not a merge, so nobody re-proposes the progress fields. What is left of line 0 is the dropped first line, unchanged. Two cross-references pointed at the deleted line. #98's eval track is the sharper of them: line 2 shipped a prompt whose whole job is stochastic, and the only thing that caught its first draft getting that wrong was a person reading a dev log — so the eval is overdue, not upcoming. Claude-Session: https://claude.ai/code/session_018KVNGUoCkDfWFdLwkGWsAi Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements specs/spec13/13-real-world-topics.md (new). Stacked on #201 (the clock-as-bearings fix); merge that first.
What
The self-initiated talk task had no source of information — one terminal tool, no built-ins, a persona and a transcript — so it invented its topics and landed on the same cozy imagery every cold boot (#44; the listener's last 30 ledger topics were all afternoon light / warm cups / fridge hum). ROADMAP §2 set the line: real material, off the live loop, weighted by language and timezone, region never stored.
Pool —
src/rwt.ts:cache/rwt.json, entries{id, title, gist, category, fetchedAt, used}, 48 h expiry, stale after 6 h;take()marks used and persists. Fetch —Brain.fetchTopics:runTaskwith a newbuiltinsseam (WebSearchontoolsandallowedTools), terminalsubmit_topics(zod), 12 turns, Haiku, a researcher framing that is never the persona. Roll —RwtRoll,RandomCadence's shape (p 0.35, minGap 1, maxGap 4 batches;MURMUR_RWT_*env knobs). Director — oneoffer()per ordinarygenerateTalks(neveranchor:*, never the coda),maybeRefresh()poked at every boundary beside the compactor. Prompt —rwtLine: "Something from out in the world, if you want it … Material, not an assignment". Knob —rwtEnabled(settings file <--no-rwt),change_settings({ rwt })+ a line in the steer rule so "stop with the news" works; the stub brain fetches nothing. Taste —$MURMUR_HOME/rwt-policy.md, the music-policy shape, seeded on first use.Language is read where the host reads its own. The first real run produced English gists on a Chinese persona:
detectLanguage()followsLANG(nowen_US), but the persona was seeded when it said Chinese and records that as "Always speak in Chinese (Mandarin)". So the request language issettings.language, elsepersonaLanguage(persona)(the seed's "speak in X" clause), else the persona's own first line of prose held up as the example — a generated persona is written in the listener's language and never names it. Never the machine locale. Spec 13 §3.5 records the measurement.This is the fourth network call;
DESIGN.md's "three" is already stale per #104 and is not edited here.Evidence (the deterministic seams, not the model's word)
Real
fetchTopicsthrough the SDK (scratch smoke, language "Chinese", tz Asia/Singapore): 6 entries in 105 s, gists in Chinese, weighted to the region (Taiwan defence budget, a Singapore animated film, Asia Cup cricket). Amake devrun (--no-music --max-segments 10,MURMUR_RWT_P=1) in.dev/dev.log:That beat proved the seam — and disproved the first draft of the line. The material reached the batch and none of it reached the air: a Netflix release scrubbed to "a friend wanted to watch a show", the #44 attractor wearing a fig leaf. The prompt had forbidden "a headline read out" and let the host "leave it". A radio names things. The second commit redraws the line on register, never content: name the thing — the title, who, where, when — say what happened in a sentence or two and what you make of it, then carry on; what stays forbidden is the newsreader's rundown, the "here is the news" frame, the list. The default policy asks the fetch to keep the hard nouns for the same reason. Spec 13 §2.5 records the measurement; #202's first box matches. The by-ear questions (does it sound like a host, is the proportion right) are #202.
Re-run with the redrawn line (2026-09-04, same flags),
.dev/dev.log:Place, numbers, dates, the cause — named, in the host's own voice, one item per beat, no rundown. Titles and gists now arrive in the persona's language (the pool this run left behind is Chinese and stays).
The third commit is a one-line test fix outside spec 13:
test/memory-fold.test.ts(from #196) reopened a store on the wall clock after recording on a fixture clock of 2026-09-01, so the 48 h recent window went red on 2026-09-04 for every PR. It now reopens on its own clock.Tests (test-first, per step)
test/rwt.test.ts(pool expiry / take / persistence / refreshDue; roll determinism; single-flight refresh, failure posture, avoid list; the fetch task's builtins, bound, framing, andsubmit_topicstermination),test/prompts.test.ts(rendering present/absent, never on anchor/coda, the fetch prompt's contract lines, the steer rule),test/brain.test.ts(agenticOptionsbuiltins; stub returns nothing),test/config.test.ts/test/app.test.ts/test/steer-tools.test.ts(flag, file, env knobs, store seeding,change_settings({rwt}),buildRwtwiring, language resolution),test/persona.test.ts(personaLanguage),test/director-rwt.test.ts(one offer per ordinary batch, none on anchor/coda, knob off, boundary poke).Peer review
Peer review (codex gpt-5.6-sol): 3 findings, 3 applied, 0 dismissed — language is never taken from the machine locale (a localized persona is held up as its own example, and a manner clause is not a language); an unwritable cache logs instead of throwing on the talk path; expired entries no longer block a recurring title. Each has a regression test.
Follow-ups
AI coding brief
Original request: second of two sequential PRs from a finished design — write spec 13 (English, coding-agent form, spec 07's header structure), then build it test-first under
murmur-build-specwithout waiting for review. Every design point was pinned in the dispatching session (pool/fetch/roll/knob/policy/debug seams).Manual interventions: none in this session. One decision taken mid-task: no pane row for the knob (spec 12's pane stays at eight; flag/file/conversation reach it) — logged in the spec's non-goals.
Retro: the real-seam smoke found the language bug the unit suite could not (persona-recorded language vs. machine locale); running the dev-log check before the PR, as the brief demanded, is what caught it. Next time, name "where the host reads its language" in the brief as a file:line, not a prose pointer.
🤖 Generated with Claude Code
https://claude.ai/code/session_018KVNGUoCkDfWFdLwkGWsAi