Repository navigation
NMOS v0.2.0
Pre-releaseThe first milestone release (docs/ROADMAP-1.0.md): Stage 4, knowledge and secrets (Phase 10) is complete, and
main has since added Stage 5, the narrative engine (Phases 11–12), and most of Stage 6, verification and
repair (Phase 13 and Phase 14 steps 1–5). The owner asked for this release on 2026-09-28. On the owner's real scenes,
48 replies of Opus 5.5 and Gemini 3.1 Pro with the new memory voiced no secret to a character it is kept from, and the
holders remembered it (docs/perf/secrets-eval.md).
What changes, in short:
- Secrets: what is kept from whom, who found it out and when; a Private section; strict and first-person
narrator modes per chat. - Open business and causes: promises, goals, questions, threats and debts are open until the story ends them;
the stated cause of a feeling or an event. - Relationships: a relationship is one pair, both ways.
- Summaries: scenes and the story so far are summarized in
<Story>; each scene character's state goes in
<Cast>. - The owner repairs memory: close or reopen a thread, retract or correct a fact, mark a secret found out, split
two names, lock a fact; each with undo. - Canon: the card, the lorebooks, the persona and the author's note become sources of each chat. Lorebook keys
give given names; the extraction model reads canon facts, which the story supersedes.
It also has the fixes of the 2026-09-26 and 2026-09-27 audits and a new text normalizer. The generations and schema
that change are:
- extractor generations
extract-v11toextract-v13; - the text normalizer
clean-v3; - the default packet policy
packet-v8; - migrations 0021–0026.
The Phase 14 evaluation of canon facts on the measured chats (their model calls, M0, the secret gate, latency with a
large lorebook) and its real-host smoke are still to come (step 6).
-
Fixes from the 2026-09-27 audit (
docs/proposals/ORIGINAL-VISION-TO-STABLE-2026-09-27.md, ADR 0033
amendment 2):- A reveal no longer carries over to a different secret after the secret's turn is edited (G1). A character
who found out one plan counted as knowing whatever plan that turn was edited into. A reveal now links by turn
only while that turn reads as it did; otherwise it must match by content, or it is shown as unrevealed. - "Extract all history" now recovers a reveal missed when a chat was first connected (K29, G2): it extracts
again the turns extracted before an earlier turn's secret, oldest first. Before, it skipped them. - A model answer without an
assertionslist fails the job (retried, then counted failed) instead of
counting as a turn with nothing to extract (G3; in every release sincev0.1.0-beta.1).
- A reveal no longer carries over to a different secret after the secret's turn is edited (G1). A character
-
Phase 12 (Stage 5, part 2) is complete (
docs/phases/PHASE-12.md, 2026-09-28):- M0 has 12 more owner-confirmed cases whose answers lie outside the prompt's own messages: 2 of 12 on Phase 11.
- Scene summaries and the story so far (ADR 0042, migration 0023), written in the background by the extraction
model for every 8-turn window and kept current through edits, deletes and swipes; the Inspector shows them. The
prompt lists the secrets still kept from someone and says to leave them out; a summary that repeats one is held
back when it is read. packet-v8(new default):<Story>and<Cast>(ADR 0043). The story so far and the scene the message is
about, in at most 30 % of the budget (none for a first-person narrator); each scene character's place, condition,
feeling toward the persona, what they carry and, when the message names them, open goals. On the owner's chat
(restored copy) 5 of 12 answers outside the prompt window reach the packet, 2 before. A summary written before a
secret it should keep is held and written again (ADR 0042 amendment 2); in front of a character a secret is kept
from, a summary that comes near it is held too (amendment 3); a reworded secret can still pass (K30).- Summaries are on by default where extraction is on (
NMOS_SUMMARIES=0or the panel turns them off). On the
first start every chat's due windows are summarized in the background: for the owner's chats, about 11 scene and
2 story calls. - The Inspector shows why a summary is or is not used: the summary generation, each scene's state (current,
held back for a secret with the secret named, not used while a character it is kept from is in the scene,
changed and written again, queued, failed with its error), and a note when summaries are off or a chat has a
narrator. A character's page starts with Current state, the lines<Cast>gives them and their open goals. - A long append no longer leaves scenes unsummarized (ADR 0042 amendment 4). A sync that made two or more
windows due at once queued only the newest, and the story so far, which waits for every window, was never
written. In play a sync adds one turn; it took a chat continued while the sidecar was down or the plugin off. <Story>in a long chat costs a request about 10 ms instead of 180 ms (10,000 messages, 624 scenes). A
request read every story the chat ever had and checked every scene against every secret; it now finds the
current scenes and one story in one query and checks only what it would offer. The choice is the same.- Fixes from the review of #137 and #139: the Inspector shows the job that would replace a summary still in use
(queued, writing, or failed with its error), such as a story behind the newest scene; and a request keeps the
scene trigrams it read, so another request clearing the shared cache cannot drop a scene from its choice. - From the first cross-model review (Codex, AGENTS.md §14): after the turns of a whole window were deleted, a later
scene summary, current now for an earlier window, was judged older than the prompt by the turns it was written
at, so a scene the prompt no longer held could be left out. The request now uses the window it is current for,
as before the step 7 rewrite.
-
Phase 14 (Stage 6, part 2): canon sources (
docs/phases/PHASE-14.md, approved 2026-09-28; steps 1–5): the card,
the lorebooks, the persona and the author's note as sources of each chat, host evidence first. Export and restore
are Phase 15.- Step 2: canon host evidence on PocketRisu v1.13.0 (
docs/HOST-FACTS.md, H19) and a count-only inventory of the
two measured chats' canon (docs/perf/canon.md). - Step 3 (ADR 0045, migration 0025; new plugin build): each chat's canon is kept as the host shows it, with every
version. The Inspector lists it in a "Canon" section, with how often a request's prompt held each text. The
plugin sends it in the background when it changes and reads the card off the request path. Canon changes no
packet yet. - Step 4 (ADR 0046): a lorebook entry's keys count as names of the one character they name. So a character called by
a given name the lorebook lists is a mention (K31). On sample 2 the given-name probes found their fact 2 of 3,
against 0 before. - Step 5 (ADR 0047, migration 0026; new plugin build): facts from canon. The extraction model reads the card,
the persona, the author's note, and each lorebook entry once a prompt held it, in the background, each text once
(NMOS_CANON_FACTS=0or the panel's new switch turns it off). Canon facts are how things stand before turn 0: the
story supersedes them from the turn it says something new. When the story changes who someone is or how two
characters stand, "Needs attention" lists it with the choices: keep canon's (a lock, a new owner repair that
keeps a canon fact or a correction current against later statements, with undo), keep the story's, or leave it.
A canon fact whose text the prompt already holds is not sent again; a locked one is, when the story contradicts
it. The plugin now recognises a card that uses{{char}}in the prompt, so it no longer reads the card again on
every request.
- Step 2: canon host evidence on PocketRisu v1.13.0 (
-
Phase 13 (Stage 6, part 1): the owner repairs memory (
docs/phases/PHASE-13.md, approved and complete 2026-09-28).- Step 2: the owner confirmed NMOS's lists for the longest chat (50 of 59 open threads ended, 6 secrets found out);
on the previousmainevery M0 packet carried a thread the owner closed (docs/perf/repair.md). - Step 3 (ADR 0044, migration 0024): the owner can close a thread with an outcome or reopen one the story closed,
and mark a secret found out by a character or keep one the story ended by mistake, through
POST /v1/conversations/{id}/repairs. A repair is owner input: it survives
rebuilds and new extractor generations, finds its target by what it says, and is listed in the Inspector, with
undo. A repair whose target's turn was edited matches nothing and says so. - Step 4: the owner can retract a fact (the version before it is current again), correct one (a new object or value,
at its own turn or from a later one, until the story says otherwise), and split two names the story joined (K8),
with the names that still join them when a split cannot separate them. - Step 5 (new plugin build): the repairs are buttons in the panel's Inspector, on the line they fix: close a thread
with an outcome (or tick several and close them together), reopen, mark a secret found out or still kept per
character, retract or correct a fact (its object or its value, from its own turn or a later one), undo; split
two names on an entity's page. Each chat's page starts with
"Needs attention": threads open for 30 turns without a restatement, ends that matched no thread, disputed
whereabouts, repairs that match nothing now, splits still joined through another name, ambiguous names. - Step 6 (
docs/perf/repair.md): with the owner's decisions made as repairs, no closed thread reaches a packet on
either measured chat, M0 gains a case and loses none, and<Story>is back in every secret-gate scene. A memory
read with many fact repairs no longer searches every assertion for each (it cost +66 ms at 10,000 messages with
100 repairs; now +7.0 ms over Phase 12, +1.7 ms with none; accepted by the owner). A thread with no counterpart
reads "하나: …" in the Inspector, not "하나 → ?: …".tools/eval_secret_gate.pytakes a case's words by secret.
- Step 2: the owner confirmed NMOS's lists for the longest chat (50 of 59 open threads ended, 6 secrets found out);
-
Cross-model review (
.ai/, AGENTS.md §14; #138): fixed after its review, the script refuses every.env*name
(.envrcpassed before) and gives the reviewer CLI only the system basics of the environment and its own login
variables, neverNMOS_*,DATABASE_URLorPG*(it passed the whole environment).- The default memory budget is 2,000 tokens (was 800); a value saved in the plugin stays. Lower PocketRisu's max
context accordingly.
- The default memory budget is 2,000 tokens (was 800); a value saved in the plugin stays. Lower PocketRisu's max
-
Phase 11 (Stage 5, part 1) is complete (
docs/phases/PHASE-11.md, 2026-09-28):- M0, an evaluation on a restored copy of the owner's chats (
tools/eval_rp.py, numbers only in
docs/perf/m0-baseline.md): 13 of 28 owner-confirmed cases on the Phase 10 code, 15 after step 3. - A relationship has one history per pair (ADR 0038, K24). A change the extraction records in the other
direction ("카이토 → 유이: 연인" after "유이 → 카이토: 같은 반 친구") replaces the old one when either is symmetric
(friends, lovers, classmates…); a directed pair ("엄마", "자녀") keeps both. Feelings and speech levels stay per
direction. - The persona's full name is the persona (
resolve-v5): "아오키 타쿠미" for the persona "타쿠미". One chat was two
people, so old speech levels stayed current and promises made to both never closed. packet-v5(new default) names what a relationship, feeling or speech level replaced, with its turn
("…: 연인; before, turn 1: …: 같은 반 친구"), and how it started when that differs, so "how did they stand before"
has an answer.NMOS_PACKET_POLICY
keeps the earlier policies.extract-v13: goals, questions, threats and debts end (ADR 0039, migration 0022). They are threads like
promises; the prompt lists the open ones andresolvedends one (achieved, abandoned, failed, answered, averted,
paid). A craving or the next thing someone is about to do is not a goal. A cause the story states is kept
(because). Re-extracts each chat's recent window. A promise or goal kept from someone who then finds it out is
no longer shown hidden from them as a thread. A goal the story never mentions again stays open (K23): on the
owner's longest chat 9 of 48 goals ended and 37 stayed open.packet-v6(new default): the cause the story states ("…: 화남; because: 약속을 잊어서"), on facts and claims,
and facts with a cause come first when the message asks why (ADR 0040). The Inspector shows each cause and the
event it names when one nearby clearly matches.- M0 counts answers the prompt's own last messages hold: of 28 cases, 9 need memory; 5 of them before Phase 11, 7 now.
- Inspector: a Relationships section with each pair on one row (relationship, each direction's feeling and
speech level, with cause, what it replaced and how it started), on the chat and character pages; open threads
counted by kind. packet-v7(new default): one numbering forturn(ADR 0041). A fact, claim or thread says the
turn index, but an excerpt'sturnand a state item'sas_of_turnsaid the message's position, so one packet
could show a goal at "turn 26" next to an excerpt of the next message at "turn 56". Underpacket-v7they carry
the turn of their message; excerpts stay in story order. Same outcomes on the memory evaluation and on M0
(26 of 28). The Inspector's state table ("As of turn") now shows the turn index; it showed the position.
- M0, an evaluation on a restored copy of the owner's chats (
-
The compose files no longer pin a packet policy. Both passed
NMOS_PACKET_POLICY=packet-v4unless.envset
it, so a stack started from them onmainkeptpacket-v4afterpacket-v5andpacket-v6became the default.
They now pass it empty, which means the sidecar's own default. No release was affected (v0.1.0-beta.21pins
packet-v1, its default). -
A saved API key is sent only to the host it was saved for (review of an external analysis, 2026-09-27).
/v1/config/testand/v1/config/modelssent the saved key to whatever URL the request named, and saving an
endpoint on another host kept the key for it, so anyone who could reach the settings API could have the key sent
to their own server. Now a test or model list for another host goes without the saved key (its error says why),
and saving an endpoint on another host drops the saved key: enter it again. A key from.envapplies again
when the endpoint is back on the.envhost. -
The plugin's host reads count against the request deadline. Only the sidecar calls had a hard deadline: a
settings or chat read the host never answered held the generation. The memory budget and deadline set in
PocketRisu's own plugin argument fields are capped like the panel's; a budget over 20,000 tokens, which the
sidecar refuses, made every request go without memory (the panel did not cap the budget either). -
The worker waits for the sidecar's migrations. Compose starts both at once, and a worker that claimed jobs
against the previous schema failed them and used up their attempts. -
A recall reads the chat as its request had it. A sync of the same chat from another tab or device (K28)
that landed during a recall added its messages to what the recall read and recorded. -
Tests: the plugin's DOM code (the Inspector sanitizer's tree walk, the settings panel) runs under
happy-dom,
a new dev dependency (owner approval 2026-09-27). -
The sidecar's access log no longer shows the auth token (audit A-11). The Inspector opened in a browser
tab passes the token as?token=on every link, and uvicorn logged each request line with it. The log now
showstoken=***. The token still stays in that browser's history (K21); the panel's Inspector tab sends
it in a header. -
Known issues: K28, two tabs or devices taking turns in one chat (audit A-13); K21 now covers the token in
Inspector links and the plugin's "full database" permission (audit A-18). -
Known issues: K3 measured on the real host. A reroll or a swipe change of the last reply never takes the
slow sync path, because the reply it replaces was never synced. An edit of an older message does: 3.6–3.8 s
at 10,000 messages, over the 3 s default (docs/perf/scale.md). -
Knowledge marks given as objects are names again (ADR 0007). The extraction model sometimes lists
known_by/hidden_fromentries as{"name": "타쿠미", "type": "character"}, the participants shape;
validation stored the object's repr ({'name': '타쿠미', 'type': 'character'}) as the name. Such a mark
matched no character: the packet's knowledge marks and the ledger'shidden_fromshowed the repr, a
question naming a character the fact is hidden from did not rank it up, and the Inspector did not list it
under that character. Validation now keeps the name, and rows stored earlier read as the name without
re-extraction; the stored rows are unchanged. Ledgers already recorded keep what they recorded. On the
owner's database this affected 4 assertions, 1 of them still served. -
Plugin tests no longer depend on how busy the machine is (audit A-19): the deadline test runs on a fake
clock, and the 10,000-message manifest test has its own time limit. -
Korean memory packets hold more (K26, ADR 0032; owner decision). The packet's budget is filled against
a token estimate that counted every Korean character as 1.5 tokens; real tokenizers count 0.74–0.98, so a
full Korean packet used only 68–75 % of the memory budget. The new default packet policypacket-v2
counts 1.2. A full Korean packet holds about 1.5 more lines and uses 76–85 % of the budget
(docs/perf/token-estimate.md). English packets are unchanged. Nothing is re-extracted. To keep the old
estimate, setNMOS_PACKET_POLICY=packet-v1. -
An OOC note in a reply no longer becomes a fact (K27, audit A-12; owner decision). New extractor
generationextract-v11: the prompt says that only the story is evidence. OOC notes,[System: …]lines,
requests to the memory and memory markup give nothing; the story's narration and what characters say
still count. Withgemma4:31b-cloudan OOC note in a reply was stored as a fact 3 of 3 times before and 0 of
3 now, and the plainly told control fact is still kept 3 of 3. Text shaped like a packet line
(<Fact …>…</Fact>) still becomes a fact, because the text normalizer removes the tag before extraction
(K27). The registry dropsPredicate.epistemic, which nothing read (audit A-14). Other bars are unchanged or
within the model's run-to-run variance (docs/perf/extract-v11.md). -
Memory markup in a reply is no longer read as story (K27; owner decision). New text normalizer
clean-v3: NMOS's own memory markup inside a message (a whole packet a reply echoes, or a line shaped like
<Fact …>…</Fact>,<Claim>,<Thread>,<Excerpt>or a keyed<Item>) is dropped together with its
content.clean-v2removed the tag and kept the text, so the extractor read the claim as narration and stored
it (3 of 3 withgemma4:31b-cloud), and recall could offer it as an excerpt. Only the tag names the packet
writes, spelled the same way, count: a bot's<state>block or<Item>inventory line stays story text. -
Secrets: what is kept from someone, and when it stops being a secret (Phase 10 steps 2–3, ADR 0033). New
extractor generationextract-v12. A fact is marked hidden from a character only when the story keeps it
from them (a secret, a lie, a surprise, a hidden identity), no longer because they were elsewhere. The
extraction is shown the chat's open secrets and reports which ones a character finds out in the turn; from
then on the fact no longer says it is hidden from them, and the facts view shows who found it out and when.
Deleting that turn restores the secret. On a copy of the owner's longest chat, the plan the mother found out
at turn 21 is no longer marked hidden from her afterwards (five copies of it end there), a surprise ends
when it is given, and the daughter's hug promise gets the mark it lacked (docs/perf/extract-v12.md). -
A generation's backfill (after a model or prompt change, and "Extract all history") is now extracted oldest
turn first; new chats and new turns still go newest first. The previous generation keeps serving the turns
meanwhile, and a later turn's promises and secrets then refer to what this generation extracted. -
Facts only some characters in the scene know go in a Private section (Phase 10 step 4, ADR 0034). The new
default packet policypacket-v3works out who is in the scene (characters of the last two turns, anyone
named now, the user's character) and moves facts and promises that someone present is not shown to know into
<Private>, with a rule: only the holders know it, others must not act on it, and the holders keep it from
those it is hidden from. A fact hidden from someone in the scene also ranks a little higher. The rule costs
about 47 tokens of the memory budget when the section opens; with the default 600 a fact line can drop out,
so raise 기억 예산(토큰) / Memory budget (tokens) for chats with many secrets (docs/perf/packet-v3.md).
NMOS_PACKET_POLICY=packet-v2keeps the previous layout; both compose files now default topacket-v3. -
Per-chat memory mode (Phase 10 step 5, ADR 0035). A chat's page in the Inspector has a 기억 모드 / Memory
mode card. Strict gives the model only what everyone in the scene knows: a fact only some of them know
becomes "something known to A, not known to B", its content withheld (fewer leaks, but the holder forgets it
too). First-person narrator (none, your character, or a character of the chat) leaves out what the
narrator is not shown to know. Excerpts of the chat that say what the mode left out are left out too. Both
are off by default, and each request records the mode it used. -
A character's claim that only some of the scene know now goes to the Private section like a fact, with who
knows it; before, strict mode could still carry a secret word for word as a<Claim>(ADR 0035). -
The default memory budget is 800 tokens (was 600; owner decision). On the owner's recorded requests a
600-token packet placed 66% of the memory lines retrieval found, 800 places 89%, and 1000 all of them
(docs/perf/memory-mode.md). If you left 기억 예산(토큰) / Memory budget (tokens) empty, lower PocketRisu's
max context by 200 more. -
The promise your message is about is remembered first (ADR 0019 amendment 1). Promises were chosen by who
is named, newest first, three at most, so newer promises could push out the one the scene is about (asking a
child to go and hug her mother dropped her old promise to do just that). Under the defaultpacket-v4a
promise whose words your message repeats now comes first. -
A character who found out a secret now counts as knowing it (ADR 0033 amendment 1). Before, with them in
the scene the fact still went to the Private section with its rule telling them not to act on it, strict
mode withheld it from them, and as a first-person narrator they were not given it. Found by the Phase 10
evaluation, which adds seven synthetic secret cases to CI (docs/perf/eval-baseline.md). -
CI also upgrades a database that 0.1.0-beta.21 wrote, and checks that an upgraded chat has the default
memory mode. -
Known issues: K29, a reveal among the turns first extracted together (connecting an existing chat) can be
missed; run Extract all history once after connecting a chat with secrets. -
The sidecar says whether your plugin is the right one (ADR 0037, K19). The plugin sends its build id with
every sync, and the sidecar compares it with the plugin file of its own version. The Inspector's first page
says "the sidecar's build", or warns that the plugin in use differs (an older plugin shows as "no build id"),
or that another tab or device still runs an older one and needs a reload. It links the matching plugin file,
which the sidecar serves at/v1/plugin/nmos-pocketrisu.js. The Status tab of a plugin with the check says
the same./v1/healthreports it too. -
The Inspector shows a chat's secrets (Phase 10 step 6). A chat's page has a 비밀 / Secrets section: each
fact kept from someone, who knows it, whom it is kept from, the turn, and for each of them whether and when
they found out. Reports of someone finding out that matched no secret are listed apart, and a character's
page lists the secrets they found out. The last packet's section names the scene's characters and the
memory mode the request used. Sidecar only. -
The Status tab says when memory did not fit (ADR 0036). When the last reply's memory budget left memory
out, a card says how many lines of how many, and the budget that holds them all (in steps of 100, up to 2000),
with a button that sets it. Lower PocketRisu's max context by the same amount. Inspector → Retrievals shows it
too ("all at N"). With the factsextract-v12writes, the owner's chat needs 1000–1100 (docs/perf/budget.md). -
A line that says an earlier line again is left out (ADR 0036). The new default packet policy
packet-v4
keeps one of two lines with the same subject, relation and content (a fact extracted twice, a character's
claim of what the narration already says), which frees room for others.NMOS_PACKET_POLICY=packet-v3keeps
the previous layout; both compose files now default topacket-v4.
Upgrading from 0.1.0-beta.21
Back up the database first (README, "Upgrade, backup and rollback"): migrations 0021–0026 run when the new sidecar
starts, and a rollback needs the backup.
- Pull the new image and restart the sidecar and the worker. At startup:
- the normalized text is rewritten (
clean-v3); - migrations 0021–0026 add memory modes, thread outcomes and causes, summaries, owner repairs and canon.
- the normalized text is rewritten (
- Model calls at the provider's cost (with an LLM configured):
extract-v13becomes active, and each chat's latestNMOS_EXTRACT_BACKFILLturns (default 100) are extracted
again once. Older turns keep their earlier facts until Extract all history, which also finds reveals a first
extraction missed (K29).- Scenes are summarized (one call per 8 turns and a story call;
NMOS_SUMMARIES=0or the panel turns this off). - Canon facts are read once the new plugin sends a chat's canon: the card, the persona and the note at once, and
each lorebook entry once a prompt has held it (NMOS_CANON_FACTS=0or the panel turns this off). - With embeddings on, every message is embedded again.
- Replace the plugin file (the host offers the update:
//@version 0.2.0) and reload PocketRisu. The sidecar
serves it at/v1/plugin/nmos-pocketrisu.js, and the Inspector says whether the plugin in use matches (ADR 0037).- The new plugin brings the memory mode, the repair buttons, the canon sync, the switches and the default budget
of 2,000 tokens. - With the old plugin memory still works, but without canon, repairs or the new default budget.
- The new plugin brings the memory mode, the repair buttons, the canon sync, the switches and the default budget
Known limitations
- Canon facts are not yet measured on the owner's chats (Phase 14 step 6), and neither are their model calls or
the latency with a large lorebook. A story that changes who someone is, or how two characters stand, is listed in
"Needs attention" against canon until the owner chooses. - Secrets: a secret is kept by instruction, not isolation (K11). A summary can say a secret in other words (K30).
- Threads stay open until the story ends them in words extraction recognizes, or the owner closes them (K23).
- Names: a given name alone is a mention only when the lorebook lists it (K31). A persona narrated in the third
person does not bring its own facts (K32). - Long chats: beyond about 10,000 messages memory needs a higher deadline (K1).
- The full list is
docs/KNOWN-ISSUES.md.