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.
- package/.pi/extensions/mirror-logger.ts +293 -64
- package/.pi/skills/mm-help/SKILL.md +4 -1
- package/.pi/skills/mm-mirror/SKILL.md +25 -6
- package/README.md +3 -3
- package/docs/releases/index.md +2 -0
- package/docs/releases/v0.36.3.md +142 -0
- package/docs/releases/v0.37.0.md +221 -0
- package/package.json +1 -1
- package/plugins/mirror-mind/.claude-plugin/plugin.json +1 -1
- package/plugins/mirror-mind/plugin.json +1 -1
- package/plugins/mirror-mind/skills/mm-mirror/SKILL.md +14 -6
- package/ts/src/builder/commands.ts +9 -6
- package/ts/src/builder/load.ts +14 -2
- package/ts/src/conversation/commandTurn.ts +3 -2
- package/ts/src/conversation/logger.ts +12 -6
- package/ts/src/conversation/piSkillTurns.ts +267 -0
- package/ts/src/db/migrateOnOpen.ts +1 -1
- package/ts/src/db/migrations.ts +16 -0
- package/ts/src/db/schema.ts +0 -1
- package/ts/src/db/schemaInventorySnapshot.ts +1 -8
- package/ts/src/db/schemaState.ts +2 -0
- package/ts/src/db/schemaTsDivergence.ts +25 -4
- package/ts/src/frontDoor/cli.ts +58 -2
- package/ts/src/frontDoor/conversationLoggerRoute.ts +20 -2
- package/ts/src/frontDoor/exploreRoute.ts +2 -2
- package/ts/src/frontDoor/mirrorModeRoute.ts +129 -5
- package/ts/src/frontDoor/routing.ts +26 -15
- package/ts/src/frontDoor/skillTurnsRoute.ts +213 -0
- package/ts/src/frontDoor/soulRoute.ts +4 -4
- package/ts/src/frontDoor/stdinSource.ts +5 -0
- package/ts/src/guards/testTempHomes.ts +92 -0
- package/ts/src/hooks/agy.ts +136 -59
- package/ts/src/hooks/claude.ts +29 -90
- package/ts/src/hooks/mirrorState.ts +10 -22
- package/ts/src/mirror/context.ts +19 -4
- package/ts/src/mirror/defaultResolution.ts +2 -1
- package/ts/src/mirror/doorman.ts +420 -0
- package/ts/src/mirror/mirrorForm.ts +4 -1
- package/ts/src/mirror/orchestration.ts +22 -3
- package/ts/src/mirror/render.ts +7 -1
- package/ts/src/mirror/runtimeSession.ts +66 -8
- package/ts/src/mode/operatingMode.ts +22 -4
- package/ts/src/providers/transport.ts +14 -0
- package/ts/src/runtime/diagnose.ts +32 -7
- package/ts/src/runtime/pluginWiring.ts +6 -4
- package/ts/src/runtime/status.ts +3 -2
- package/ts/src/runtime/update.ts +17 -1
- package/ts/src/runtime/wiring.ts +14 -1
- package/ts/src/search/memorySearch.ts +38 -18
- 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
|
@@ -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
|
-
>
|
|
16
|
-
>
|
|
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
|
-
> **
|
|
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.
|
|
209
|
-
*
|
|
210
|
-
*
|
|
211
|
-
* only
|
|
212
|
-
*
|
|
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
|
|
222
|
+
return state?.mode === "Builder Mode" && state.journey && state.namedSession === true
|
|
223
|
+
? state.journey
|
|
224
|
+
: null;
|
|
222
225
|
}
|
|
223
226
|
|
|
224
227
|
/**
|
package/ts/src/builder/load.ts
CHANGED
|
@@ -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
|
-
{
|
|
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)
|
|
33
|
-
//
|
|
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
|
|
177
|
-
//
|
|
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
|
-
|
|
182
|
-
VALUES (?, ?, ?, 0, ?, ?,
|
|
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`,
|