Repository navigation
Releases: Sallos725/NMOS
Release list
NMOS v0.5.0
R8 (docs/ROADMAP-1.0.md): the owner's features pulled in before 1.0 — the rebuilt panel (Phase 52), notes for the
author's note (Phase 50), "not a character" (Phase 47), "remember AI replies only" for recall (Phase 48 step 2) and
extract-v17 (Phases 45 and 51) as a selectable extractor. Prepared 2026-10-25; the owner authorizes the tag. Like
every release before 1.0, it is a GitHub pre-release. The defaults are packet-v19, extract-v16 and clean-v3;
migrations run through 0032.
Upgrading from 0.4.0
Back up first (README, "Upgrade, backup and rollback"; for a bundle, quit NMOS and back up its data folder).
- Docker:
docker compose pull && docker compose up -d. Bundle: unpack the new version and start it; the
data folder is where 0.4.0 put it. At startup migrations0030–0032add a column forextract-v17's speakers
and widen a check for its horizons, add the "not a character" repair kind, and add the per-chat "replies only"
flag. Nothing is extracted or embedded again, so the update itself makes no provider calls. - Replace the plugin and the sidecar together, then reload PocketRisu. The panel opens on the new hub; today's
Inspector pages are under each screen's details. An older plugin with this sidecar keeps working with the
restyled Inspector pages. - Memory: the default packet is
packet-v19(since 2026-10-13, below): a question that relates to nothing in
memory no longer gets extra fact slots from the budget.NMOS_PACKET_POLICY=packet-v18keeps 0.4.0's packet. extract-v17only if chosen:NMOS_EXTRACT_COMPILER=extract-v17selects it, and choosing it re-extracts
every chat once (model calls). Its copy measurements passed; its full live gate has not run.- "Remember AI replies only" affects recall only; facts and summaries are still made from both sides (K51).
Changes
- Remember AI replies only (Phase 48, recall). For a chat where the AI writes your character's words and actions
too, a toggle in the chat's settings ("AI 답장만 기억") leaves your own messages out of recall: none of them is an
excerpt or a quote, so directions to the model ("write it long", "next scene") stop coming back as memory. Off again,
the chat recalls as before at once. It queues nothing. Facts and summaries still come from both sides for now (K51).
Migration0032. - "Not a character" (Phase 47). On a character's page, on a relationship row, or at the foot of a person's sheet,
the panel marks a name as not a character: a monster, or a status-window or game-master bot card. It leaves the
people, the relationships, the author's note's address lines and the packet's<Cast>, and every fact about it stays,
so "how did the monster die?" is still answered. Tick also leave it out of memory to hide its own facts too (a
bot's). A preview counts what leaves; undo is in the chat's repairs. Migration0031. - The panel, rebuilt (Phase 52). It opens on a hub of blocks (working, memory, needs a look, the author's note,
usage, settings, backup, other chats); a chat's memory reads as one document (now, open threads, people, so far,
secrets, changed from the setting), a person's sheet ends in a timeline (an overview bar and the changes as lines),
"working" says it in plain words, settings come in seven groups, and other chats are a list to search. Dark only, body
16 px. Closing a thread asks how it ended, offering only the endings of its kind. extract-v17, selectable, not the default (Phases 45 and 51; it becomes the default after 1.0.0, once its live
gate has run). An extractor generation that records how long a
thread is meant to last (this scene, a storyline, or lasting) and who says each quoted line of a turn. The Inspector,
the author's note and the panel's memory let a scene's business go after 10 turns and keep a lasting one; the quote
route names the speaker of a line its narration does not. Migration0030. Nothing changes until it is chosen.- A thread's horizon decides when it needs a look (Phase 51). Threads without one (every chat today) keep the
30-turn rule. - A note for the author's note, made on request (Phase 50). On a chat's page in the panel, For the author's
note makes one text to paste into the chat's author's note, in English Markdown (names and forms of address as the
chat writes them). It holds the parts picked: a chronicle of the events so far, how they went and how they ended
(one call to the extraction model, 3,000 or 6,000 characters, with a text assembled from the scene summaries when
there is no model or its reply cannot be used; the open threads always come from memory), what changed from the
setting (values the card or lorebook states that the story has since replaced) and address and standing (what
each pair calls each other, their relationship, roles and feelings). The last two call no model. Secrets are left
out unless asked for. The text sits between[NMOS note …]and[/NMOS note]; NMOS does not read what is between
them as the setting, so pasting or replacing it starts no canon read (POST /v1/conversations/{id}/notes). The
Inspector's canon section says whether the note holds it and as of which turn. - The Inspector says why coverage is short, and a chat's failed work can be run again on its own. The "partial" chip
is replaced by its reasons: failed (with the chat's last errors), in progress, and not queued (the past the first sync
left out). A long message embedded up to the chunk cap (K13) is a note beside a full count, no longer shown as
unfinished. On a chat's page in the panel, Retry failed appears while something failed and queues those jobs
again, and only those (POST /v1/conversations/{id}/retry-failed); Extract all history still fills in the past. - Status windows can be tracked without writing a rule (Phase 43 steps 3–4). On a chat's Inspector page,
"Status window (this chat)" shows what was found and offers Track, pins and Stop tracking; Settings folds every card's
rules and keeps hand-written rules under Advanced. NMOS finds a status bar by its shape
([… key=value | key: value …], at least three fields) in a chat's last replies and offers it; tracking it binds the key
set to the card, beside the card's other rules, so later chats of that card are read too. Pinned keys are told every
time. Nothing is tracked until it is confirmed. An archive now carries the pinned keys too (Phase 43 step 5; before,
a restored install kept the tracked status window and lost its pins). - Small talk keeps the configured limits:
packet-v19, the default (Phase 46, ADR 0075; the owner, 2026-10-13).
A message that ends in a question mark, names no one and shares no fact's or claim's words gets no extra fact or
claim slots from the budget, so "오늘 저녁은 뭐 해 먹을까?" no longer pulls in 30 facts about the scene's
characters. Every other message is compiled as underpacket-v18. - A character's possession lane draws what is held only. "No longer has it" ends the lane, and an item that was
only ever denied has no lane (display only). - The current status reaches the model even when the preset strips status bars (Phase 43 step 2). Under
packet-v18
and the defaultpacket-v19, the status block holds the date, time and place, the keys that change often, and
whatever the last status window changed, told as changes (HP 150 / 200 (−30),+횃불 · −열쇠); an inventory is shown whole only when asked. It
is placed even when the last reply is in the prompt, since many presets remove its status bar. On the trial install the
current date reached every replayed request of a card where it had reached none, in less space than the whole status. - The Korean-particle keyword supplement skips words it cannot help (Phase 41 amendment 2). A keyword of four
or more syllables already matches with a particle attached, so no extra lookup is made for it. Recall results are
unchanged; the supplement's median cost on recorded requests falls from about 30 ms to about 6 ms. - Lexical and keyword recall look a short chat up by its own messages (Phase 41 amendment 3, ADR 0052 amendment 1).
A chat of up to 500 messages and 100,000 characters no longer rechecks other chats' long messages, so its lookups
finish inside their time limits instead of being dropped. The same messages match. On the trial install's recorded
requests the whole-message route's median fell from about 141 ms to 28 ms and the keyword route found 23 % more
candidates; larger chats keep the previous lookup. - A request NMOS skips because the prompt does not hold the latest user message is no longer silent. The
progress display shows "⚠ Skipped: this message is not in the prompt", the Status tab's last request says so,
and the console logs the reason. Before, the display cleared and the Status tab kept the previous request's
result. The request still goes out unchanged. - Memory reads the reply the model is sent, after a trigger or Lua rewrote it. The plugin took a rerolled reply's
text from its selected swipe, which a trigger (modifychat), LuasetChat, a continue or the reparse after a
generation do not update; memory then kept a reply, status bar included, the model no longer saw. It now reads
data, the text PocketRisu shows and sends. Replies already rewritten are sent again once, which re-extracts them,
and the progress display says " · N script-edited replies picked up" when that happens.
Known limitations and verification
- Executed recall checks: the replay gate scored 300/314 recall probes and 19/24 exact-quote probes on the merged
code, three rounds per snapshot, the same before and after Phases 47 and 48 with nothing turned on. With every replay
set to "replies only", nothing was lost (quotes 19 → 20). These measure source-linked memory packets, not final
respo...
NMOS v0.4.0
The third milestone, Stage 7 (forensic recall), with Phases 32–42 and the pre-release audit corrections.
The owner authorized this release on 2026-10-10. Like every release before 1.0, it is a GitHub pre-release.
The default is packet-v18; extraction stays on extract-v16 and normalization on clean-v3.
Upgrading from 0.3.0
Back up first (README, "Upgrade, backup and rollback"; for a bundle, quit NMOS and back up its data folder).
- Docker:
docker compose pull && docker compose up -d. At startup migration 0029 adds a repair kind (dismissing
a status flag); it is the only schema change. Nothing is extracted or embedded again, so the update itself makes no
provider calls. - Without Docker (Windows, Linux): the first start of this version moves 0.3.0's
datafolder into the per-user
data folder (Phase 37, below). Unpack the new version beside the old one and movedatainto it, or start the new
version from the old folder, and don't delete the old folder before the new version has started once. From now on
edit the.envin the data folder: the one beside NMOS is copied there once, and where the two differ the data
folder's wins (the log says so). If start at login is
on, turn it off in the old version and on in the new one, and point a systemd unit at the new folder: 0.3.0 does
not see the moved data, and started again it starts with an empty database. - Replace the plugin and the sidecar together, then reload PocketRisu. The Status tab says whether they match and
offers the matching plugin file. - Memory: the default packet becomes
packet-v18(quotes, labels, resting lines, status history and explicit persona questions; Phases 33–40, below).
NMOS_PACKET_POLICY=packet-v12keeps the packet of 0.3.0. - Status rules: rules without
cardare now inactive. Review the preserved JSON in Settings and use the
character-specific Apply to rebind it. Existing fully bound rules keep their parser version. - Switching between Docker and a bundle does not carry the memory over: move it with an archive (README, "Moving
between Docker and a bundle").
Changes
-
A recalled scene can carry its source's status-window clock (Phase 42, AGE-78).
When an explicit time question recalls an old excerpt or quote, packet-v18 can append the literal date/time
from that exact source revision, for up to two sources, using spare packet space only. Existing selected lines
stay unchanged. This is the clock shown on the source's status window, not an inferred event occurrence date.
Missing/ambiguous stored clocks, full budgets and strict/first-person narrator modes abstain; no date arithmetic.
Replay: 291/314 recall and 21/24 quotes unchanged, 85 Voyage request ledger majorities preserved.
Seedocs/perf/phase42-source-clock.md; these checks do not score generated answers. -
Explicit status-window setup (Phase 39 amendment 2). Status drafts, character targets and preset selection
start blank. Importing JSON, saving/selecting a named preset and general settings Save cannot activate rules.
A separate Apply binds the draft to the selected character name and preserves other characters' rules.
Unbound legacy rules stay visible but inactive until rebound. Presets persist separately and travel in full-install
archives. Names are exact display-name bindings, not stable card IDs; state/history still belong to each chat. -
Korean keyword recall can recover a source missed because of a particle (Phase 41, AGE-76).
Under the defaultpacket-v18, an exact word such as a name followed by a supported Korean particle can
supply an additional raw excerpt. Existing keyword candidates keep their priority; additions use only
remaining candidate slots and lookup time. Word boundaries work across native platforms, and late lookup
results are discarded. A full existing list still admits no additions. Historical
traces and explicit v16/v17 requests retain the old matching behavior. -
Explicit third-person persona questions have a bounded recall route (
packet-v18, now the default; Phase 40).
A question about the persona's work, location or another supported kind can add at most two matching facts or
attributed claims within the existing budgets. An unshared Korean given name works on this route without
changing identity or making ordinary narration boost all persona facts. The owner selectedpacket-v18as the 0.4.0 default on 2026-10-09. -
Hidden candidates have an audit label. The Inspector records a packet candidate excluded by a memory mode or
secret gate as hidden, with its source, turn and reason instead of copying its body. The diagnostic rows never
reach the model or consume packet budget, and do not affect placement, rest or echo statistics. Existing traces
are not rewritten; replay can add the missing diagnostic rows while preserving the model-facing packet. -
Linux bundles pin the current libxml2 security patch. Ubuntu's 2026-10-08
USN-8910-1 moved Jammy's package from
2.9.13+dfsg-1ubuntu0.13to.14; the build correctly refused the changed package version. The amd64 and arm64
pins now match the signed Ubuntu archive. The mismatch guard stays in place; current target smokes are separate evidence. -
A bundle adoption preserves a copy whose PostgreSQL might still be running. Every attempted start is followed
by teardown, and the copy is promoted or removed only after PostgreSQL reports it stopped. A stop that cannot be confirmed retains
both folders and refuses another attempt until the process is stopped. -
Disabled, comment and pre-cut replies do not keep supportive memory awake. Echo detection now follows live
eligible turns and the immutable metadata of historical membership, including itsallBeforecut. Active replies
still count; later edits, undo and reorder do not alter a historical request's reply eligibility. -
A restore recovers its status after an interrupted startup. State backfill now finds message revisions without
current-rule observations independently of normalized text, in batches of 500. Existing observations stay intact;
messages with no matching field are checked once per startup. This also covers text filled by a concurrent worker. -
The final archive chunk can be retried after a lost answer. Its verified identical bytes are acknowledged in
the received state without a second write; chunks are still refused once checking or restoring starts. -
A persona's full name, given name and one nickname stay one person. The part-name rule checks the names as
written rather than the persona's internal placeholder; a shared given name remains ambiguous. -
CI requires its test database. An unavailable Postgres now fails the suite rather than skipping database tests;
local runs may still skip them. The upgrade fixtures now include a database written by v0.3.0 and check migration
0029, the status-dismissal constraint and a second migration run. -
The embedding wait is editable in the panel. Settings → Recall tuning → Embedding wait (ms) accepts 100–5,000 ms,
with the unchanged default of 300 ms. A sidecar that predates this setting disables the field. A save warns when
the wait leaves less than 500 ms of the plugin's deadline, while allowing the save. -
NMOS that cannot help says so, and the panel offers the fix (the pre-0.4.0 audit). Once per page, after a reply
that went without memory because the sidecar could not be reached, refused the token or refused the host, or because
the page is not a secure context, a PocketRisu alert says which and what to do. On a page without Web Crypto
(PocketRisu v1.13.0 over plain HTTP on a LAN address) the plugin skips each request with a readable reason, and the
Status tab says so first (K6). The Connection settings have a token field, and a refused token or host gets its own
fix on the Status tab. A plugin that differs from the sidecar's gets a button that saves the matching file. The
bundle's launcher setsNMOS_INSTALL=bundle;/v1/healthand/v1/configreport it, the panel's Ollama presets
fill inhttp://127.0.0.1:11434/v1on a bundle instead ofhost.docker.internal, and the hint for an unreachable
sidecar names the bundle's tray or menu bar besidedocker compose up -d. -
Without Docker, the data now lives outside the bundle, and the first start moves it there (Phase 37, ADR 0060
amendment 1). Windows and Linux bundles kept the database, its password, the log and.envinside the bundle folder,
so the natural update (delete the old folder, unpack the new one) deleted the memory with it. From this version they
live in%LOCALAPPDATA%\NMOS(Windows) or~/.local/share/nmos(Linux; macOS already used Application Support),
and replacing the bundle folder keeps them. The first start of this version moves thedatafolder beside the
launcher there: on one drive by a rename, across drives by a copy that is checked before the old folder is renamed
data.moved(with a note; nothing is deleted). So when updating from 0.3.0, movedatainto the new folder as
before, or start the new version from the old folder, before deleting the old one. A database in both places stops
the start and names both. The.envbeside the launcher is copied into the data folder once and read under it.
NMOS_DATA_DIR(relative to the bundle:NMOS_DATA_DIR=datakeeps the old layout) names another folder. On Windows,
a user folder with non-English letters on a drive without short names asks once where the data goes. -
A name in the message is not a question about everything (Phase 36, ADR 0070). A
message that only names a character made every fact about it required, so none of them could rest. Now how the
character stands now or with another, its knowledge boundaries, what the message's words poin...
NMOS v0.3.0
The second milestone release (docs/ROADMAP-1.0.md): Stage 6, verification and repair, is complete (canon sources,
owner repairs that survive a new extractor, export and restore), with the recall and extraction work since 0.2.0
(Phase 14 step 6, Phases 15–31; the corrections of AGE-24). The owner asked for this release on 2026-10-05, after the Phase 31 reduced live gate met every
bar: on the long synthetic chat, questions about now at turn 240 went from 18–19 to 23 of 25, the full-history set from
19 to 23, and the lent-book set from 5 to 6 of 6 (docs/perf/phase31-live-gate.md).
What changes, in short:
- Memory knows what changed (
packet-v12): an older excerpt of a replaced value (where someone lives, what someone
is called) and an ended role stay out of a question about now; a question about the past keeps them. - Roles and names (
extract-v16): a role the story ends is ended; a character written in full and by part of the
name is one person. - A chat connected for the first time is read in story order, so each turn sees what came before.
- Recall finds more of the answer: keyword recall, "how it started" questions, given and romanized names, excerpts
that land on the answer and grow with a 4,000-token default budget. - Run without Docker: bundles for Windows x64, macOS arm64 and Linux x64/arm64.
- Move or back up memory with NMOS Archive (export and restore).
- Repair with a preview, and repairs that survive a new extractor generation (or are listed when their item is gone).
- See the cost: model-call usage per chat,
/dashboardwith the version and recent job errors. - Google Vertex AI with the key file alone.
Generations and schema that change:
-
the extractor
extract-v16(extract-v14andextract-v15were never released); -
the default packet policy
packet-v12(frompacket-v8:packet-v9topacket-v12); -
canon facts are a new generation (read again once);
-
migrations 0027–0028.
-
Recall knows what changed:
packet-v12is the default (ADR 0066, D75; Phase 31, AGE-24, AGE-37). An older
excerpt that states a value the story has since replaced (where someone lived, what someone was called) is left out
of a question about now, and so is a role the story ended; a question that asks about the past ("전에", "예전에",
"before", "used to") keeps both. The excerpt of a long message now centres on the sentence that also holds the
question's one-letter words (책, 집, 돈), so "이안이 빌려준 책 제목이 뭐였지?" reaches the title (K43). Measured on
the live gate's long synthetic chat: the questions about now at turn 240 went from 18–19 to 23 of 25. Nothing is
extracted again;NMOS_PACKET_POLICY=packet-v11keeps the previous packet. -
Keyword recall finds its words again on a live chat (ADR 0052). A keyword's lookup has 25 ms; Postgres often
planned it from the chat's messages, checking each one, instead of from the trigram index: after a reroll, an edit or
a deletion, because its statistics did not know the new version of the chat yet, and on a connection that had run the
lookup five times, because a prepared statement is planned without its word. That took longer than 25 ms, so the word was dropped as if it were
too common (in the measurement runs, most requests' keyword route ended astoo_broad), and a replay could find what
its request had not (ADR 0027). The lookup now always starts from the trigram index. A word whose own matches take
longer than 25 ms to check (long messages) is still dropped, as before. -
extract-v16is the default extractor (ADR 0064 accepted, D73; Phase 28 step 3, AGE-24). It shows the model the
roles in force and closes the one the story ends, and links a character the story writes in full and by part of the
name, each confirmed once more before it is stored (Phases 28 and 29). Updating re-extracts every chat's recent turns
once (a new generation, ADR 0014), oldest first; until a turn is re-extracted, the previous extractor's facts still
serve it.NMOS_EXTRACT_COMPILER=extract-v15keeps the earlier extractor.extract-v15was never released, so 0.3.0
users re-extract once either way. Each of its rules comes only with the list it is about (ADR 0064 item 5): on
turns with no role or name pair to check, its prompt is close toextract-v15's, which kept new roles and first
events it had been missing. -
A chat connected for the first time is read in story order (ADR 0065, D74; Phase 30, NMO-36). NMOS used to
extract a new chat's recent turns newest first, so each turn was read before the turns that came before it: it could
not see who was already known, which promises and secrets were open, or which roles held, and a later turn's ending
or second name for someone was missed. The turns are now read oldest first, and a turn you write meanwhile waits
behind them. The newest turns are still in the chat's own prompt, and their messages are searchable at once.
Nothing is extracted again; chats NMOS already knows are unaffected. A secret found out within those turns is now
matched when they are first read, so K29 (a missed reveal) no longer needs "Extract all history" there. -
Google Vertex AI: pick the key file, and the model list works (ADR 0022 amendment 1). The fact-extraction LLM
section shows Load key file for a Vertex endpoint: pick the service-account JSON key file instead of pasting it,
and the endpoint's project is filled and the model list loads at once. Load models now lists the Gemini models
Vertex serves (google/…) from Vertex's publisher-model catalog, since its OpenAI-compatible endpoint has no
/models. Pasting the key works as before. -
extract-v16, an extractor to measure, off by default (Phases 28 and 29, ADR 0064 proposed, D73; AGE-24, NMO-35).
The owner's live run found a role kept as current after the story ended it, and one character split in two by its
full name and its given name.NMOS_EXTRACT_COMPILER=extract-v16shows the extractor the roles in force and asks it
which one the story ends; each ending is asked once more about that one role before it is applied, and an ending the
model calls planned, or the other direction of an ending, is held for you rather than applied. It links a character
the story writes in full and by part of the name (윤하나 and 하나; Elena Vance and Elena) unless the two could be
different people, and an alias whose two names are both in the turn is asked once more too, so a nickname one
character calls another no longer becomes the speaker's. A name counts as written only as a word of its own (람이 is
not found inside 하람이). The Inspector's "Needs attention" lists the role endings it applied recently and the ones
it held, and the aliases it held, each with one action (undo, apply, or link the two names as one person).
Selecting it re-extracts every chat once;extract-v15stays the default, and without the setting nothing changes. -
A model reply with a trailing comma is read (#251). A reply such as
{"a": [1, 2,],}was rejected and its turn
retried; at temperature 0 the retry wrote the same comma, so the turn was never extracted. A reply that fails to
parse is now read again without a comma that ends a list or an object (outside strings); every other reply parses
exactly as before. This applies whichever extractor you use. -
The query is embedded while recall reads, so a slower embedder still gives vectors (ADR 0061, D70; K34, AGE-24).
A request used to give the embedding of your message 300 ms on its own, after lexical recall and before the facts
were read: an embedder behind a proxy, or a busy one, missed it, and 70 % of the owner's production requests found
memory by shared words only. The embedding call now starts when recall starts and runs while the state, facts,
threads, cast and summaries are read; the request waits for it at most 300 ms after those reads. The embedder so
has the reads' time as well (≈100–300 ms on a long chat), a request whose embedder answered during the reads is
faster than before, and none waits longer for the embedding than before (a request that now finds vectors spends
what a request with vectors always spent: the search and a fuller packet). And since the plugin syncs your message before it asks for the packet,
the sidecar now starts the embedding of that message the moment the sync delivers it and hands it to the request
that follows: on a long chat the sync alone outlasts a slow embedder's call, so that request waits for nothing. The timeout's meaning for you is unchanged (NMOS_EMBED_TIMEOUT_MS:
what a request may wait for the embedding); the call itself may run twice that long and ends on its own. Fail-open
and the Status tab's notice are as before; the trace now recordsembed_wait. Recorded requests replay as they were.
A cold model (an unloaded Ollama) still gives no vectors to the request that wakes it. Measured:
docs/perf/query-embedding.md. -
packet-v11, the excerpt that lands on the answer — the default (Phase 27, ADR 0063, D72; AGE-31). A message
found by your words and by meaning is excerpted from the part the meaning found, instead of around the sentence
with most of your words, and a why or contents question gets the whole of a short-sentence explanation (up to 320
characters) instead of four sentences; the sentence an excerpt starts from is chosen by your question's keywords
alone. Measured on the owner's chats by replay (docs/perf/answer-span.md): the main chat's questions that need
memory 16 → 19 of 23, forbidden phrases down over twelve sets, no set worse by more than one case; packets at most
0.11 % larger on one set and smaller on another.NMOS_PACKET_POLICY=packet-v10keeps the previous policy. Traces record a new recall
option,excerpt_anchor; recorded requests replay as they were. -
The chunk cap is a setting (ADR 0062, D71; K13). Semantic rec...
NMOS v0.2.0
The 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 no...
- Step 2: the owner confirmed NMOS's lists for the longest chat (50 of 59 open threads ended, 6 secrets found out);
NMOS v0.1.0-beta.21
The rest of the 2026-09-26 audit (docs/audits/NMOS-AUDIT-2026-09-26.md and its review): the owner's
decisions and the remaining fixes. No schema change and no new extractor or embedding generation: nothing
is re-extracted or re-embedded (the generation keys are the same as in 0.1.0-beta.20). The plugin warns
when memory runs out of time; the sidecar checks host names when it has no token.
-
The plugin warns when memory runs out of time (owner decision on audit A-09, plugin). A long chat that
needs more than the deadline (3 s by default) used to go without memory silently. Now:- the NMOS panel's Status tab opens with a card when the last request missed the deadline, or used 80 %
of it or more. The card says how long it took and the value to set, with a button to the Settings tab; - after a reply that went without memory for the deadline, PocketRisu shows one notice saying the same,
once per page; - the progress display says "over the N s deadline · tap to raise".
The default deadline stays 3 s. Checked on an isolated PocketRisu v1.12.0.
- the NMOS panel's Status tab opens with a card when the last request missed the deadline, or used 80 %
-
Without a token, the sidecar answers only to known host names (audit A-05, ADR 0030; owner decision).
A web page can use DNS rebinding to reach a sidecar on127.0.0.1or the LAN from your own browser,
and without a token it could read chats and settings. The sidecar now accepts requests addressed to an
IP address,localhostor a single-label name (nmos,sidecar), and refuses other names with HTTP 400.
If you reach the sidecar by a domain name (a reverse proxy, a tailnet name), add it to
NMOS_ALLOWED_HOSTSin.env(for examplerisu.example.com,*.ts.net) or set a token. With a token
nothing changes. -
Google Vertex AI is verified against the real service (audit A-10; owner decision). A key's token
exchange, the connection test, extraction and recall work withgoogle/gemini-3.8-flash(ADR 0022).
One pitfall showed up: a key whose service account lacks the Vertex AI User role
(roles/aiplatform.user) gets HTTP 403, even with the APIs enabled. The connection test now says which
role to grant. -
A restart during a long startup backfill resumes it (audit A-08). The sidecar's startup work
(normalized text after a normalizer change, turn data, parser state, missing jobs) ran as one
transaction. A restart before it finished discarded every batch already written, and the next start
began again. Each step now commits on its own, the batched backfills batch by batch. Parser-state
backfill and generation activation stay atomic. A startup that fails no longer leaves an open
connection pool behind. -
Sidecar and worker always get the same settings (audit A-03). The development
docker-compose.yml
passedNMOS_LLM_JSON_MODEto the worker only. Both processes build the extraction generation from their
settings, so withNMOS_LLM_JSON_MODE=0(and no value saved in the panel) the worker never picked up the
sidecar's jobs and extraction stayed pending. Both compose files now give the two services one shared
environment, andNMOS_EXTRACT_HINTS(documented, but never passed to the containers) is included. The
worker also logs a warning when queued jobs of the active generation match none of its handlers. A test
checks that every documented variable reaches both services. -
The per-message extraction window is retired (audit A-15, ADR 0031; owner decision). Extractions made
before 0.1.0-beta.8 (per message,extract-v3and earlier) no longer serve facts. If your database still
relies on them, those turns show no facts until they are extracted again: the recent turns at the next
sync, older ones with "Extract all history" in the Inspector. Extractions since 0.1.0-beta.8 are
unaffected.NMOS_EXTRACT_WINDOWis removed and ignored if set. -
Known issue K27: an OOC note or memory-like markup inside a reply can become a fact (audit A-12). The
first measurement, with the owner's extraction model, is indocs/perf/memory-poisoning.md: a
[System: …]line and a typed command were ignored, but an OOC note and a packet-shaped<Fact …>
inside a reply were stored as facts every time. The fix, a line in the extraction prompt, waits for the
next extractor generation, so re-extraction is paid once (owner decision).tools/eval_poisoning_model.py
re-runs the check. -
Long chats with extraction and embeddings on need a higher deadline, measured (audit A-09, docs). On
PocketRisu v1.12.0 with 10,000 messages, 15,000 facts and 15,000 vectors, recall adds 0.4–0.7 s. A warm
generation then takes 3.1–3.3 s, over the 3 s default, so those requests go without memory. Raise
Deadline (ms) to about 4,000 for such chats. K1 and the README give the numbers. -
Upgrades from earlier releases are tested, and backup and rollback are documented (audit A-16). CI
restores databases that 0.1.0-beta.7 and 0.1.0-beta.16 wrote, with their own code, and upgrades them. It
checks that the chats stay in sync, the earlier facts stay served, the story goes on, and the worker,
delete and rebuild work. The README and the Korean guide give the backup, upgrade and rollback commands.
The rollback restores a backup; migrations only go forward. -
Docs follow the code again (audit A-07). The README lists every plugin argument (
route,language,
hudwere missing) and no longer callsextract-v10unreleased. K1's 10,000-message margin is stated
for what was measured: chats with no facts or vectors; extraction and embeddings add to it. CI now fails
when a summary's host-fact, decision, known-issue, ADR or phase range falls behind. -
Invariant 9 now says what the code does: storage is PostgreSQL, and replacing it is not a goal (owner
decision on audit A-06).
Before upgrading: back up the database (README, "Upgrade, backup and rollback"). If you reach a
tokenless sidecar by a domain name, add that name to NMOS_ALLOWED_HOSTS in .env first, or requests
will get HTTP 400. This covers a reverse proxy that keeps the browser's host, or a tailnet name. Addresses,
localhost and Docker names such as nmos need nothing. Then pull the new sidecar image and restart both
services, replace the plugin file, and reload PocketRisu.
Known limitations
- An OOC note or packet-shaped markup inside a reply can become a fact (K27). The prompt fix waits for the
next extractor generation. - With extraction and embeddings on, a chat of about 10,000 messages needs a deadline above the 3 s
default (K1). The plugin now says so and suggests a value. - A bot whose Lua
requesttrigger rewrites the injected system message into another role can get the
memory twice when the host retries a failed request (H2, H3). - Databases from before 0.1.0-beta.8 lose their per-message facts until those turns are extracted again
(ADR 0031). - Otherwise unchanged from 0.1.0-beta.20; the full list is
docs/KNOWN-ISSUES.md.
NMOS v0.1.0-beta.20
Three fixes from the 2026-09-26 audit (docs/audits/NMOS-AUDIT-2026-09-26.md, review
docs/audits/NMOS-AUDIT-2026-09-26-REVIEW.md). No schema, generation or hash change: nothing is
re-extracted or re-embedded.
-
A broken emoji no longer stops a chat's sync (audit A-01, ADR 0029, sidecar). A message holding half
of an emoji (a lone UTF-16 surrogate, e.g. cut by a script) made every sync of that chat fail with HTTP
500, so the chat went on without memory. A persona, chat or character name with one failed every sync
the same way. The sidecar now checks the message as sent and stores U+FFFD in place of the broken half.
The same applies to names, the recall query, extraction replies and stored JSON. The plugin is unchanged. -
A reply quoting the memory tag no longer turns memory off (audit A-02, plugin). The plugin took any
message containing<NarrativeMemory version="0" source="nmos">for its own injected packet and
skipped the request as a host retry (H2). Once a reply (or your own message) quoted that tag, the chat
silently got no sync and no memory until the message left the prompt. Only a system message now counts. -
One bad job no longer stops the worker (audit A-04, worker). An error other than the expected
provider, database and validation errors (for example a provider reply with"message": null) ended
the worker thread while the process kept running, so extraction and embedding stopped with jobs left
pending. Such an error now fails that job (retried with backoff, thendead) and the worker goes on;
malformed chat replies are reported as provider errors.
Pull the new sidecar image (sidecar and worker) and restart. Replace the plugin file and reload PocketRisu.
Each fix works on its own: an older plugin with this sidecar gets the A-01 and A-04 fixes.
Known limitations
- A message whose text holds half an emoji is stored with U+FFFD in its place (ADR 0029); recall and the
Inspector show that character. A host message id holding one is refused (HTTP 422); it has not been seen. - A bot whose Lua
requesttrigger rewrites the injected system message into another role can get the
memory twice when the host retries a failed request (H2, H3). - Otherwise unchanged from 0.1.0-beta.19; the full list is
docs/KNOWN-ISSUES.md.
NMOS v0.1.0-beta.19
Phase 9 — Accountable Packets (ADR 0027, D39), plus two owner-reported fixes to what the packet keeps:
standing facts first (ADR 0026, D37) and speech level and forms of address (ADR 0028, D38). Schema:
migration 0020 (applied at startup). New extractor generation extract-v10. The plugin code is unchanged
apart from its version.
-
Settled relationships stay in the packet in crowded scenes (ADR 0026). When a message names
several characters, every fact about them scored the same, and a fact's list of who knows it decided
the order, so trivia ("엘피 knows: 계란 껍질 …") took the few slots a 600-token packet has. A character's
agreement to speak 반말 ranked 23rd and was forgotten. Now, among facts of equal mention, how two
characters stand (relationship,feels_toward) comes first, then major events, and standing facts
get the budget before promise threads. A name in a fact's "known by" list no longer ranks it. -
Speech level and forms of address are remembered (ADR 0028). New extractor generation
extract-v10
with the factaddresses: how one character speaks to and calls another, one per direction, e.g.
"라디아 addresses {{user}}: 반말, '유우마'라고 부름". It is recorded when the story settles it (an
agreement, a requested form of address, a decided change back), not when a reply merely slips into
another speech level, and a newer one replaces the older. It ranks with relationships. On upgrade each
chat's recent turns are re-extracted once at the provider's cost; run "Extract all history" in the
Inspector for older turns. -
See how much memory fit. Inspector → conversation → Retrievals shows facts as kept/offered.
If most facts do not fit, raise 기억 예산(토큰) / Memory budget (tokens) in the panel (and lower the host's max
context by the same amount). -
Raw words get room again. On real chats the facts filled the whole memory budget, and an excerpt
(a password, the words of a promise, a line of dialogue) reached the model in 7 of 168 requests. The
new packet compiler (packet-v1) keeps room for the best excerpt, shortening it to its best sentence
when needed, and caps status-window state. In an answer probe with a real response model, questions whose
answer was only in a message's words were answered in 6 of 6 runs withpacket-v1when recall found
the message, and in none with the earlier compiler, which never placed it
(docs/perf/phase9-packets.md).NMOS_PACKET_POLICY=packet-v0restores the earlier compiler. -
Every line of the packet is accounted for. Each request records every line it offered: the fact,
claim, promise, excerpt or state value and the assertion or message it came from, its cost, and whether
it went in or why not. The Inspector shows this as Last packet, and the retrievals table shows what
each packet held. -
What the reply used. Once the reply to a request is in the chat, the Inspector shows which placed
lines the reply reused, and flags a hidden fact the reply repeated (a possible leak). This is a report
only. -
Replay. A recorded request can be compiled again exactly as it was, as of its own time, or with
another packet compiler:GET /v1/trace/{id}/replay, andtools/replay_packets.pyfor an offline
comparison over many requests (read-only). -
Fix: your own message is no longer recalled as memory. A short message (under 16 characters) as
the first of a chat was not recognized as already in the prompt and came back as an excerpt of itself.
The latest message is now always treated as in the prompt.
Upgrading. Pull the new sidecar image and restart it. Migration 0020 is applied at startup; it adds
nullable columns and needs no backfill. With an LLM configured, extract-v10 becomes active, and each
chat's latest NMOS_EXTRACT_BACKFILL turns (default 100) are re-extracted once at the provider's cost.
Older turns keep their earlier facts until Extract all history. Replacing the plugin file is optional
(its code is unchanged). If you replace it, reload PocketRisu.
Known limitations
- A speech level or form of address can still be missed or cut (K25), and turns before
extract-v10
have none until "Extract all history". - Traces recorded before this release cannot be replayed or audited. Real-chat comparisons of packet
policies need traces recorded from now on. - The packet's token estimate still over-counts Korean, so part of the reserve goes unused (K26).
- Echo is a surface measure: a secret the reply rightly keeps is used without being echoed.
- Otherwise unchanged from 0.1.0-beta.18; the full list is
docs/KNOWN-ISSUES.md.
NMOS v0.1.0-beta.18
Plugin only: the Status tab shows the memory the last request injected. No schema change; the sidecar
is unchanged apart from its version.
- See what memory went in. The Status tab's "Last request" card has Show the injected memory,
which opens the exact text the last request carried. The progress display only gave its length. The
text stays in the plugin's memory until the page reloads; nothing new is stored. A reroll served from
the plugin's cache now updates the card too.
Replace the plugin file and reload PocketRisu. Updating the sidecar image is optional.
Known limitations
- Only the last request's memory is shown, and only until the page reloads. Earlier requests keep only
counts (Inspector → conversation → Retrievals). - Otherwise unchanged from 0.1.0-beta.17; the full list is
docs/KNOWN-ISSUES.md.
NMOS v0.1.0-beta.17
Salience by what an event changes, names revealed later, and owner links between names (ADRs 0024,
0025, D35, D36). Schema: migration 0019 (applied at startup). New extractor generation extract-v9.
- Important events are judged by what they change (ADR 0024). The extractor labeled turning points
told only in wordsminor: a change from formal to informal speech, a new form of address, a
relationship someone allowed, an admission of responsibility; and an incident everyone had to deal with
(a measuring device bursting) as well. A minor event comes back only when you ask about it, not when
you name the people involved.extract-v9names these categories; routine business (meals, chores,
travel) stays minor. - Someone shown without a name joins their name later (ADR 0024). The extractor writes such a
character as a?description (e.g.?검은 망토의 남자), lists them to later turns, and records the
name when a turn reveals it. Until then the?in the packet tells the response model the identity is
unknown. - Join two names by hand (ADR 0025). On an entity's page in the Inspector tab, "Same as another
entity" joins it with another entity of the same type; "Undo" takes it back. Joins survive "Rebuild
memory" and need no re-extraction. - Entity ids change once (
resolve-v4); Inspector links saved before the upgrade no longer open. - Switching to
extract-v9re-extracts each chat's recent window (NMOS_EXTRACT_BACKFILLturns). Older
turns keep theirextract-v8labels and names until "Extract all history".
Upgrade both parts (migration 0019 is applied at startup), then replace the plugin file and reload
PocketRisu.
Known limitations
- An admission is still often recorded as the past act it tells of and labeled minor (2 of 3 runs
major on the owner's chat). Ongemma4:31bshort dialogue scenes sometimes yield no event at all, so
no label (docs/perf/extract-v9.md). - A name the model links to the wrong listed description stays linked until its turn is edited or
deleted; there is no owner split. Names written byextract-v8have no?and are joined only by
hand. - More major events means a character with many of them fills the 3-event cap with major ones.
- Each extraction prompt is about 350 tokens (≈19 %) longer.
- Otherwise unchanged from 0.1.0-beta.16; the full list is
docs/KNOWN-ISSUES.md.
NMOS v0.1.0-beta.16
Bug fix: a named persona is the persona (ADR 0023, D34). Schema: migration 0018 (applied at startup). Upgrade both parts, then replace the plugin file and
reload PocketRisu.
- A named persona is the persona (ADR 0023). The extractor wrote your persona both as
{{user}}
and by its name (e.g. 유우마), and NMOS kept them as two characters: two current locations, split
promises, and every fact naming 유우마 counted as mentioned in every message you narrate by name.
The plugin now reads the persona's name from PocketRisu (the chat's bound persona, else the selected
one) and sends it with each sync; both spellings are one entity, the persona's names never count as a
mention, and KNOWN ENTITIES leaves it out. Facts already extracted join up at the next read, with no
re-extraction. - A second permission dialog at load. PocketRisu asks "Plugin nmos_memory is requesting to access
the full database, which may expose sensitive information." NMOS reads only persona names with it.
Answering No leaves the behavior as before (reset: Settings → Plugin → NMOS row menu → Reset
permission responses). - Entity ids change once (
resolve-v3); Inspector character links saved before the upgrade no
longer open.
Known limitations
- A chat's persona joins up at its first sync with the new plugin. Without the database permission
(answered No, or an older plugin) nothing changes from 0.1.0-beta.15. - NMOS follows the persona PocketRisu uses now. A different persona bound to the chat later takes over
the persona role, and facts written under the old name become an ordinary character again. - A persona name that is also another character's name merges the two (K8).
- The extraction prompt still allows either spelling (
{{user}}or the name); both resolve to one
person. Asking the model for one spelling would be a new extractor generation, deferred. - Otherwise unchanged from 0.1.0-beta.15; the full list is
docs/KNOWN-ISSUES.md.