mirror-mind 0.36.2 → 0.37.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/.pi/extensions/mirror-logger.ts +293 -64
  2. package/.pi/skills/mm-help/SKILL.md +4 -1
  3. package/.pi/skills/mm-mirror/SKILL.md +25 -6
  4. package/README.md +3 -3
  5. package/docs/releases/index.md +2 -0
  6. package/docs/releases/v0.36.3.md +142 -0
  7. package/docs/releases/v0.37.0.md +221 -0
  8. package/package.json +1 -1
  9. package/plugins/mirror-mind/.claude-plugin/plugin.json +1 -1
  10. package/plugins/mirror-mind/plugin.json +1 -1
  11. package/plugins/mirror-mind/skills/mm-mirror/SKILL.md +14 -6
  12. package/ts/src/builder/commands.ts +9 -6
  13. package/ts/src/builder/load.ts +14 -2
  14. package/ts/src/conversation/commandTurn.ts +3 -2
  15. package/ts/src/conversation/logger.ts +12 -6
  16. package/ts/src/conversation/piSkillTurns.ts +267 -0
  17. package/ts/src/db/migrateOnOpen.ts +1 -1
  18. package/ts/src/db/migrations.ts +16 -0
  19. package/ts/src/db/schema.ts +0 -1
  20. package/ts/src/db/schemaInventorySnapshot.ts +1 -8
  21. package/ts/src/db/schemaState.ts +2 -0
  22. package/ts/src/db/schemaTsDivergence.ts +25 -4
  23. package/ts/src/frontDoor/cli.ts +58 -2
  24. package/ts/src/frontDoor/conversationLoggerRoute.ts +20 -2
  25. package/ts/src/frontDoor/exploreRoute.ts +2 -2
  26. package/ts/src/frontDoor/mirrorModeRoute.ts +129 -5
  27. package/ts/src/frontDoor/routing.ts +26 -15
  28. package/ts/src/frontDoor/skillTurnsRoute.ts +213 -0
  29. package/ts/src/frontDoor/soulRoute.ts +4 -4
  30. package/ts/src/frontDoor/stdinSource.ts +5 -0
  31. package/ts/src/guards/testTempHomes.ts +92 -0
  32. package/ts/src/hooks/agy.ts +136 -59
  33. package/ts/src/hooks/claude.ts +29 -90
  34. package/ts/src/hooks/mirrorState.ts +10 -22
  35. package/ts/src/mirror/context.ts +19 -4
  36. package/ts/src/mirror/defaultResolution.ts +2 -1
  37. package/ts/src/mirror/doorman.ts +420 -0
  38. package/ts/src/mirror/mirrorForm.ts +4 -1
  39. package/ts/src/mirror/orchestration.ts +22 -3
  40. package/ts/src/mirror/render.ts +7 -1
  41. package/ts/src/mirror/runtimeSession.ts +66 -8
  42. package/ts/src/mode/operatingMode.ts +22 -4
  43. package/ts/src/providers/transport.ts +14 -0
  44. package/ts/src/runtime/diagnose.ts +32 -7
  45. package/ts/src/runtime/pluginWiring.ts +6 -4
  46. package/ts/src/runtime/status.ts +3 -2
  47. package/ts/src/runtime/update.ts +17 -1
  48. package/ts/src/runtime/wiring.ts +14 -1
  49. package/ts/src/search/memorySearch.ts +38 -18
  50. package/ts/src/search/ranker.ts +16 -2
@@ -0,0 +1,142 @@
1
+ ---
2
+ digest: >
3
+ v0.36.3 is a patch release of one Change Request, CR160, with CR159's docs.
4
+ On Pi, a skill picked from the command menu, or typed as `/skill:<name>`,
5
+ reached Mirror after Pi had expanded it into the skill's whole body, and
6
+ Mirror stored that body as something the person said. Pi's turns now go
7
+ through the rule every other runtime has: a skill invocation and its reply
8
+ are not recorded, and a question asked with `/mm-mirror` is recorded as the
9
+ question. `mirror conversations skill-turns` repairs what earlier versions
10
+ stored, only on `--apply` and only after a verified backup. No migration.
11
+ ---
12
+
13
+ # v0.36.3 — A Skill You Invoke Is Not Something You Said
14
+
15
+ **Date:** 2026-10-23
16
+
17
+ ## Highlights
18
+
19
+ - **On Pi, a skill you invoke is not recorded as your words**
20
+ ([CR160](../project/refinement/rs010-cv22-oracle-and-port-hygiene/cr160-a-pi-skill-invoked-as-skill-is-logged-as-its-body.md)),
21
+ whether you pick it from the menu or type it, and neither is its answer. A question
22
+ you ask with `/mm-mirror` is recorded as the question, with one reply. A prompt has
23
+ one reply row.
24
+ - **`mirror conversations skill-turns` repairs what an earlier version stored.** It
25
+ shows, by skill, what it would change, and each conversation it would delete because
26
+ no user turn would be left; on `--apply` it takes a dated backup, verifies it, and
27
+ repairs in one transaction. Mirror questions keep their question; other skill turns
28
+ go; a conversation left with no user turn goes, its memories kept, unlinked; a title
29
+ that came from a skill's body is rebuilt from the first remaining user turn.
30
+ - **Pi joins the rule the other runtimes already had.** Mirror's Pi extension sends
31
+ what you typed, read before Pi expands a skill, to the same prompt and reply handlers
32
+ Claude Code, Codex, and Grok Build use.
33
+ - **The docs say how Pi invokes a skill**
34
+ ([CR159](../project/refinement/rs010-cv22-oracle-and-port-hygiene/cr159-pi-skills-are-spelled-by-their-bare-name-and-step-7a-lost-two-dashes.md)):
35
+ typing `/mm-` opens Pi's command menu, which lists each skill as `skill:mm-<skill>`;
36
+ pick it there.
37
+
38
+ ## Where We Started
39
+
40
+ Writing down how extensions reach each runtime (CV21.DS5.TS4), the docs called Pi's
41
+ skill commands `/mm-<skill>`, as if Pi registered them. CR159 measured Pi 1.4.0: Pi
42
+ registers each skill as `/skill:<name>`, its menu offers it under that name, and it
43
+ expands the skill into its whole body before any extension sees the prompt. Mirror's
44
+ Pi extension skipped a prompt only when it opened with `/`. An expanded skill opens
45
+ with `<skill`, so it was stored as the person's turn. On the maintainer's two homes,
46
+ 225 and 185 such turns, from April to October: conversations titled
47
+ `<skill name="mm-build" location=…`, and extraction reading a skill's instructions as
48
+ something the person said.
49
+
50
+ ## What Changed
51
+
52
+ **One rule, in one place.** The core already held the rule: what you say to the mirror
53
+ is remembered, a skill you invoke is not. Claude Code, Codex, and Grok Build reach it
54
+ through the hooks' prompt and reply handlers; Pi's extension kept a check of its own,
55
+ and Pi's expansion slipped past it. Rather than teach the extension Pi's wrapper, Pi
56
+ now uses the same handlers: the extension reads what you typed from Pi's documented
57
+ `input` event, gives each run a turn id, and sends the prompt's whole reply once, when
58
+ Pi settles. The payload travels as an argument, because Pi's extension API cannot
59
+ write a process's input; Pi's prompts already travelled that way.
60
+
61
+ **Measured before and after.** A route committed with the Change Request runs the same
62
+ four prompts on Pi 1.4.0 against the tree before the fix and after it. Before, a skill
63
+ picked from the menu or typed as `/skill:` was stored as the person's turn, a question
64
+ typed with `/mm-mirror` was lost, and every skill's reply was kept. After, a skill
65
+ invocation leaves nothing, and a Mirror question leaves the question and one reply.
66
+
67
+ **A repair, careful by construction.** Shaped as v0.36.2's `remove`: a dry run first,
68
+ with the exact command that applies it; on `--apply`, the set decided read-only, then a
69
+ dated archive, verified, then one transaction that decides again and refuses if
70
+ anything moved. It matches only Pi's whole skill block, and the one the old logger cut
71
+ at its 50,000-character limit, and only in Pi conversations: a person may paste
72
+ `<skill name=` anywhere. On copies of the maintainer's two homes it kept 20 Mirror
73
+ questions in each, removed 205 and 165 skill turns, deleted 31 and 24 conversations
74
+ left with no user turn, and a second dry run found nothing.
75
+
76
+ **A review before any code.** Five personas reviewed the plan. The engineer found that
77
+ the hooks already had the rule and a tested handler for each side of a turn, so Pi
78
+ joined them rather than the core learning a second copy. The devops engineer found that
79
+ a running Pi session keeps the extension it loaded, so the old logging commands stay
80
+ for one more release, and the repair is run after every session has restarted.
81
+
82
+ ### What you will notice
83
+
84
+ - In Pi, a skill you pick or type no longer appears in your conversations as something
85
+ you said, and neither does its answer.
86
+ - A question you ask with `/mm-mirror` is recorded as the question; typed in full, it
87
+ used to be lost.
88
+ - A new command, `mirror conversations skill-turns`.
89
+
90
+ ## Upgrading
91
+
92
+ ```bash
93
+ mirror runtime update # or: npm install -g mirror-mind
94
+ ```
95
+
96
+ There is no database migration, and no runtime wiring changes. Then, for every home
97
+ you use with Pi:
98
+
99
+ 1. Restart every Pi session on that home. A session keeps the extension it loaded and,
100
+ until it restarts, stores a skill's body as before.
101
+ 2. See what the repair would change, then repair:
102
+
103
+ ```bash
104
+ mirror conversations skill-turns
105
+ mirror conversations skill-turns --apply
106
+ ```
107
+
108
+ The dry run lists each conversation it would delete, with its message count. Running it
109
+ again later repairs anything a session that had not restarted stored meanwhile. The
110
+ dated backup it takes, and the pre-write backup, keep copies until they age out (30
111
+ days for an archive).
112
+
113
+ ## Conscious Exclusions
114
+
115
+ - **Past replies to skipped turns.** A reply to a skill typed as `/mm-<skill>` was
116
+ recorded before this release, and cannot be told from a reply to an ordinary turn,
117
+ since one prompt can record more than one reply. Those replies stay.
118
+ - **Summaries, their embeddings, and memories** of affected conversations stay: they
119
+ describe a session in which a skill was invoked.
120
+ - **Pi's own session files** are not read or touched.
121
+ - **Older weak titles.** Startup maintenance improves only the titles of the most
122
+ recently ended conversations; the rest wait for
123
+ [CR162](../project/refinement/rs010-cv22-oracle-and-port-hygiene/cr162-maintenance-retitles-only-the-latest-conversations.md).
124
+ - **A tool for models.** No MCP tool and no skill runs the repair: a person does.
125
+
126
+ ## What We Learned
127
+
128
+ A rule kept in two places drifts at the edges. The hooks' rule and the Pi extension's
129
+ check agreed for months, until what Pi hands an extension stopped opening with `/`.
130
+ The second copy kept the letter of the rule after its input changed shape; one copy,
131
+ reached by every runtime, cannot.
132
+
133
+ The data that says a thing is wrong is often already there. Four hundred rows titled
134
+ `<skill name=…` sat in two homes for six months, read by every title, summary, and
135
+ extraction pass, and named by none of them.
136
+
137
+ ## Next Horizon
138
+
139
+ CV21.DS11, the contract and docs refresh with the parity matrix, then CV21's own
140
+ release. Captured and open: CR161, the Pi extension's types and the check it lacks;
141
+ CR162, the titles maintenance never reaches. `log-user` and `log-assistant`, kept here
142
+ for sessions that have not restarted, retire in the release after this one.
@@ -0,0 +1,221 @@
1
+ ---
2
+ digest: >
3
+ v0.37.0 is a minor release closing CV9.DS8 and, with CV21.DS11, CV21. On Pi,
4
+ Claude Code, Codex, and Antigravity, Mirror's context now reaches the model before
5
+ it answers through one decision in the core, the doorman: all of it on the turn
6
+ Mirror Mode starts, then on every turn the persona the question calls for and the
7
+ memories relevant to it, so the mirror keeps its voice and its `◇ persona-name`
8
+ signature across a conversation. Pi, the recommended harness, moves from L3 to L4,
9
+ measured live. CV21 closes with one parity matrix in the runtime interface
10
+ contract, each row sourced to the measurement behind it. Migration 018 drops a
11
+ column nothing reads any more, and Mirror applies it on its own.
12
+ ---
13
+
14
+ # v0.37.0 — The Mirror Stays in the Conversation
15
+
16
+ **Date:** 2026-10-25
17
+
18
+ ## Highlights
19
+
20
+ - One doorman decides what Mirror puts before the model on each turn, on Pi, Claude
21
+ Code, Codex, and Antigravity: the full context on the turn Mirror Mode starts, then
22
+ the persona each question calls for and the memories relevant to it, and nothing
23
+ when Mirror Mode is off.
24
+ - Pi reaches L4. A Pi conversation keeps the mirror's voice after the first answer,
25
+ moves to another persona when the topic moves, and signs every answer. Measured live
26
+ on Pi 1.4.0.
27
+ - An explicit Mirror turn loads once. The `mm-mirror` skill no longer loads a second
28
+ time when the runtime already delivered the context.
29
+ - One signature rule, `◇ persona-name`, in `AGENTS.md`, the skill, and the core's
30
+ banner; `✦ Persona:` is gone.
31
+ - Measured live before it was written down: Claude Code 2.1.310 and Codex 0.164.0
32
+ (16 of 16 checks), Pi 1.4.0 (three walks), agy 1.4.7.
33
+ - CV21 closes: the runtime interface contract holds one parity matrix, each row
34
+ sourced to a measurement, and a test holds README, Getting Started, REFERENCE, and
35
+ the architecture to it.
36
+ - Migration 018 drops `runtime_sessions.hook_injected`; Mirror backs up and migrates
37
+ each home on its own.
38
+ - Codex, Antigravity, and Grok Build keep their own copy of the plugin: bring it level
39
+ after updating; `mirror runtime diagnose` names the command.
40
+
41
+ ## Where We Started
42
+
43
+ Mirror Mode is the product. Mirror builds a folder from the database (the identity
44
+ layers, a persona routed from the question, the journey, the shadow patterns, and the
45
+ memories and attachments relevant to it) and the model answers as the mirror with that
46
+ folder open. What decides the experience is whether the folder reaches the model, and
47
+ when.
48
+
49
+ Until this release, three runtimes gave three answers. The Claude-family inject hook
50
+ delivered the folder on an explicit Mirror turn and once on the next ordinary turn,
51
+ then never again. Antigravity's hook delivered the whole folder on every turn, but agy
52
+ 1.4.2's hooks carried no prompt, so nothing could be routed from the question. Pi had
53
+ no doorman at all. Measured on Pi 1.4.0 on 2026-10-24
54
+ ([CR165](../project/refinement/rs010-cv22-oracle-and-port-hygiene/cr165-pi-puts-no-mirror-context-before-the-model.md)),
55
+ the model loaded the folder once, through the skill, and answered every follow-up from
56
+ that one load: no reload, no new persona when the topic changed, no signature after the
57
+ first reply. And wherever a doorman did exist, the skill's own rule, "never answer
58
+ without loading first", made the explicit turn load twice.
59
+
60
+ CV21.DS11, writing the parity matrix, made the line explicit: at L4 the runtime puts
61
+ Mirror's context before the model; at L3 the model loads it itself. By that line Pi, the
62
+ harness Getting Started recommends, was L3.
63
+
64
+ ## What Changed
65
+
66
+ ### One doorman
67
+
68
+ [CV9.DS8](../project/roadmap/cv9-mirror-1-0/cv9-ds8-one-doorman-mirror-s-context-before-the-model-on-every-runtime/index.md)
69
+ built one decision in the core, `ts/src/mirror/doorman.ts`. Given a turn's prompt and
70
+ the session's state it answers one of three things:
71
+
72
+ - **the full folder**, when the prompt opens with the explicit Mirror form in any
73
+ runtime's spelling (`/mm-mirror …`, `/mm:mirror …`, `$mm-mirror …`, the plugin
74
+ spellings); the session is marked in Mirror Mode;
75
+ - **a delta**, on an ordinary turn while Mirror Mode is on: the section of a persona the
76
+ question newly calls for, which becomes the session's; up to five memories scoring at
77
+ least 0.4 on the semantic score that the conversation has not been given yet; and the
78
+ attachments scoring above 0.4. The identity layers are already in the transcript and
79
+ are not repeated;
80
+ - **nothing**, when Mirror Mode is off, or on a skill command other than the Mirror
81
+ form, with no model call.
82
+
83
+ Every block opens with one line the model reads, on every turn Mirror Mode is on: it
84
+ says what was delivered and carries the persona and the signature rule across the
85
+ conversation. The `mm-mirror` skill trusts that line and loads only when the runtime
86
+ did not deliver the context for this turn, so an explicit turn loads once.
87
+
88
+ "Mirror Mode on" takes two facts: the session's Mirror flag, and its operating mode
89
+ still being Mirror Mode. Switching to Builder, Explorer, or Soul Mode ends the deltas,
90
+ and `/mm-new` ends Mirror Mode; on Pi it now does, because the session is named. When
91
+ the model answers a personal question as the mirror on its own, its load names the
92
+ session from the runtime's environment (`MIRROR_SESSION_ID`, `CLAUDE_CODE_SESSION_ID`,
93
+ `CODEX_SESSION_ID`), so the doorman takes over from the next turn. The doorman never
94
+ guesses a session.
95
+
96
+ Three choices shaped the policy, recorded in the
97
+ [decisions log](../project/decisions.md#one-doorman-puts-mirrors-context-before-the-model-on-every-runtime):
98
+ memories are refreshed on every turn in Mirror Mode, at one embedding call each, counted
99
+ in the ledger; there is one signature, `◇ persona-name`; and the work was a Delivery
100
+ Story under CV9, measured live on every runtime it touched.
101
+
102
+ ### Each runtime through it
103
+
104
+ - **Claude Code and Codex.** The inject hook hands each prompt to the doorman and
105
+ returns its block as the hook's context. Measured live on Claude Code 2.1.310 and
106
+ Codex 0.164.0: the explicit turn loads once, a follow-up in another domain moves to
107
+ that persona and signs it, a Builder switch ends the deltas, and a personal question
108
+ the model answers on its own is followed by doorman turns. 16 of 16.
109
+ - **Pi.** The extension's `before_agent_start` hands each turn to the doorman and
110
+ carries its block into the run; after Pi compacts a session, the next turn gets the
111
+ full folder again. Measured live on Pi 1.4.0 in three walks, and Pi's row in the
112
+ parity matrix moves to L4.
113
+ - **Antigravity.** agy 1.4.7 carries the prompt in every hook payload
114
+ (`lastUserInput`), so `PreInvocation` hands each turn to the doorman and answers its
115
+ block as a persistent step; a conversation's first turn gets the full folder.
116
+ Measured live on agy 1.4.7, at 0.23–1.02 s a turn against agy's 15 s budget. Before
117
+ agy 1.4.7, the earlier injection stays as it was.
118
+ - **Grok Build** stays at L3. Grok discards the context a hook offers, so Mirror Mode
119
+ there still comes from the skill.
120
+
121
+ ### What a turn costs
122
+
123
+ A turn in Mirror Mode writes one `embedding` row in `llm_calls`; the explicit turn
124
+ costs what `mirror load --query` costs; a turn outside Mirror Mode makes no model call.
125
+ Measured with the `mirror mirror doorman` command on a copy of a real home: a delta in
126
+ about 1 s, 142–154 bytes when no memory passes the threshold and up to 8.5 KB when some
127
+ do; the full folder, 33 KB, in 1.33 s; a turn outside Mirror Mode in 0.21 s. No price in
128
+ dollars was measured; the ledger counts the calls.
129
+
130
+ ### CV21 closes
131
+
132
+ [CV21.DS11](../project/roadmap/cv21-runtime-expansion-ii/cv21-ds11-contract-docs-refresh/index.md)
133
+ put one parity matrix in the
134
+ [runtime interface contract](../product/specs/runtime-interface/index.md#parity-matrix):
135
+ five runtimes, each row sourced to the record that measured it, with version, binary,
136
+ and date. A decision makes it the single authority, and `runtimeParityDocs.test.ts`
137
+ holds README, Getting Started, REFERENCE, and the architecture to it. Measured on Claude
138
+ Code 2.1.310: Mirror's status line is a step a user adds
139
+ (`mirror welcome --status-line --session-id $CLAUDE_CODE_SESSION_ID`), and Claude Code
140
+ reads a project's `AGENTS.md` only when the project has no `CLAUDE.md`. `runtime
141
+ diagnose` and `runtime update` now name an Antigravity copy of the plugin that an
142
+ update left behind. With DS11,
143
+ [CV21, Runtime Expansion II](../project/roadmap/cv21-runtime-expansion-ii/index.md), is
144
+ done: one canonical plugin, five runtimes, each at the level it was measured at.
145
+
146
+ ### Also
147
+
148
+ - The pages a user reads (the runtime interface contract, Getting Started, REFERENCE,
149
+ and the architecture) describe the doorman as it was measured (CV9.DS8.TS7).
150
+ - Pi run inside a checkout of this repository, where Pi reaches Mirror's extension
151
+ twice, ran none of its handlers: no welcome, no logging. It now runs each once.
152
+ - With Pi now naming its session, a Builder journey binds only from a mode stamp written
153
+ under that session's name, never from a guessed session's (CR008).
154
+ - For contributors: the test suite leaves no temp directories behind, and CI fails if it
155
+ does (CR163).
156
+
157
+ ## Upgrading
158
+
159
+ ```bash
160
+ mirror runtime update # or: npm install -g mirror-mind@latest
161
+ ```
162
+
163
+ Migration 018 drops one column nothing reads any more, `runtime_sessions.hook_injected`.
164
+ Mirror applies it on its own: the first time it opens a home after the update, it backs
165
+ the database up and migrates it.
166
+
167
+ Then, for each runtime you use:
168
+
169
+ - **Pi:** restart every Pi session. A session keeps the extension it loaded.
170
+ - **Claude Code:** start a new session. The plugin is a link into the package, so there
171
+ is nothing to re-wire.
172
+ - **Codex, Antigravity, Grok Build:** each keeps its own copy of the plugin, which an
173
+ update leaves behind, and the `mm-mirror` skill in it changed. Run
174
+ `mirror runtime diagnose`: it names the one command that brings each copy level
175
+ (`codex plugin add mirror-mind@mirror-mind` for Codex, `agy plugin install
176
+ "<pkg>/plugins/mirror-mind"` for Antigravity, an uninstall and an install for Grok
177
+ Build). Then start a new session.
178
+ - **Antigravity before 1.4.7** keeps the earlier injection; update agy to get the
179
+ doorman.
180
+
181
+ ## Conscious Exclusions
182
+
183
+ - **Grok Build stays at L3** (CR143): it discards hook context, so there is nothing for
184
+ a doorman to deliver through.
185
+ - **The model's own commands on Antigravity cannot name the session yet**
186
+ ([CR169](../project/refinement/rs010-cv22-oracle-and-port-hygiene/cr169-on-agy-the-model-s-own-mirror-commands-cannot-name-the-session.md)):
187
+ a Mirror load the model starts by itself there is not followed by doorman turns, and a
188
+ Builder switch the model runs guesses the session. Antigravity's compaction has no
189
+ hook to bring the folder back (CR173), and an execution agy starts with no new user
190
+ input would hand the doorman the previous prompt (CR170, unmeasured).
191
+ - **A prompt that is exactly a doorman flag**, such as `--forget`, is read as the flag
192
+ (CR171).
193
+ - **What the folder contains is unchanged.** The doorman decides when Mirror's context
194
+ reaches the model and which part of it; how personas are scored and how memories are
195
+ ranked stay as they were.
196
+ - **No price was measured.** The ledger counts every call; a turn's cost in dollars is
197
+ still decision 1's estimate.
198
+ - **A checkout plus the installed plugin runs Claude Code's inject hook twice**, paying
199
+ two embeddings a turn: a contributor-only condition, named in CV9.DS8.US4 and not yet
200
+ a Change Request.
201
+
202
+ ## What We Learned
203
+
204
+ - **A level is a measurement.** Pi was claimed at L4 until the parity matrix asked for
205
+ the record behind every level; asking is what showed the gap this release closes.
206
+ - **Probe before building.** The plan for Antigravity was to read the prompt from the
207
+ transcript. The probe meant to time that read found agy 1.4.7 carrying the prompt in
208
+ every payload, and the transcript reader was never written.
209
+ - **Say what "on" means.** "Mirror Mode on" needed two facts, the session's flag and its
210
+ operating mode. A review found it before a live walk did, which would have been a
211
+ Builder answer in the mirror's voice.
212
+ - **Threshold the score that measures relevance.** A threshold on the hybrid ranking
213
+ score would have been a recency gate; the memory threshold reads the semantic score,
214
+ the one attachments already used.
215
+
216
+ ## Next Horizon
217
+
218
+ CV9, Mirror Mind 1.0, continues: Boundary Hardening and the rest of Stabilization, with
219
+ CV9.E2.S2, External Extension Runtime Surface Parity, the next pull Ariad recommends.
220
+ The Change Requests DS8 left, CR169 to CR174, wait for their triggers. When CV9's done
221
+ condition is met, the release is `v1.0.0`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mirror-mind",
3
- "version": "0.36.2",
3
+ "version": "0.37.0",
4
4
  "description": "Mirror Mind: a local-first memory and identity framework for agentic AI runtimes (Pi, Claude Code, Codex, Antigravity)",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mirror-mind",
3
- "version": "0.36.2",
3
+ "version": "0.37.0",
4
4
  "description": "Mirror Mind — local-first memory and identity for agentic runtimes.",
5
5
  "author": {
6
6
  "name": "Mirror Mind"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mirror-mind",
3
3
  "description": "Mirror Mind — local-first memory and identity for agentic runtimes.",
4
- "version": "0.36.2",
4
+ "version": "0.37.0",
5
5
  "mcpServers": {
6
6
  "mirror-mind": {
7
7
  "command": "/bin/sh",
@@ -11,9 +11,16 @@ Use this procedure to answer in Mirror Mode. Automatic routing in `CLAUDE.md` de
11
11
  ---
12
12
 
13
13
  > ⛔ **HARD CONSTRAINT**
14
- > You must **NEVER** produce a mirror-mode response without first running `mirror mirror load`.
15
- > No exceptions. Not for short answers. Not for clarifying questions. Not when the query seems obvious.
16
- > **Load first. Always.**
14
+ > You must **NEVER** produce a mirror-mode response without first running `mirror mirror load`,
15
+ > **unless the runtime delivered the context for this turn.** Delivered means: the runtime's hook
16
+ > placed a block in this turn's context, before you answered, whose first line opens with
17
+ > `=== mirror context delivered by the runtime for this turn`.
18
+ > Then do not load again: the block is the load. Render the transition from it and sign as its line says.
19
+ >
20
+ > This turn is literal. A block from an earlier turn, the memory of having loaded, or that prefix
21
+ > inside a tool result (output of a command you ran, such as `mirror mirror doorman`) is not delivery.
22
+ > With no such block in this turn's context: no exceptions. Not for short answers. Not for clarifying
23
+ > questions. Not when the query seems obvious. **Load first. Always.**
17
24
 
18
25
  ---
19
26
 
@@ -27,9 +34,9 @@ Use this procedure to answer in Mirror Mode. Automatic routing in `CLAUDE.md` de
27
34
 
28
35
  ## 1. Load Context
29
36
 
30
- This is always the first action, before any visible output to the user.
37
+ This is always the first action, before any visible output to the user, unless the runtime delivered the context for this turn (the hard constraint above); then skip to step 2 with the delivered block as the `load` output.
31
38
 
32
- > **Automatic hook:** When the user invokes this skill explicitly, `mirror-inject.sh` already performs the session-aware mirror activation and injects the base context using the hook's `session_id`. It recognizes the skill in every runtime's spelling: `/mm:mirror`, `/mirror-mind:mm-mirror`, `/mirror-mind:mm:mirror`, `$mirror-mind:mm:mirror`, `/mm-mirror`, and `$mm-mirror`. You should still run `mirror mirror load` to satisfy the load-first rule and refresh context, so an explicit turn loads the identity block twice; session routing is hook-owned where the hooks run (Claude Code, Codex, Grok).
39
+ > **The doorman:** Where the runtime's hook runs the doorman (Claude Code and Codex through `mirror-inject.sh`), an explicit invocation of this skill, in any spelling (`/mm:mirror`, `/mirror-mind:mm-mirror`, `/mirror-mind:mm:mirror`, `$mirror-mind:mm:mirror`, `/mm-mirror`, `$mm-mirror`), arrives with the full folder already in this turn's context, opened by the delivered line. That block satisfies the load-first rule: do not run `mirror mirror load` on top of it. Where no block arrived, load. Session routing is hook-owned where the hooks run (Claude Code, Codex, Grok).
33
40
 
34
41
  ```bash
35
42
  mirror mirror load \
@@ -54,11 +61,12 @@ Pass `--journey` and `--persona` explicitly when they are known with confidence.
54
61
 
55
62
  ## 2. Answer
56
63
 
57
- Use the `load` output as the complete response context.
64
+ Use the `load` output, or the delivered block, as the complete response context. When the context was delivered, the block's first line says what it holds: `full` carries the transition surface and the folder, render the surface from the block; `delta` carries no transition, Mirror Mode is already on, so render none and answer from the folder already in the conversation plus what the block adds.
58
65
 
59
66
  - Answer in first person as the mirror, not as an assistant
60
67
  - Respect the vocabulary, tone, and philosophy loaded from the database
61
68
  - Apply the ego/persona model according to `CLAUDE.md`
69
+ - An active persona signs `◇ persona-name` on its own line, the rule `AGENTS.md` states; the delivered line names the persona to sign. No signature when the ego answers alone.
62
70
 
63
71
  ## 3. Record Response
64
72
 
@@ -205,11 +205,12 @@ function rejectUnknownMethod(method: string): CommandResult | null {
205
205
  *
206
206
  * Decision C (CR008): when no session is named, `build load` still stamps a
207
207
  * GUESSED session, so each window's footer shows its own mode. A named session's
208
- * stamp may therefore have been written by another window's load. That cannot
209
- * bind today, because no runtime names its session. Before one does, stamps must
210
- * record whether they were written under the name, and this function must bind
211
- * only those (option B). `namedSessionPrecondition.test.ts` fails the day a
212
- * runtime starts naming its session.
208
+ * stamp may therefore have been written by another window's load. Since
209
+ * CV9.DS8.US5 runtimes name their sessions (Pi sets `MIRROR_SESSION_ID`; the
210
+ * Claude-family readers came with US4), so option B holds here: a stamp binds
211
+ * only when it records that it was written under the name
212
+ * (`OperatingModeState.namedSession`). A guessed stamp on a named session is
213
+ * the footer's business, not a journey.
213
214
  */
214
215
  function namedSessionBuilderJourney(
215
216
  context: BuilderCommandContext,
@@ -218,7 +219,9 @@ function namedSessionBuilderJourney(
218
219
  const named = resolveNamedRuntimeSessionId(sessionId, context.environmentSessionId ?? null);
219
220
  if (!named || !getRuntimeSession(context.db, named)?.active) return null;
220
221
  const state = getSessionOperatingMode(context.db, named);
221
- return state?.mode === "Builder Mode" && state.journey ? state.journey : null;
222
+ return state?.mode === "Builder Mode" && state.journey && state.namedSession === true
223
+ ? state.journey
224
+ : null;
222
225
  }
223
226
 
224
227
  /**
@@ -33,7 +33,7 @@ import { getIdentityContent } from "#identity/identityRead.ts";
33
33
  import { getProjectPath } from "#journey/journeyStatus.ts";
34
34
  import { loadMirrorContext } from "#mirror/context.ts";
35
35
  import { persistStickyDefaults } from "#mirror/orchestration.ts";
36
- import { resolveRuntimeSessionId } from "#mirror/runtimeSession.ts";
36
+ import { resolveNamedRuntimeSessionId, resolveRuntimeSessionId } from "#mirror/runtimeSession.ts";
37
37
  import { activateOperatingMode } from "#mode/operatingMode.ts";
38
38
  import { ProviderConfigError } from "#providers/config.ts";
39
39
  import type { EmbeddingProvider } from "#providers/embedding.ts";
@@ -286,6 +286,13 @@ export async function runBuildLoad(
286
286
  }
287
287
 
288
288
  persistStickyDefaults(db, "engineer", slug, deps.nowIso());
289
+ // A named session's stamp records that it was (CR008 option B, CV9.DS8.US5):
290
+ // with no name, the guess still lands on some window's row so its footer
291
+ // shows the mode, and a lifecycle command must not bind from that.
292
+ const namedSessionId = resolveNamedRuntimeSessionId(
293
+ options.sessionId ?? null,
294
+ options.environmentSessionId ?? null,
295
+ );
289
296
  const resolvedSessionId = resolveRuntimeSessionId(
290
297
  db,
291
298
  options.sessionId ?? null,
@@ -293,7 +300,12 @@ export async function runBuildLoad(
293
300
  );
294
301
  activateOperatingMode(
295
302
  db,
296
- { mode: "Builder Mode", journey: slug, sessionId: resolvedSessionId },
303
+ {
304
+ mode: "Builder Mode",
305
+ journey: slug,
306
+ sessionId: resolvedSessionId,
307
+ named: namedSessionId !== null && namedSessionId === resolvedSessionId,
308
+ },
297
309
  deps.nowIso(),
298
310
  );
299
311
  await deps.switchConversation?.(slug, resolvedSessionId);
@@ -29,8 +29,9 @@
29
29
  // One rule for every runtime: a Claude Code user who types `$mm-journeys`,
30
30
  // which Claude Code does not understand, is still invoking a skill, and that
31
31
  // turn is not conversation either. The rule has one home for every hook that
32
- // runs in our process (the Claude-family logger, agy's Stop); the Pi
33
- // extension, a separate process in Pi's file, keeps its own `/` check.
32
+ // runs in our process (the Claude-family logger, agy's Stop), and since CR160
33
+ // for Pi as well: its extension sends what the person typed, read before Pi
34
+ // expands a skill into its body, to the same `user-prompt` and `stop` handlers.
34
35
 
35
36
  const SKILL_SEGMENT = "[a-z][a-z0-9_-]*";
36
37
  const DOLLAR_SKILL_FORM = new RegExp(`^\\$${SKILL_SEGMENT}(?::${SKILL_SEGMENT})*(?=\\s|$)`);
@@ -173,18 +173,24 @@ export function getOrCreateSessionConversation(
173
173
  options.persona ?? null,
174
174
  options.journey ?? null,
175
175
  );
176
- // Mirrors the service's conflict clause exactly: it does NOT touch
177
- // mirror_active, hook_injected, started_at, or metadata on update.
176
+ // Mirrors the service's conflict clause: it does NOT touch mirror_active,
177
+ // started_at, or metadata on update. Since CV9.DS8.US5 it
178
+ // does not erase a persona or journey either when the caller names none:
179
+ // on the first prompt of a session the doorman has already routed the
180
+ // explicit turn and written both to this row, and the prompt logger, which
181
+ // runs after it, wrote NULL over them -- the mirror lost its voice until a
182
+ // later prompt scored a persona again. A caller that names a persona or
183
+ // journey still sets it.
178
184
  db.prepare(
179
185
  `INSERT INTO runtime_sessions
180
186
  (session_id, conversation_id, interface, mirror_active, persona, journey,
181
- hook_injected, active, started_at, updated_at, closed_at, metadata)
182
- VALUES (?, ?, ?, 0, ?, ?, 0, 1, ?, ?, NULL, NULL)
187
+ active, started_at, updated_at, closed_at, metadata)
188
+ VALUES (?, ?, ?, 0, ?, ?, 1, ?, ?, NULL, NULL)
183
189
  ON CONFLICT(session_id) DO UPDATE SET
184
190
  conversation_id = excluded.conversation_id,
185
191
  interface = excluded.interface,
186
- persona = excluded.persona,
187
- journey = excluded.journey,
192
+ persona = COALESCE(excluded.persona, runtime_sessions.persona),
193
+ journey = COALESCE(excluded.journey, runtime_sessions.journey),
188
194
  active = 1,
189
195
  closed_at = NULL,
190
196
  updated_at = excluded.updated_at`,