Repository navigation
Releases
alpha 10.1.1 — A thing examined tells you what it is like
Six commits since 10.1.0, all found on one story: the owner's Doctor and
Hinami on the moonlit beach (chat 152), turns 4337–4338, 2026-09-21.
A body that checks a thing is answered from the thing's record. The Doctor
stood at the TARDIS console with both hands on a panel "checking what the scar
and the landing cost her" for two beats and was told nothing. Read against each
other, the stages showed three gaps and none in the stage the symptom appeared
in: the console was a room anchor with a description and no record; both
console rows were categorised contacts, attention, so the objects hand never
received a work item and entities wrote {} twice; and nothing on the view
path rendered a thing's state at all — _visible_things used it only as a
placement key. Now a body READS a thing by having its hands on it or by
standing at its station with light to see by (perception._things_read_by,
both admissions subtracting from the contact and station ledgers), and is told
the thing's own state, rendered clinically in both packs
(composer.thing_reading_text, thing_reads_touch / thing_reads_sight). A
bystander gets the act, never the readings. Verified on a copy: recomposing
turn 4338 put the console's readings in the Doctor's view under his hands and
none of them in Hinami's. The owner: "not so much a bug but a missing feature."
The sheets ask for the record. The interpret sheet's three sibling clauses
about work on a thing were reduced to one that also covers a read — the one
attempt that leaves a thing as it was and still routes entities — under the
sheet's hard length budget; the reduction dropped the boundary's counterfactual
("if nothing else happened") on the first pass and a guard caught it, which is
the reduction rule working. The objects sheet writes harm short of
destruction into state in the beat that inflicts it and authors a reading
from three things only: what the thing is and can do, where it is and has
been, and what its record already holds. A thing whose record holds no harm
reads as sound. The establish sheet mints a vehicle or mechanism WITH a state.
The plan tier carries an initial condition. A thing's plan item takes
brief.state (plan_entity), which materialize_plans_in_sight copies onto
the thing when it stands it up and plans_in_view hands to the Director's own
render. A later change to what a thing can do — a sensor welded on and wired to
a readout — is an ordinary entities redeclaration, and _merge_entity
merges state key-wise, so a new key joins the record without erasing it.
The Director sees this frame's paradox again. Since alpha 9 the Director
read one payload and paradox rode on it. The split kept the key on the full
context dicts of both stages and built a separate lean payload for the model
afterward, and neither lean payload carried it — so the interpret author and
the resolve author wrote every beat of an unfolding paradox blind to it while
the commit-side consequences kept applying. Alpha 9.10.2 never mentioned
paradox in any prompt, so the key alone is the contract; it is restored on
both lean payloads. The owner's ruling the same day: detection stays code, the
consequence belongs to the Writers' Room as a plot package, and a package that
lands a turn after the violation is fine.
A logged line echoed inside another speaker's quote is not a dispute.
Hinami asked "How does she move?"; the Doctor's reply contained the words
verbatim; the prose is second person, so her line had no quote of its own and
its only match was inside his. Two speakers in one region read as a dispute and
his whole speech went uncoloured. The region's own text is the tie-break: the
line whose body IS the quoted line delivered it, and anything else that matched
inside is an echo. Two fragments from different speakers stay a dispute, and
two people saying the same short line in two regions now each get their own,
which the old code's comment promised and did not do. Six node-executed tests;
three fail on the old matcher.
The shell's cache revision is the bundle's content. index.html's
?v= token was hand-bumped, which means it was not bumped: chat.js had
changed in eleven commits since the token last moved, so a browser that held
the old file never asked again, and a fix "still" did not work after a server
restart because the server was not what held the stale copy. The shell is now
served with one hash over every script it names, recomputed only when a named
file's mtime or size moves.
Thirty guards the Director split superseded are retired. Every one of the
31 failures carried since 10.0.0 asserted against the monolithic Director:
phrase checks on sheets whose clauses moved into specialist chunks (the eight
TIME-clause tests, mouth engagement, the sleeper's room, the background-dialogue
occasion, the presence-knowledge rule, the re-stamped tags, the comms
vocabulary, the weather discipline guard), payload keys the split moved
(author_notes, world_pressure, director_interpret, the null state_diff
example), the extension seam calls, the crowd example, and a deleted
prose-author leaf. The thirty-first was paradox, which was a real gap and is
fixed above rather than removed. tests/test_time_advances_with_fiction.py
goes entirely.
Known failures
1 test fails on this tag,
test_charter_registry_cache::test_frames_cache_separately_and_ambient_follows_the_pipeline,
the same parallel-runner flake recorded at 10.1.0: it passes in isolation and
on the 10.1.0 tree, and failed in two of four full parallel runs measured for
this release. 16,090 pass.
alpha 10.1.0 — A character who walks away gets a life, and the world answers
Twenty commits since 10.0.1, most of them found by launching stories with no
player in them and reading what the characters actually got.
A character outside the player's reach gets a frame of their own. Until
this, walking away was a pause: her scene was preserved exactly as she left it
and she stood in it, beat after beat, until somebody played her. Measured
before it landed (the Millbrook run, 2026-09-17): a courier crossed to the far
shore and stood on the landing for three player beats, her frame's scene
byte-identical each time, and formed not one memory. The trigger is RANGE and
not a declared zone — a cast body the beat does not attend to has left, read
off the same set the Director is shown so the payload and the range cannot
drift. Her beat runs the ordinary pipeline minus the two stages that exist
because a player is watching: no interpret call, because there is no declared
line to read, and no narrator, because nobody is reading the page and prose no
eye receives is the one way this could leak. The town can answer her —
background_react belongs to a bubble's beat, since a bubble's beat is a beat
somebody is in — and a bubble's turn 0 is never treated as an opening, which
had been placing and dressing a player who did not exist.
The Writers' Room designs the ground a story opens on. An inhabited
location used to be one utility call that asked for the whole object at
once — map, institutions, posts, upkeeps, economy, naming law — with one
attempt and no way to be told it was wrong; chat 136 spent an entire 16k
budget on a reasoning trace and returned JSON cut off inside a washroom. It is
now the Room's work, drafted through tool calls it can be corrected between.
Claude Code is a provider kind. A host signed in to Claude Code can play
under that login with no API key stored in the engine: one call is one
process, tools and slash commands and MCP off, no session persisted, run in an
empty scratch directory so no CLAUDE.md is discovered. It is a DEVELOPMENT
backend and the hint and the guide both say so.
What the world was failing to answer
Four things the engine knew and did not say, each found by reading a beat as
fiction rather than as a diff.
A beat now costs what happened in it. state_diff.time belonged to the
spatial specialist, and a specialist receives the ledger rows selected for it,
never the beat — so its own sheet asked it for "the sum of its parts" while the
fan-out handed it a subset of the parts. It declined, which is the correct
answer to that question. Measured 2026-09-20: the channel was in that hand's
scope on 38 of 60 playerless beats and 50 of 118 live beats since 2026-09-15
and was written on 0 of either, while the prose author routed the category 0
times in 3,000 stored resolve variants. Every beat fell to
UNCLAIMED_BEAT_SECONDS = 10.0 — 0.0028 hours against the charter's own "a
beat is minutes of story" — so after sixty beats the off-screen clock still
stood at the presim's 72.0 and had never ticked once. The ask moved to the
agent that cuts the steps: each ledger row carries seconds, and code sums
them. Verified in play: 24 of 24 beats priced, durations 2–15s against a 10s
floor, and the charter clock advancing for the first time.
A thing somebody sets down can be seen. scene.entities had no sight path
to any composed view at all: the furniture reader answers for a room's
ANCHORS, and perception's only walk over scene entities was the scent loop,
gated on entity.scent. A thing reached a mind only through a channel that
owned it for some other reason — attire, a pose, a contact, a smell. Measured:
Sal Weatherby laid a copper coin on a taproom counter, the commit recorded it
exactly, the coin appeared in 0 of her 24 views, and two beats later her
own view named the host's coin pouch and not her coin, so she drew a second
coin and paid twice. A stationed thing inherits the visibility of the anchor it
stands at, so the cone, the line, the occluder and the light are already
decided and nothing new judges what an eye reaches.
The sky reaches the body under it, and a canopy is cover.
weather_for_room had already reduced the weather per room and per channel —
can this body see it, is it landing on them, does the wind reach — on its own
grounds that "a cellar under a downpour sees nothing, feels nothing and may
still hear it". No mind read any of it: composer.py contained the word
"weather" zero times. A character in a freezing thundersnow gale was told the
room was dim and that it was loud. Sight and touch now carry it separately,
hearing is left to the soundscape that already folds rain in, and exposure is
asked of the STATION rather than the room — a market square is open, so every
body in it stood in the rain including the one under the stall canopy.
A planned thing standing where somebody stands becomes real. The plan
tier's handoff was "the Director renders a plan when it comes into view", and
the causal Director's sheet forbids that row in so many words: "initial
standing descriptions and unchanged background facts are context". A police box
on sand is not an event, so a plan was rendered only when a body TOUCHED the
thing — which nobody does to a thing they cannot see. Measured in the owner's
own story: a granted mandate, a complete plan with a look and three truths,
both bodies standing in the room it named, and scene holding no TARDIS after
four beats. It is stood up deterministically now, placed on a cell so a map can
draw it, and the night it stands in is no longer pitch black — the sky's
account had no moon in it, so a beach whose own weather read "a clear black
night sky strewn with stars, the full moon high" composed dark in every view.
Simulation reach is gone
The dialogue panel's off-screen cognition ladder and its four living-world
approaches are retired. Charter, the Writers' Room and causality bubbles
superseded every rung; the antagonist ladder went last, as the owner's "lesser
version of the causality bubbles". What replaces the dial is max open
threads — how many bubbles may EXIST, refused at the opening and never by
pausing an open one, which is a different cap from the one struck down in
September ("a character should never freeze unless they've been made dormant").
Room drift, due consequences and a place's owed history are unconditional now,
on the argument that retired rumour transport before them: a world whose fires
do not burn down and whose causes do not land is not coherent at any setting.
The retired config KEYS survive, defaulted and unread. Deleting them was tried
and measured: 120 readers index offscreen_life directly, several inside the
commit path, and turns rolled back. The readers come out first.
Known failures
32 tests fail on this tag; 31 of them also fail on 10.0.1 and are the set
triaged in 10.0.0. The thirty-second,
test_charter_registry_cache::test_frames_cache_separately_and_ambient_follows_the_pipeline,
is a flake under the parallel runner rather than a regression: it passes in
isolation at both tags (measured twice each), it failed in one full parallel run
here and passed in another the same afternoon, and it did not fail in the
10.0.1 run measured for this release.
alpha 10.0.1 — Keep the beat
Five repairs on the day 10.0.0 shipped, every one of them found by launching
stories rather than by reading code.
A character's beat is no longer thrown away because it arrived
incomplete. Chat 135 lost a reroll and two resumes to one turn: each
attempt omitted a required field or two, and each time an appraisal, a
sequence, a speech, a revised belief, a progressed intention, a relationship
delta, three mind models, a learned association and three memory operations
went in the bin with them. The repair pass, which said in its own reasoning
that it knew exactly which sections were missing, failed the same way.
The contract is unchanged: the schema still declares all fourteen fields
required and the card still teaches them. What changed is what happens to an
answer that does not carry them all, and the line is the owner's bar --
all a character fundamentally needs to do is remember, and know why it did
the things it did. Twelve fields are now simply allowed, filled empty and
reported nowhere: the eight learning lanes, because a character does not
revise a belief or move a relationship every time it speaks and an absent
lane says the event did not happen; plus demeanour, salience, floor control
and effects, which touch neither capacity. Four still fail first so the
repair gets its chance -- what the character did, and why -- and are filled
only when the alternative is discarding the beat, loudly. Replayed against
chat 135, the character keeps every sequence element, the belief, the memory
and the relationship, and loses the decision record alone.
Expression is an action and renders no matter how temporary. A player's
flush, look and stiffening tails reached nobody: the body hand rightly
declined to record a momentary reaction as a lasting condition, the verifier
read that silence as proof the act never happened, and perception dropped it
with a bare continue that recorded nothing. A thin ledger is a fact about
the ledger; only a requested patch the world does not show is evidence an act
failed. The audit still records the gap, and the drop now speaks.
A town plan cut off mid-structure is asked for once more. Reasoning off
is a request, not a guarantee: z-ai/glm-5.2:thinking ignored it, spent all
16,000 response tokens and returned 3,461 characters of plan cut off inside a
washroom. Over 99% of what was paid for was the trace. A cut-off object is
the one JSON failure more room fixes, and it was fatal; a malformed or
reasoning-only response is still refused at once, because neither is helped
by asking again.
A failed beat now says whether that was all of it. The evidence attached
to a validation failure is trimmed to 600 characters, which makes a response
that stopped early and one that merely continues past the window look
identical. Both end mid-object on the page. That cost two wrong diagnoses in
one afternoon, so the length and whether the object closed are now said out
loud.
And the belief card shows the shape it was only describing. Reinforce and
weaken are told to write target_belief as the empty string, and the only
worked example was a revise. A required field whose correct value is usually
empty, demonstrated nowhere, is one a model drops as redundant.
Known failures are unchanged from 10.0.0 and are listed there.
alpha 10.0.0 — The room that plans it, the space it happens in, and a Director that only rules
The first release carrying the Writers' Room and the geometry system, and
a substantially revised orchestrated Director. 469 commits since alpha 9.10.2,
most of them never on main until now.
The Writers' Room is an author that works beside the story rather than
inside it: a Story Planner that keeps the prepared horizon, a Dramaturge that
proposes direction and builds nothing, plot packages that land all-or-nothing
through typed operations, and mandates that record in the player's own words
what the room has been allowed to do. It plans the ground a story opens on
before the Director ever runs.
The geometry system gives a room a floor. Anchors placed on their walls,
bodies at cells, sight cast across the shape by recursive shadowcasting with
eye height from posture, light and sound as scalars over one shared grid,
storeys, extents and room shapes. A fixture stands where its own description
says it does.
The Director was already orchestrated at 9.10.2; here it is revised to
stop encoding the world at all. It slices, routes and rules, and the five
specialist hands own the channels.
Everything in the sections below was found by launching stories rather than
by reading code. Six live quick starts from one greeting produced five
distinct defects in the opening path, every one of them in work landed the
same day, and the sixth run is the first where a plan published.
Known failures, carried into this release deliberately
31 tests fail on this tag. Twenty-nine of them were already failing
before any of this work began; the other two are Japanese twins of two of
those, visible only now that the English-only mandate is retired and the
Japanese checks run again. None is a regression from this release.
They were triaged rather than fixed, and the triage is the finding:
fourteen are stale locations where the rule is alive and has moved to a
specialist hand, five ask about structures retired on purpose, and ten are
real. The real ones share a cause. 38b560c6 (2026-09-12) correctly retired
the Director's monolithic sheet and moved its channels to the five hands,
but only seven clauses were relocated and the rest went out with the sheet,
including duties that still have an owner and still have code feeding them.
The full list, each verified against the tree, is docs/UNBUILT_PIPELINE.md
§ 1.164. The one to fix first is that the world-pressure must-tick retry
cannot fire on a real beat, which means the floor that makes the world act
while the cast talks is off.
The opening plan is new and thinly proven. It published on one of six
live runs. Three defects in it were fixed today and the fixes have one
successful run behind them, not a measured rate. It fails silently by
design: a plan that does not land costs nothing but the plan, and the
opening runs as it always did.
Two other things are named and not changed. The establish sheet grew by
half when it took the shared place vocabulary, and the provider silence
budget is ten seconds while that sheet's successful call ran forty-three;
two of six runs died on that timeout before any token arrived. Both numbers
are the owner's to set.
All of the above will be addressed. They are written down here so the next
person to ask "when did this break" has a dated answer.
The opening plan, after its first six live runs (2026-09-16)
It published nothing in five quick starts from one greeting, for three
separate reasons, all of them in the feature rather than around it. The step
budget cut the plan off one call from the end: chat 127 drafted every
operation, validated clean, and stopped without publishing a finished
package. A placement drafted before the rooms it names could never validate,
because the package preview only knew about rooms from plan_rooms
operations it had already walked past -- so chat 131's placements at
reception were refused as nowhere while the same package planted
reception two operations later, and the planner spent seventeen calls
re-drafting rooms that had been right all along. A package lands in one
transaction, so its rooms are known to the whole preview now. And nothing
recorded what the room did: a planner exchange is captured against a turn,
and this stage runs before turn 0 exists, so the opening_plan row carries
a tool-by-tool trace, refusals included.
Japanese is back at protocol parity, and the English-only mandate is over (2026-09-16)
tools/project_check.DEFERRED_PACK_PARITY is empty again, so every English
prompt edit must carry its canonical tokens into ja in the same change. The
week the deferral ran cost 97 tokens across 17 prompt leaves: schema keys,
enum values and JSON shape fragments that a translation pass had rendered into
Japanese or dropped, and whole paragraphs ja had never had at all -- storeys
and what the walls give back, the cell grid, the errand that carries word, the
detail a body perceives on a thing, and the opening the Story Planner now runs
before the first beat. A Japanese Director emits routable JSON again.
Two things worth knowing about that work. A token is never translated: look,
left, where, spent, proven, at, purpose and bare are field names
and enum values that happen to be ordinary English words, and every one of them
had been translated. And the Japanese narrator has adopted its first section
marker, so the dialogue-placeholder block is now loaded only on beats that
carry dialogue, exactly as English does; the rest of that sheet is still one
core section.
Wall-adjacent cells are not restricted in any way (2026-09-16)
Two rules kept fixtures off the ring of cells against a room's walls, and
both are gone on the owner's ruling. A fixture with a bearing was pushed one
pace inward whenever it carried any height other than floor, to leave a
lane a body could take cover in; a fixture with no bearing was seeded into
the interior only, so it could never reach the ring at all. Between them the
outer ring of every room was reserved for doorways.
Measured on chat 127, a four-pace-square reception room: four of its five
authored fixtures stood off the walls their own descriptions name, the west
and south walls held nothing, and the remaining two-by-two middle had to
hold five fixtures, so the door shared a cell with the table and the bench
with the chair. The corpus hid it -- 918 of 930 authored anchors predate the
height field and normalise to floor, so only 8 had ever been pushed -- and
every story authored under the current card would have been chat 127.
The lane is now AUTHORED, not inferred: an anchor says lane when it is
something you work behind, which a bar, a shop counter and a screen with a
blind side are, and a bench, a shelf and a hearth are not. Height was the
wrong proxy, because a bench and a counter are both waist-high and only one
of them has a behind. Cover still works where a lane is authored, and where
one is not it falls through to the room side, which is the right answer: a
thing flat against a wall has no behind to hide in. 155 free-standing
anchors move in the live corpus, and every wall-bearing anchor now touches
its wall.
A failed quick start can be retried again (2026-09-16)
A greeting quick start that died AFTER writing turn 0 could never be
resumed: the retry re-inserted the same row and answered 500 with UNIQUE constraint failed: turns.chat_id, turns.idx. Every other stage of the
resume path asks the chat whether its own work is already there; the turn
insert was the one that did not, so the failure kept so the story could be
resumed was the failure that made resuming impossible. Measured on chat 128,
which had turn 0 written and one stage saved. The row is reused now, and the
pipeline restores and re-runs what is stale, which it already knew how to
do.
Glare is gone (2026-09-16)
The light field's glare cap -- a lit source within two cells of the
observer's front cone, with the target beyond it, capping sight at shapes
-- is removed outright, with its two constants and its glare cause. Owner's
ruling: light that falls on a thing shows it, and a source between two faces
does not hide either one's detail short of something no fiction here has
authored. Measured over every checkpoint of chats 60-126 before the cut:
5 firings, all one wall sconce stationed at the bench its observer sat
on (chat 126, turns 2-6 -- a guest at arm's reach became "an indistinct
figure"), and 0 on a case a reader would accept. Raising the threshold to
bright would have fired 0 times on the corpus and dazzled every occupant
of a bright room, since a bright room is lit by bright fixtures; so the rule
is gone, not weakened. tests/test_light_field. test_a_lamp_held_in_your_face_costs_nothing pins the rule's own strongest
fixture at full. DESIGN_LIGHT_FIELD.md § 4b carries the retraction.
A room the opening does not see is a stub (2026-09-16)
Chat 126's opening wrote the parlor's back room, which nobody had seen, as a
described room: a placeholder desc ("not currently visible from the
reception parlor"), a guessed light: dim, no anchors. Nothing in the
engine could tell that from a description, so when the door opened and the
player walked in the spatial hand was handed moves and nothing else, the
placeholder rendered verbatim into every view, the guessed light told the
narrator sight was "degraded: dim light -- shapes, not detail", and the
narrator invented a table. The establish sheet now says A ROOM THE OPENING
DOES NOT SEE IS A STUB: planned: true, a purpose, exits, and no desc,
light or anchors -- the registry's own stub shape -- and RoomDef declares
the three keys, so the first beat that enters or looks into it is briefed
and furnishes it (tests/test_opening_room_stubs.py).
The opening is planned before the Director runs (2026-09-16)
Both launches -- a scenario chat's first turn and the greeting quick start
-- now run the Story Planner once before the first turn row ex...
alpha 9.10.2 — The latch was narrower than the answer
Three audit workflows against two stated conventions: ask the model broad
questions and make the latch wide enough to catch the answer, and feed each
stage only what the present scene holds. Twenty-five defects, and nearly all of
them were the first convention — the model was right and the engine could not
catch what it said.
The comms channel was dead end to end
The spatial hand owns comms_ops and was never shown scene["comms"], while
its own chunk makes set a complete replacement snapshot demanding an id. So
every maintenance op ever written — 4 of 4 in the corpus — named an id the
establishing beat had not used. Chats 78 and 84 still carry two live PA channels
each, and chat 78 turn 8's prose says "She then releases the PA button" while
the close hit an unknown id and flipped nothing.
Fixed on five surfaces: the payload, director_establish's output shape, the
changes_asserted vocabulary, the reconcile auditor, and the delegation list.
Measured A/B on the real beat — with the ledger visible the Director emitted
comms_ops: [], correctly recognising nothing needed doing; with it stripped,
a third invented id.
A sentence spliced in half, in all twelve specialist cores
"Omit resolved_events entirely when you were given no numbered events." had
another block pasted into its middle, orphaning the word events. two
paragraphs later. English was repaired by relocation alone, not one word
changed. Japanese could not be — it had been translated from the spliced
English, so the sentence's object was simply absent, and ja/offscreen had
swallowed the heading entirely: that block opened mid-thought with nothing ever
saying an event might not be yours.
And the defect nobody looked for: ja/body had translated three of six
reroute_to roster values and ja/objects all six. reroute_to is matched
by identity (target in SPECIALISTS), so on a Japanese session the objects
hand could address nobody — every forwarding address dropped, routing falling
back to the category map, which is the re-ask that block exists to prevent.
Stale authority: the author told to write what it cannot write
prose_author_sheet/08.txt is ungated core and instructed the prose author
to write state_diff.entities and state_diff.rooms on every beat — against
12.txt and 27.txt stating those exist in no field of its output and are
discarded unread. 09_transit.txt was literally {{fragment:transit_note}},
the same fragment director_establish and mapping_stage include: right for
those two, wrong for the hand that cannot write those channels, and gated on
sc["contained"] alone so not rare. Routed rather than deleted — what the
author still judges stays with the author.
Latches widened, vocabularies published
drive_shift was erased by any silent later round (merged = dict(new) and a
sixteen-name tuple it was not on). Two channel lists in director_evidence had
drifted from StateDiff. travel_interrupted, thoughts_omitted and
CourierOp.freight were asked for in prose and absent from the shape. Five
engine-owned vocabularies were graded on and never published — room size
among them, six values read against three published.
Two failed toward concealment, where blank would have been safe:
inventory_ops.relation copied verbatim into a containment mode hides an object
from the room, and a tell's channel offers six body regions where perception
recognises two sense modalities, so four of them are sight-only and a listener
in the dark loses the tell.
Three caps ended fiction in silence
_MAX_OVERLAY_ENTRIES, OBLIGATION_CAP and WORLD_PRESSURE_CAP each drop the
oldest entry by list position, with no record — and in the two ledgers age
is exactly the must-discharge signal, so the cap preferentially deleted the
entry the next beat was about to be forced to act on. The evictions are now
legible. The values are unchanged and remain an open question.
From live play
The charter bridge asks which bodies are near by handing a room id to a
filter that matches on place, inside a bare except that hid its own
failures. 37 simulated people in one chat were unreachable for fourteen turns,
and the whole subsystem read as unexercised. It now says when it is certainly
dead, and the swallow reports.
Six copies kept, deliberately
The six specialist cores are 8-of-10 paragraphs byte-identical in English and a
shared fragment would collapse them — but the Japanese renderings measure
0.70–0.93 pairwise, so one fragment would impose one hand's Japanese on the
other five. Pinned by test instead, and the guard was proven to bite by
restoring the pre-fix files: six failures. The pre-existing agreement test
passed at HEAD, correctly — both defects were uniform pastes, which is exactly
why nothing saw them.
11,069 tests pass, up from 11,010. project_check clean, compileall
clean over sixteen roots, nothing reverted.
alpha 9.10.1 — The switch, and the half of the ruling that never shipped
A point release for three things 9.10 claimed or implied and did not deliver.
The Director's interpret half really does rule now
9.10 said the interpret gap was closed. It was not: ledger_notes had to be
named in a FOURTH place nobody had found. interpret_delegation_note is
appended after the prompt body, so it gets the last word, and it closed with an
enumeration of what stays the author's — …speech, movement, follow_op, location_query, other_players, flow, notes — with ledger_notes absent. A
field declared in the output shape and omitted from a later, more specific list
is a field the model is right to leave out.
The test that should have caught it asserted on DEFAULT_PROMPTS, which is the
prompt BODY, never the assembled text the stage sends. Green on the body,
silent on the wire. It now builds the assembled prompt, and a second test pins
the enumeration itself.
The general lesson, since it cost two rounds: a prompt that enumerates what its
output contains must be updated at EVERY place it enumerates, and an
enumeration nobody remembers exists is exactly what a closed list decays into.
Debug capture has a switch
It shipped readable only from a settings row or an env var — the same shape as
the export that had no HTTP route. ⚙ API now carries Debug capture: the
on/off, the hash-only-versus-full-text choice, and the log level, which applies
on the spot rather than at next start. Off stays the default, and the help text
says why in the reader's terms.
Verified end to end on a real replayed beat: six calls captured — the prose
author and all five dispatched specialists — each with its full system prompt,
its payload, its output and its reasoning, for 199 KB of distinct text across
60 blobs.
A number that disagreed with the order it sat in
seq was assigned at insert, and the six specialists run concurrently and land
in completion order, so the exported timeline sorted correctly by wall clock
while the numbers printed beside each row contradicted it. seq is now the
row's position in the timeline it appears in; the stored counter survives as
capture_id for anyone joining back to the row.
Note for anyone upgrading
The two capture tables are created by db.init() at startup, so a server that
was already running when 9.10 landed does not have them — restart before
switching capture on. Turns played before it is on export exactly as they did
before, and say capture_was_on: false rather than looking complete.
alpha 9.10 — Say what you decided, somewhere it can be read
30 commits. The thread through most of them is the same: the engine knew
something, decided something, or refused something, and left no way for anyone
— a specialist, a reader, the next stage — to find out. The Director resolved a
beat and its six hands had to infer the ruling from prose. A perception guard
declined an act correctly and left no trace, so a correct refusal and "nothing
happened" looked identical. A provider rejected a request mid-stream and the
rejection arrived with its status thrown away. A turn could be read only by
excavating the database.
The Director rules; the specialists keep the books
The six specialists were invented to take bookkeeping off the Director, and
they inherited a judgement they were never meant to make: with only the prose
to read, each decided for itself what the beat had settled. ledger_notes is
the Director saying so directly — one line per specialist whose channels the
beat actually settled, written in that channel's terms.
Measured across seven beats from seven stories, the ruling reached the right
hand every time it was emitted, and the hand acted on it in 70% of cases. Two
defects turned up in the wiring and are fixed: the field existed in the schema,
the payload and the sheet's prose but not in the output shape, which is the
field list the model is actually handed — so on a live replay grok-4.3 emitted
zero of them, correctly, because as far as it could tell the field did not
exist. And {specialist:line} used "specialist" as a placeholder while
enumerating the six names nowhere, so the model guessed: 1 keyed by specialist,
8 by channel, 2 unrecognised. With the names declared, 26 of 26 keyed correctly.
And the gap that was found by reading the shipped feature rather than by
testing it: director_interpret is a structural mirror of director_resolve
and fans out to the same six hands, and the ruling channel had been built into
the resolve half only. Half the Director's specialist work ran with the hands
inferring the reading from the declaration — invisible for the same reason the
channel exists, since an absent ruling is indistinguishable from "this settled
nothing". Four places had to name the field before a model would write one:
the schema, the beat view, the output shape, and — the one that took two
attempts to find — interpret_delegation_note, which is appended AFTER the
prompt body and so gets the last word. It closed with an enumeration of what
stays the author's (…speech, movement, follow_op, location_query, other_players, flow, notes) that did not include ledger_notes, so the field
was declared in the shape and excluded three lines later. The model was right
to omit it.
The general lesson, since it cost two rounds: a prompt that enumerates what its
output contains has to be updated at EVERY place it enumerates, and an
enumeration nobody remembers exists is precisely what a closed list decays
into. The normalization is now one function shared by both beat views, because
a copy in each is how the halves drifted; the test builds the ASSEMBLED prompt
rather than the body (asserting on the body is why this shipped once already);
and it is pinned as a mirror rather than as two separate facts.
Recorded because it did not work: rewriting the absence rule — the clause
saying a missing note means "nothing changed", which contradicts eight other
statements in the same sheet — changed nothing. Two arms over the same seven
beats on gemini-3.7-flash: 2.00 notes/beat as shipped against 1.71 for the
rewrite, direction flipping between beats. The shipped wording stays. What the
arms did agree on is the open defect: both wrote about a fifth of their
channels with no ruling at all (4 of 22, 4 of 19), which is the failure the
channel exists to prevent and is not a wording problem.
A turn you can read in order
GET /api/turns/{id}/debug and an Export turn button in the pipeline
drawer produce one turn as a chronological file: what was sent, what came back
including the reasoning, and what the deterministic layer decided. Export
chat does the same across recent turns.
Most of it existed and had no way to ask for it — persist/pipeline_trace.py
could already build a portable per-turn document and had no HTTP route. What
was genuinely missing was the other half of an exchange, what a stage was
asked, and the six Director specialists entirely: they are sub-calls with no
steps, so no variants, so no record beyond a token count.
It is affordable because it is content-addressed. One beat sends 104,065 chars
of sheet across seven calls plus ~107KB of payload, and almost all of it is
byte-identical on the next beat, so blobs are stored once under their SHA-256
and payload keys are hashed individually. hash_only falls out for free and is
the default once enabled: the hash proves which sheet ran without the sheet
leaving the machine. Off by default, so the call ledger's standing
"never content" promise holds unless you turn it on.
Alongside it, the reported gap that started this: logging was pinned at INFO
with no way to change it and no file handler anywhere, so raising the level
would have produced more output in the same place that already disappears.
SONDER_LOG_LEVEL / the log_level setting, and an optional rotating file.
Refusals leave a trace now
Of seven deterministic screens, four recorded nothing at all: act_percept,
changes_asserted, pick_background_reactors, already_established_phrases.
This follows from the architecture — the firewall works by SUBTRACTION, and
subtraction is invisible by construction. act_percept refused every unseen
act for four turns while a body stood exposed inside another, behaved
perfectly, and left nothing to show it had run; finding that took a database
excavation.
ctx.note_decision records the engine DECLINING, not only repairing, which is
what warnings already cover. act_percept is instrumented at all four of its
refusal points, each with the reason it refused. Capped at 2,000 entries a
turn, with the truncation reported rather than silent.
Guards that were themselves the defect
A run of prompt work with one rule behind it: state the class, and prefer its
complement to a list.
- A reaction rule told a character it "cannot react" to an onset it did not
perceive — guarding a channel that does not exist, since a character's
payload is its perception, and costing two real things: anticipation, and
moving before you know what you are moving from. - Three memory bans sat one clause away from the fields that made them
unnecessary. A memory section now says what memory is, how it is organised
and how to use it, and says it in 6 fewer prohibitions. GOAL VIABILITYandGOAL EXHAUSTIONbecame one sentence, with all ten
cases the children covered written down and checked against the merged text
first.- Eight sentences told the character to read fields it can never receive.
- Two deterministic triggers designed that week were replayed against the chat
that motivated them and both died on the data — one would have ejected a
swallowed body mid-passage. Each became one clause instead.
Two 250-second failures with two different causes
The first was ours: a 90-second read timeout reasoned entirely about
time-to-first-byte, applied to a blocking POST where silence until completion
is normal, times three retries. The second was real and needed measuring —
gemini-3.6-flash cannot compile the director_resolve schema, so it accepts
the request and never answers, an intermediary cuts the connection at 60.2s,
and four attempts make ~248s. The same body without response_format answered
in 12.5s. The recovery ladder was gated on HTTP 400 only; a stall is now
treated the same way, and a provider that stalls twice is remembered.
And the one this release closes: a rejection that arrives inside a stream
frame carried its status as 0, so it could never reach that recovery. Running
the whole engine on gemini-3.7-flash — which rejects response_format and
answers fine without it — died on provider stream error: Request contains an invalid argument. The frame states its own code; all four raise sites now read
it. A rejection is the same event whether a status line or a frame carried it.
A snapshot that outlived its referents
22 chats held checkpoints naming characters that had since been deleted, and
restoring one raised a bare FOREIGN KEY constraint failed. Fixing memories
exposed the same defect in memory_summaries — a class, not an instance. All
22 restore.
Smaller, from live play
- A weather field is not an enclosure; emptying a room is not removing it;
releasing a body is moving it, and limbs are not interiors. - A pose that keeps restating what happened leaves the body still doing it.
- An abort now reaches the blocked read instead of waiting for it, and stops
the next stage starting. - A helper standing between a decorator and its handler was stealing the route.
alpha 9.9 — The world has people in it, and they are nobody's spare parts
153 commits, most of them found by playing rather than by
reading. Two long live runs — a forty-turn Enterprise-D shift and the town
generations before it — produced the defects that shaped this release, and the
pattern they share is that the engine knew something and no one downstream was
told: a person standing in the room who never reached the view, a handover
resolved and filed nowhere, a mind that could not remember what it had said one
sentence ago.
A generic name is never made out of a named person
The mint read name lists. Where a law was written by a model reading a story's
lore, those lists ARE the story's cast, so a twenty-body institution issued
"Jean-Luc Crusher" and eighteen more like it. Filtering cannot fix this: two of
the elements the planner supplied appear in no lore entry anywhere — it wrote
them from its own knowledge of the setting — so no reservation, however
complete, can reach them.
So the mint no longer reads names at all. It reads a phonology: syllable
fragments that name nobody, written back into the story's own lorebook as an
entry an author can open and edit. The pools are refused before they reach a
body, the fragments are refused where they are a piece of somebody
(measured: the old law could mint a registered character's surname exactly,
from a three-letter opening plus "er"), and what replaces refused material
comes from the law's own other field and from the setting's PLACE names —
because a place is not a person.
Canon characters still appear. lore_cast_residents reads a lorebook, works
out who is not already in the active roster, and hands them over as real
background residents under their own names, with the post the lore gives them.
What is forbidden is cutting them up for parts.
The Charter is the cheap persistent mind, and it was already one
The presim builds acquaintance, reputation and shared service and nothing read
it, so a crew who had served together for years began as strangers. Background
people are now Charter bodies rather than a second population with its own
rules; acquaintance is DERIVED from the simulation instead of seeded by hand;
and a body may now go somewhere for its own sake — a lounge, a commons, a place
whose purpose is being in it — where before charter_places was every place
with a post or an upkeep, and off-duty circulation had nowhere to send anyone.
Presence is composed in the view, not inferred from who spoke
A player walked into a room holding five simulated people and could not see,
address, or be answered by any of them: four turns of speaking to a table, and
silence every time. The roster a view is composed from was the cast plus the
players — a presence reached a view only if it had already spoken. Meanwhile
the crowd projection subtracts everyone "presented individually", and nothing
ever presented one, so the subtraction deleted people instead of relocating
them. A captain and a lieutenant commander had been standing unseen on a bridge
since turn 0.
A mind is present at its own conduct
Nothing commits mid-turn, so the within-turn views are the only ledger a
character has for the beat it is in — and the actor was excluded from its own.
A character granted a second round had no record of the first, which reads as a
person who does not listen. Its own speech now comes back verbatim (a mind
knows its own words, not a muffled rendering) and its own deed by the same
intent-free surface everyone else gets.
Beside it: one mouth's consecutive lines with nothing between them are ONE
utterance. A round emitting three speech elements became three quoted lines set
back to back on the page; the speech budget's own sheet already forbade it and
nothing enforced it.
A mind's record of itself now covers the beat it is standing in
A character granted a second speaking round restated what it had already said
— an order reissued, a fact reworded, a question re-asked — and it read as a
person who does not listen. The obvious explanation was that they did not know
what had happened yet in an unresolved beat. Measured, that is false: the prior
lines were in the view handed to them, verbatim, and their own stored results
cite the observation that carried them.
What was true is narrower and worse. The conduct arrived only as VIEW TEXT — a
perception of the room — while every structured record a mind keeps OF ITSELF
is projected from committed turns. So the beat's own rounds sat in the mind's
picture of the room and in none of its records of itself, including the one the
engine's own anti-repetition rule points at. The ledger now extends into the
running beat, and a deterministic floor drops a later round that re-delivers a
line the same mouth already gave, with short interjections and completions of a
cut-off line exempt.
The cross-turn version of the symptom was never this defect at all: an order
addressed by rank-plus-name was matched against a bare canonical name by exact
equality, so the reply debt never registered and the asker re-issued into the
silence. An address now resolves through every key the body answers to.
The author owns the tense, and the clock owns the time
Narration tense was nobody's decision and is now the author's, read per turn so
the dial applies to the next beat with no restart. Memories are dated in
fiction time rather than in beats, and a passage phrase ("moments later")
stopped being handed to minds as a time of day.
Bodies have insides, and the inside is rooms
A body that has taken another one inside HAS an inside, whether or not anyone
authored it: the floor mints a room from the record itself. Metric space,
extended bodies and instruments landed with it, along with a crossing that
takes real time on the clock.
Fixes
The run that found most of these
- A transfer is evidence that a thing exists. A handover of an object the
scene had never minted was resolved perfectly and filed nowhere — 26 beats
across 7 chats. Four turns after handing a padd away, the prose had it back,
because nothing had ever contradicted her holding it. A transfer now mints
the record, and never mints over a body. - A quarter of a run had no player view at all.
perception_outcome's
player view was NULL on six of 38 turns and the narrator wrote four hundred
words of the room anyway. That was in no defect register; it was found by
reading the pipeline data after the fact. - An object contact was narrated as a person. A combadge resting on a
uniform reached the page as a body pressed continuously against the player,
with three absent people's scents attached to it. The identity floor was
person-shaped and had no answer for a thing, at three separate sites; the
third — the one that actually feeds the narrator — was still wrong after the
first two were fixed. - Clothing is not a surface that touches. A garment is the layer BETWEEN
two surfaces, never one of them. - Tenure is not a tense. A card saying "serving aboard" or "has served as
Chief Engineer of" was refused as insufficiently explicit while "officer of"
passed, so two of four attached characters generated ZERO pre-story memories.
The router was reading the verb's inflection, not the claim. - One person, one body. A caller pinning a cast member's post and rank AND
requesting their history got two bodies for one person — two captains with
the same name in one institution. - A demand reaches a person through a channel or it does not reach them.
The voice gate spent 29 of a run's 51 calls on a body two decks from the
player, which qualified on stale evidence for 14 consecutive beats and
produced not one line.
Correctness, found by review
- A body that changed room was graded from the room it left.
- A doorway declared from one side is a doorway from both.
- An object that exists is somewhere; a minted entity had no position, ever.
- A re-description is not a new fact, in four ledgers that kept both.
- A goal the world has invalidated must be closable by the world.
- A silhouette receives no face; a diagnostic is not a fact about the room.
- A presence is tracked on an id, and its name is an attribute of it.
- The phonology entry a caller who passes lore as text never got — the artifact
that makes the one model naming judgement reviewable was silently never
written for exactly the callers most likely to want it.
Performance and testing
- Parallel is the default for every caller, not only for
make. - Twenty-one reads of a forty-one megabyte registry, for one turn.
- The suite's wall clock was two tests, and neither needed what it spent.
- A charter presim forks across institutions and replays identically.
Thresholds introduced in this release
Named here rather than buried, because a cap that is not stated is a cap
nobody can argue with.
- Beat-reissue similarity, 0.7. Calibrated over all 40 recorded turns of
the run that found the defect: every true within-beat reissue scored
0.75–1.00, the closest distinct pair scored 0.33, and nothing was measured
between. It catches all seven recorded instances and touches nothing else,
including a healthy beat where one speaker asks a question and then gives a
different officer an order. - Beat-reissue minimum, 6 tokens. Under it, a short line that is a prefix
of a longer one reads as a reissue when it is a completion. - Name-element floor, 2 characters. Two letters that open or close
somebody's name are a piece of that name. Set at 2 rather than 3 because a
measured law carried canon name-heads at exactly that width. Unmeasured
cost: on a very large lorebook, every two-letter opening of every character
entry becomes unavailable as naming material.
Known residuals
- A contact's identity key excludes
manner, so two wordings of one claim are
one claim. Where a floor refuses one wording it refuses the claim — which is
correct for the measured case (a placement ver...
alpha 9.8.2 — A campaign can build a world and watch it run
Charter and the Living World were reachable only by orchestrating browser
calls. An extension can now provision them at the moment it creates a story,
and read back not what was stored but what will actually run. The surface is
four hooks; most of this release is the adversarial review behind them, which
found that the recovery path for an interrupted world generation destroyed the
work it existed to recover and reported success.
An extension can see the Living World as it will actually run
story_viewcarries aliving_worldslice: the four approaches as stored
and as they will actually run, the off-screen ceiling, the actor cap, the
background config, a row per institution, and the engine's own warnings about
them. The distinction is the point — six independent gates compose here, each
defaulting to less than a reader expects, and none of them raises when they
disagree.charters="full"returns the whole registry — the same bytes
GET /api/chats/{cid}/chartershas always served a host session, belief and
private habits included. The summary default is about size on a per-render
read and nothing else.- Both are unconditional, so there is no schema bump. A key that is always
present keeps absence meaning "this engine predates the field", which makes
the key its own capability check.
A campaign can set them when it creates the story
provision_storygainedoffscreen_life=andliving_world=, applied
inside the transaction that already creates the story.- Both refuse an unreadable value rather than normalizing it — deliberately
the opposite of the HTTP routes. A host typing into a panel sees the
normalized answer come back and can correct it; a campaign sees nothing, and
normalize_offscreen_lifefalls to the DEFAULT rather than the floor, so a
silent normalization would have bought a story MORE off-screen life than it
asked for. living_worldmerges rather than replaces, because normalization is total
over the four approaches: a replace would switch off every approach the call
did not happen to name.
A caller can name characters by the identity that survives an archive
A lived-location request may name characters by resource_uid, which is what a
caller that has just provisioned a story actually holds. An unresolvable uid is
refused; an unresolvable char_id is still skipped. The asymmetry is
deliberate — a stale numeric id from a browser is a UI bug the extension author
cannot act on, while a uid is a claim the caller made and should hear about.
The recovery path destroyed the thing it existed to recover
Found by four independent review lenses, and the reason the review was worth
running. _resumable_plan returned the stored plan and nothing bound it to the
town: the resume fed None to close_plan, which returns an EMPTY town rather
than raising. So the recovery path planted an empty structure over a real one,
reported success, and cleared the job holding the only copy of the work.
The tests did not catch it because they drove _resumable_plan in isolation and
never ran a resume end to end. The unit was right and the seam it sat in was
not.
Five more in the same lifecycle, each fixed here:
- The past-the-boundary refusal was gated on
status == "interrupted", which is
only ever derived for a foreign owner — so a run that died inside a live
process keptrunning, and the retry replanted the town. It is now gated on
STAGE alone: a story with half-planted rooms is in that state whatever is
asked for next. - The
plantedmarker was written AFTER the writes, so a failure inside
plant_structurerecordedplannedwith a stored plan and the next run
resumed over the top of it. Aplantingmarker now leads the first write. A
marker claiming a write that did not happen costs one wasted regeneration; one
that misses a write that did happen costs a second town on the same ground,
and only the first is recoverable. - Only the pure prefix had a failure handler, so every later failure left a
runningclaim owned by this process — which by construction never reads as
interrupted._fail_jobnow clears a pre-boundary job and MARKS a
post-boundary one. - Nothing serialized two generations on one chat.
_require_frame_idlecannot
see them, because it reads the turn pipeline's registry and generation never
joins it, so a double-click gave two runs claiming one job slot and the
loser's marker was erased by the winner's. charters=was documented in two guides and forwarded by nothing — not
api.story_view, not the frame-bound view, not the HTTP route. Three
signatures, and its test now goes through the public surface rather than the
in-repo function.
api.generate_lived_location was also missing both guards the HTTP route holds:
_require_frame_idle, without which a generation plants rooms while a turn is
mid-pipeline in that frame, and story_language_scope, without which a Japanese
story gets an English town planted in it permanently. The uid cap is now checked
before its SQL is built, so 1,000 uids get the documented refusal rather than
sqlite's variable limit.
The firewall was not the constraint here
An early draft of these hooks withheld Charter internals from the Python
surface — counts only, no bodies, no belief — on the assumption that this was
the careful thing to do. It was theatre. The firewall bounds what reaches a
fictional MIND; an extension is not one, and the HTTP route has always served
the complete registry to any host session. A Python caller getting less than the
browser is a weaker path, not a safer one.
The guide that made any of this documentable
docs/guides/LIVING_WORLD.md did not exist: twenty-nine charter_* modules,
world/offscreen.py at 2,223 lines, six design notes that docs/README.md
labels argument rather than authority, and zero mentions of Charter or Living
World in EXTENSIONS.md. An integrator's entry point was a facade with a
sixty-name __all__.
Four things were named as though they were tiers of one thing and are not:
Charter is the institution simulator, Living World is four author-selectable
policies over which machinery runs, off-screen life is the authority ceiling
above both, and background life is a different subsystem whose subjects are IN
the room. They share no setting, no step and no ledger. Two different things
were called "ceiling"; two different things were spelled place_obligations.
The guide now carries the composition table, which cannot be derived from any
one module — six independent gates, every one defaulting to less than a reader
expects. A chat raised to offscreen_life = character_agent and nothing else
fires zero agent ticks, silently, because that rung composes with
antagonist_ladder, whose default is off. The converse is worse:
antagonist_ladder = ceiling without the rung does not turn the mechanism off,
it clamps to floor and keeps firing.
EXTENSIONS.md gains the two capability names in its §4a table — which the
review caught listing 13 against an actual 16 — and a §7 section.
DIRECTIVE_HOST_SURFACE.md §10 answers the originating request, including the
two asks that were declined and why.
alpha 9.8.1 — The body and the beat agree again
This corrective release keeps complex physical scenes causal without making
their machinery genre-specific. Multi-action declarations retain their actual
order across reactions, perception and narration; dialogue stays in its
original slot; rejected prerequisites cannot leak their dependent effects into
state; and contact, pose, target and substance updates keep the body and object
they belong to.
Character perception now retains complete current sensation and the full
visible appearance of other people while omitting only redundant self-card
description. Autobiographical memory uses a consistent first-person
perspective. Narration receives one ordered event package, preserves the
player's question/answer order, rejects undelivered dialogue, and screens
repetitive sensor-ledger phrasing before it reaches the page.
Substances gain qualitative conservation and transfer, contact actions remain
open-ended physical relations, and speech formation reflects an occupied or
engaged mouth without baking restraint, consent, morality or any other
personality trait into the agent. Finally, attire exposure is durable across
later wardrobe changes: a revealed authored body surface is not forgotten by
an unrelated change of clothing, and a phrase such as bare feet can no longer
be invented as a garment.