@jossuealcala/madre 0.3.3 → 0.4.1
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/CHANGELOG.md +497 -3
- package/CONTRIBUTING.md +3 -1
- package/README.md +68 -186
- package/SECURITY.md +2 -1
- package/bin/madre.mjs +56 -13
- package/docs/INTERNALS.md +16 -0
- package/docs/REFERENCE.md +249 -0
- package/docs/SDK.md +121 -0
- package/docs/room.png +0 -0
- package/docs/sdk/hello-module.mjs +51 -0
- package/package.json +9 -1
- package/public/app.js +3979 -867
- package/public/es.js +2258 -0
- package/public/i18n.js +66 -0
- package/public/index.html +96 -15
- package/public/inquiry.js +220 -0
- package/public/resay.js +77 -0
- package/public/styles.css +622 -65
- package/public/troubleshooting.js +255 -46
- package/src/adapters/claude.mjs +2 -1
- package/src/adapters/codex.mjs +2 -1
- package/src/adapters/gemini.mjs +6 -5
- package/src/adapters/opencode.mjs +2 -1
- package/src/adapters/process.mjs +79 -20
- package/src/asking.mjs +128 -0
- package/src/auth-probe.mjs +58 -1
- package/src/chats.mjs +193 -0
- package/src/checkpoint.mjs +1 -1
- package/src/cold.mjs +56 -0
- package/src/commands.mjs +6 -0
- package/src/conversation-context.mjs +35 -3
- package/src/credentials.mjs +145 -0
- package/src/dataset.mjs +56 -4
- package/src/distiller.mjs +12 -5
- package/src/event-store.mjs +14 -8
- package/src/exam.mjs +240 -0
- package/src/extensions.mjs +3 -2
- package/src/eyecat-watch.mjs +100 -0
- package/src/eyecat.mjs +169 -0
- package/src/i18n.mjs +47 -0
- package/src/image-studio.mjs +2 -0
- package/src/launch.mjs +61 -0
- package/src/maturity.mjs +94 -0
- package/src/mcp/image-server.mjs +36 -3
- package/src/mcp/memory-server.mjs +1 -1
- package/src/memory.mjs +325 -17
- package/src/modules/ahp.mjs +9 -7
- package/src/modules/ash.mjs +36 -0
- package/src/modules/git-pulse.mjs +5 -3
- package/src/modules/helpers.mjs +31 -0
- package/src/modules/image-studio.mjs +10 -4
- package/src/modules/index.mjs +141 -9
- package/src/modules/ollama.mjs +66 -10
- package/src/modules/playwright.mjs +44 -23
- package/src/modules/ripley.mjs +5 -3
- package/src/modules/sdk.mjs +93 -2
- package/src/modules/updates.mjs +81 -0
- package/src/ollama.mjs +5 -2
- package/src/outbound.mjs +297 -0
- package/src/privacy.mjs +54 -7
- package/src/room/context.mjs +4 -4
- package/src/room/economy.mjs +161 -0
- package/src/room/prompt.mjs +118 -46
- package/src/room.mjs +443 -44
- package/src/runtime-detection.mjs +27 -8
- package/src/sentinel-errors.mjs +19 -1
- package/src/server.mjs +709 -71
- package/src/setup.mjs +1 -1
- package/src/updates.mjs +4 -2
- package/src/usage-sentinel.mjs +13 -8
- package/src/verdict.mjs +74 -0
- package/src/ashcode.mjs +0 -64
- package/src/modules/ashcode.mjs +0 -28
|
@@ -1,55 +1,84 @@
|
|
|
1
|
+
import { t } from './i18n.js';
|
|
2
|
+
|
|
1
3
|
// MU/TH/UR knowledge base: known conditions, how to recognise them in the
|
|
2
4
|
// room's own failure records, and the remedy as terminal commands per OS.
|
|
3
5
|
//
|
|
4
6
|
// Pure module (no DOM) so the catalog can be unit-tested and reused by the CLI.
|
|
5
7
|
|
|
8
|
+
// The two MADRE is tested on. Every remedy below is a command written for one of these two
|
|
9
|
+
// shells; a third set that nobody has ever run would be worse than none.
|
|
6
10
|
export const PLATFORMS = {
|
|
7
11
|
darwin: { label: 'macOS', shell: 'zsh' },
|
|
8
12
|
linux: { label: 'Linux', shell: 'bash' },
|
|
9
|
-
win32: { label: 'Windows', shell: 'PowerShell' },
|
|
10
13
|
};
|
|
11
14
|
|
|
12
15
|
const envExport = (name, value) => ({
|
|
13
16
|
darwin: [`export ${name}="${value}"`, `# persist: echo 'export ${name}="${value}"' >> ~/.zshrc`],
|
|
14
|
-
linux: [`export ${name}="${value}"`, `# persist: echo 'export ${name}="${value}"' >> ~/.bashrc`]
|
|
15
|
-
win32: [`$env:${name}="${value}"`, `# persist: setx ${name} "${value}"`],
|
|
17
|
+
linux: [`export ${name}="${value}"`, `# persist: echo 'export ${name}="${value}"' >> ~/.bashrc`]
|
|
16
18
|
});
|
|
17
19
|
|
|
18
20
|
const installAgent = {
|
|
19
21
|
codex: {
|
|
20
22
|
darwin: ['npm install -g @openai/codex', '# or install the ChatGPT desktop app, which bundles codex'],
|
|
21
|
-
linux: ['npm install -g @openai/codex']
|
|
22
|
-
win32: ['npm install -g @openai/codex'],
|
|
23
|
+
linux: ['npm install -g @openai/codex']
|
|
23
24
|
},
|
|
24
25
|
claude: {
|
|
25
26
|
darwin: ['brew install --cask claude-code', '# or: npm install -g @anthropic-ai/claude-code'],
|
|
26
|
-
linux: ['npm install -g @anthropic-ai/claude-code']
|
|
27
|
-
win32: ['npm install -g @anthropic-ai/claude-code'],
|
|
27
|
+
linux: ['npm install -g @anthropic-ai/claude-code']
|
|
28
28
|
},
|
|
29
29
|
gemini: {
|
|
30
30
|
darwin: ['npm install -g @google/gemini-cli'],
|
|
31
|
-
linux: ['npm install -g @google/gemini-cli']
|
|
32
|
-
win32: ['npm install -g @google/gemini-cli'],
|
|
31
|
+
linux: ['npm install -g @google/gemini-cli']
|
|
33
32
|
},
|
|
34
33
|
opencode: {
|
|
35
34
|
darwin: ['brew install opencode', '# or: npm install -g opencode-ai'],
|
|
36
|
-
linux: ['curl -fsSL https://opencode.ai/install | bash', '# or: npm install -g opencode-ai']
|
|
37
|
-
win32: ['npm install -g opencode-ai'],
|
|
35
|
+
linux: ['curl -fsSL https://opencode.ai/install | bash', '# or: npm install -g opencode-ai']
|
|
38
36
|
},
|
|
39
37
|
};
|
|
40
38
|
|
|
41
39
|
const loginAgent = {
|
|
42
|
-
codex: { darwin: ['codex login', 'codex login status'], linux: ['codex login', 'codex login status']
|
|
43
|
-
claude: { darwin: ['claude auth login', 'claude auth status --text'], linux: ['claude auth login', 'claude auth status --text']
|
|
44
|
-
gemini: { darwin: ['gemini', '# inside gemini: /auth → "Use Gemini API key" or Google login'], linux: ['gemini', '# inside gemini: /auth']
|
|
45
|
-
opencode: { darwin: ['opencode auth login', 'opencode auth list'], linux: ['opencode auth login', 'opencode auth list']
|
|
40
|
+
codex: { darwin: ['codex login', 'codex login status'], linux: ['codex login', 'codex login status']},
|
|
41
|
+
claude: { darwin: ['claude auth login', 'claude auth status --text'], linux: ['claude auth login', 'claude auth status --text']},
|
|
42
|
+
gemini: { darwin: ['gemini', '# inside gemini: /auth → "Use Gemini API key" or Google login'], linux: ['gemini', '# inside gemini: /auth']},
|
|
43
|
+
opencode: { darwin: ['opencode auth login', 'opencode auth list'], linux: ['opencode auth login', 'opencode auth list']},
|
|
46
44
|
};
|
|
47
45
|
|
|
48
|
-
const same = (commands) => ({ darwin: commands, linux: commands
|
|
46
|
+
const same = (commands) => ({ darwin: commands, linux: commands });
|
|
49
47
|
|
|
50
48
|
export const CONDITIONS = [
|
|
49
|
+
{
|
|
50
|
+
id: 'claude-api-key-shadows-plan',
|
|
51
|
+
code: 'MU-001',
|
|
52
|
+
agent: 'claude',
|
|
53
|
+
severity: 'common',
|
|
54
|
+
title: 'ANTHROPIC_API_KEY takes precedence over the claude.ai plan',
|
|
55
|
+
match: /connectors are disabled because ANTHROPIC_API_KEY|another auth source is set and takes precedence|takes precedence over your claude\.ai login/i,
|
|
56
|
+
diagnosis: 'Claude Code found an API key in the environment as well as the claude.ai login, and the key wins. MADRE hands the CLI the environment exactly as this machine has it, so whatever is exported in your shell profile reaches it. The turn is billed to the API account, not to the plan, and your organisation connectors do not load.',
|
|
57
|
+
remedy: 'Decide which one you mean. To work on the plan, unset the key where your shell exports it and open the room again; to work on the API, this message is only a notice and can be ignored.',
|
|
58
|
+
fixes: {
|
|
59
|
+
darwin: ['grep -rn ANTHROPIC_API_KEY ~/.zshrc ~/.zprofile ~/.zshenv 2>/dev/null', '# remove or comment the line it finds, then:', 'unset ANTHROPIC_API_KEY', '# and start MADRE from a new terminal'],
|
|
60
|
+
linux: ['grep -rn ANTHROPIC_API_KEY ~/.bashrc ~/.profile ~/.bash_profile 2>/dev/null', 'unset ANTHROPIC_API_KEY', '# and start MADRE from a new terminal'],
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
id: 'codex-reading-stdin',
|
|
65
|
+
code: 'MU-002',
|
|
66
|
+
solvedIn: 'connections',
|
|
67
|
+
agent: 'codex',
|
|
68
|
+
severity: 'informational',
|
|
69
|
+
title: 'Codex says it is reading additional input from stdin',
|
|
70
|
+
match: /Reading additional input from stdin/i,
|
|
71
|
+
diagnosis: 'A notice Codex prints, not a failure. MADRE runs it with stdin closed, so the line is harmless and the turn is decided by what Codex returned. If the turn failed, the reason is the line above this one — the exit code, or a clean exit with no answer.',
|
|
72
|
+
remedy: 'Nothing to do about the notice itself. If turns keep failing, check that this Codex is recent enough for the flags MADRE passes: `codex --version`, then update it.',
|
|
73
|
+
fixes: {
|
|
74
|
+
darwin: ['codex --version', 'npm install -g @openai/codex'],
|
|
75
|
+
linux: ['codex --version', 'npm install -g @openai/codex'],
|
|
76
|
+
},
|
|
77
|
+
},
|
|
51
78
|
{
|
|
52
79
|
id: 'gemini-ineligible-tier',
|
|
80
|
+
code: 'MU-003',
|
|
81
|
+
solvedIn: 'connections',
|
|
53
82
|
agent: 'gemini',
|
|
54
83
|
severity: 'blocking',
|
|
55
84
|
title: 'Gemini refuses the personal Google login',
|
|
@@ -58,12 +87,13 @@ export const CONDITIONS = [
|
|
|
58
87
|
remedy: 'Switch Gemini to an API key from Google AI Studio. MADRE copies only the auth selection into its isolated home, so the key can stay in the keychain or in ~/.gemini/.env.',
|
|
59
88
|
fixes: {
|
|
60
89
|
darwin: ['gemini', '# inside gemini: /auth → "Use Gemini API key" and paste the key from https://aistudio.google.com/app/apikey', '# or, without the prompt:', ...envExport('GEMINI_API_KEY', 'YOUR_KEY').darwin],
|
|
61
|
-
linux: ['gemini', '# inside gemini: /auth → "Use Gemini API key"', ...envExport('GEMINI_API_KEY', 'YOUR_KEY').linux]
|
|
62
|
-
win32: ['gemini', '# inside gemini: /auth → "Use Gemini API key"', ...envExport('GEMINI_API_KEY', 'YOUR_KEY').win32],
|
|
90
|
+
linux: ['gemini', '# inside gemini: /auth → "Use Gemini API key"', ...envExport('GEMINI_API_KEY', 'YOUR_KEY').linux]
|
|
63
91
|
},
|
|
64
92
|
},
|
|
65
93
|
{
|
|
66
94
|
id: 'gemini-credits-depleted',
|
|
95
|
+
code: 'MU-004',
|
|
96
|
+
solvedIn: 'connections',
|
|
67
97
|
agent: 'gemini',
|
|
68
98
|
severity: 'blocking',
|
|
69
99
|
title: 'Gemini key out of prepaid credits',
|
|
@@ -74,6 +104,8 @@ export const CONDITIONS = [
|
|
|
74
104
|
},
|
|
75
105
|
{
|
|
76
106
|
id: 'gemini-rate-limited',
|
|
107
|
+
code: 'MU-005',
|
|
108
|
+
solvedIn: 'connections',
|
|
77
109
|
agent: 'gemini',
|
|
78
110
|
severity: 'transient',
|
|
79
111
|
title: 'Gemini key rate-limited by Google (HTTP 429)',
|
|
@@ -82,12 +114,12 @@ export const CONDITIONS = [
|
|
|
82
114
|
remedy: 'Wait a minute and retry, pick an explicit model in the composer (gemini-3-flash-preview skips the router), or raise the key\'s quota in Google AI Studio.',
|
|
83
115
|
fixes: {
|
|
84
116
|
darwin: ['# in the room: click the Gemini sphere twice → choose gemini-3-flash-preview', '# check quota: https://aistudio.google.com/app/apikey', 'gemini --model gemini-3-flash-preview -p "ping"'],
|
|
85
|
-
linux: ['gemini --model gemini-3-flash-preview -p "ping"', '# quota: https://aistudio.google.com/app/apikey']
|
|
86
|
-
win32: ['gemini --model gemini-3-flash-preview -p "ping"', '# quota: https://aistudio.google.com/app/apikey'],
|
|
117
|
+
linux: ['gemini --model gemini-3-flash-preview -p "ping"', '# quota: https://aistudio.google.com/app/apikey']
|
|
87
118
|
},
|
|
88
119
|
},
|
|
89
120
|
{
|
|
90
121
|
id: 'gemini-high-demand',
|
|
122
|
+
code: 'MU-006',
|
|
91
123
|
agent: 'gemini',
|
|
92
124
|
severity: 'transient',
|
|
93
125
|
title: 'Gemini model under high demand (503)',
|
|
@@ -98,6 +130,8 @@ export const CONDITIONS = [
|
|
|
98
130
|
},
|
|
99
131
|
{
|
|
100
132
|
id: 'opencode-default-provider',
|
|
133
|
+
code: 'MU-007',
|
|
134
|
+
solvedIn: 'connections',
|
|
101
135
|
agent: 'opencode',
|
|
102
136
|
severity: 'blocking',
|
|
103
137
|
title: 'OpenCode picked a provider without a valid session',
|
|
@@ -106,32 +140,35 @@ export const CONDITIONS = [
|
|
|
106
140
|
remedy: 'Tell MADRE which provider/model OpenCode should use in the room, or remove the stale credential so the default changes.',
|
|
107
141
|
fixes: {
|
|
108
142
|
darwin: ['npx @jossuealcala/madre setup', '# press [m] and pick a provider/model that has a session, e.g. openai/gpt-5.6-sol', '# one-off alternative:', 'PULSE_OPENCODE_MODEL=openai/gpt-5.6-sol npx @jossuealcala/madre start', '# or drop the stale key:', 'opencode auth logout anthropic'],
|
|
109
|
-
linux: ['npx @jossuealcala/madre setup', '# press [m] and pick a provider/model that has a session', 'PULSE_OPENCODE_MODEL=openai/gpt-5.6-sol npx @jossuealcala/madre start', 'opencode auth logout anthropic']
|
|
110
|
-
win32: ['npx @jossuealcala/madre setup', '# press [m] and pick a provider/model that has a session', '$env:PULSE_OPENCODE_MODEL="openai/gpt-5.6-sol"; npx @jossuealcala/madre start', 'opencode auth logout anthropic'],
|
|
143
|
+
linux: ['npx @jossuealcala/madre setup', '# press [m] and pick a provider/model that has a session', 'PULSE_OPENCODE_MODEL=openai/gpt-5.6-sol npx @jossuealcala/madre start', 'opencode auth logout anthropic']
|
|
111
144
|
},
|
|
112
145
|
},
|
|
113
146
|
{
|
|
114
147
|
id: 'not-signed-in',
|
|
148
|
+
code: 'MU-008',
|
|
149
|
+
solvedIn: 'connections',
|
|
115
150
|
severity: 'blocking',
|
|
116
151
|
title: 'Agent installed but signed out',
|
|
117
152
|
match: /not logged in|sign in required|Error authenticating|unauthorized|please (?:log|sign) ?in|no credentials/i,
|
|
118
153
|
diagnosis: 'The CLI is on this computer but has no session for its provider. MADRE never stores credentials; each agent keeps its own.',
|
|
119
|
-
remedy: '
|
|
154
|
+
remedy: 'Press SIGN IN on the agent\'s card: on first contact the room opens on the bridge, and later the same button lives in ⚙ CONNECTIONS. Codex and Claude sign in with a browser flow MADRE runs for you; Gemini and OpenCode hand you their command. `madre setup` does the same from a terminal.',
|
|
120
155
|
fixes: same(['npx @jossuealcala/madre setup', '# or directly:', 'codex login', 'claude auth login', 'opencode auth login', 'gemini # then /auth']),
|
|
121
156
|
perAgent: loginAgent,
|
|
122
157
|
},
|
|
123
158
|
{
|
|
124
159
|
id: 'not-installed',
|
|
160
|
+
code: 'MU-009',
|
|
125
161
|
severity: 'blocking',
|
|
126
162
|
title: 'Agent not found on this computer',
|
|
127
163
|
match: /is not installed on this computer|not found on this computer|ENOENT.*(codex|claude|gemini|opencode)/i,
|
|
128
164
|
diagnosis: 'MADRE looks for `codex`, `claude`, `gemini` and `opencode` on PATH plus a few known locations. Nothing answered.',
|
|
129
|
-
remedy: '
|
|
165
|
+
remedy: 'Press INSTALL on the agent\'s card: MADRE shows the exact command, runs it here and looks again when it finishes, no restart. `madre setup` does the same from a terminal.',
|
|
130
166
|
fixes: same(['# pick the agent you want; see per-agent commands below']),
|
|
131
167
|
perAgent: installAgent,
|
|
132
168
|
},
|
|
133
169
|
{
|
|
134
170
|
id: 'adapter-pending',
|
|
171
|
+
code: 'MU-010',
|
|
135
172
|
severity: 'blocking',
|
|
136
173
|
title: 'Agent detected, adapter not enabled',
|
|
137
174
|
match: /adapter is not enabled yet|does not have a supported MADRE adapter/i,
|
|
@@ -141,6 +178,7 @@ export const CONDITIONS = [
|
|
|
141
178
|
},
|
|
142
179
|
{
|
|
143
180
|
id: 'claude-args',
|
|
181
|
+
code: 'MU-011',
|
|
144
182
|
agent: 'claude',
|
|
145
183
|
severity: 'fixed',
|
|
146
184
|
title: 'Claude read the prompt as an MCP config path',
|
|
@@ -151,6 +189,7 @@ export const CONDITIONS = [
|
|
|
151
189
|
},
|
|
152
190
|
{
|
|
153
191
|
id: 'timeout',
|
|
192
|
+
code: 'MU-012',
|
|
154
193
|
severity: 'tunable',
|
|
155
194
|
title: 'Agent did not respond before the timeout',
|
|
156
195
|
match: /did not respond before the timeout|went silent for/i,
|
|
@@ -158,12 +197,12 @@ export const CONDITIONS = [
|
|
|
158
197
|
remedy: 'Press the RAISE button on this card, or type a new number in ⚙ CONNECTIONS (DEFAULT TIMEOUT · SECONDS, or the field on the agent\'s card): fields save the moment you leave them. It applies to the next turn and persists in ~/.pulse/config.json. Environment variables work too, but only for a server started after exporting them; a running room never sees a later export.',
|
|
159
198
|
fixes: {
|
|
160
199
|
darwin: ['# live, no restart: ⚙ CONNECTIONS → DEFAULT TIMEOUT · SECONDS → SAVE', '# at launch only (env wins over config.json):', 'PULSE_AGENT_TIMEOUT_MS=300000 PULSE_CLAUDE_TIMEOUT_MS=600000 madre start', '# or ~/.pulse/config.json → {"timeouts":{"default":300000,"claude":600000}}'],
|
|
161
|
-
linux: ['# live, no restart: ⚙ CONNECTIONS → DEFAULT TIMEOUT · SECONDS → SAVE', 'PULSE_AGENT_TIMEOUT_MS=300000 PULSE_CLAUDE_TIMEOUT_MS=600000 madre start', '# or ~/.pulse/config.json → {"timeouts":{"default":300000,"claude":600000}}']
|
|
162
|
-
win32: ['# live, no restart: ⚙ CONNECTIONS → DEFAULT TIMEOUT · SECONDS → SAVE', '$env:PULSE_AGENT_TIMEOUT_MS="300000"; $env:PULSE_CLAUDE_TIMEOUT_MS="600000"; madre start', '# or %USERPROFILE%\\.pulse\\config.json → {"timeouts":{"default":300000,"claude":600000}}'],
|
|
200
|
+
linux: ['# live, no restart: ⚙ CONNECTIONS → DEFAULT TIMEOUT · SECONDS → SAVE', 'PULSE_AGENT_TIMEOUT_MS=300000 PULSE_CLAUDE_TIMEOUT_MS=600000 madre start', '# or ~/.pulse/config.json → {"timeouts":{"default":300000,"claude":600000}}']
|
|
163
201
|
},
|
|
164
202
|
},
|
|
165
203
|
{
|
|
166
204
|
id: 'interrupted',
|
|
205
|
+
code: 'MU-013',
|
|
167
206
|
severity: 'informational',
|
|
168
207
|
title: 'Turn interrupted by a MADRE restart',
|
|
169
208
|
match: /interrupted because MADRE is shutting down|MADRE stopped while|turn was not completed/i,
|
|
@@ -173,6 +212,7 @@ export const CONDITIONS = [
|
|
|
173
212
|
},
|
|
174
213
|
{
|
|
175
214
|
id: 'message-too-long',
|
|
215
|
+
code: 'MU-014',
|
|
176
216
|
severity: 'tunable',
|
|
177
217
|
title: 'Message rejected for length',
|
|
178
218
|
match: /Message is too long/i,
|
|
@@ -180,12 +220,12 @@ export const CONDITIONS = [
|
|
|
180
220
|
remedy: 'Split the message, or raise the cap if your CLIs cope.',
|
|
181
221
|
fixes: {
|
|
182
222
|
darwin: ['export PULSE_MAX_MESSAGE_CHARS="40000"'],
|
|
183
|
-
linux: ['export PULSE_MAX_MESSAGE_CHARS="40000"']
|
|
184
|
-
win32: ['$env:PULSE_MAX_MESSAGE_CHARS="40000"'],
|
|
223
|
+
linux: ['export PULSE_MAX_MESSAGE_CHARS="40000"']
|
|
185
224
|
},
|
|
186
225
|
},
|
|
187
226
|
{
|
|
188
227
|
id: 'limits-real',
|
|
228
|
+
code: 'MU-015',
|
|
189
229
|
severity: 'informational',
|
|
190
230
|
title: 'Where the rings get their numbers',
|
|
191
231
|
match: /ring|quota|limit window|resets|rollout|oauth usage|window reset|limits-real/i,
|
|
@@ -195,6 +235,7 @@ export const CONDITIONS = [
|
|
|
195
235
|
},
|
|
196
236
|
{
|
|
197
237
|
id: 'budget-exhausted',
|
|
238
|
+
code: 'MU-016',
|
|
198
239
|
severity: 'tunable',
|
|
199
240
|
title: 'Local token budget exhausted for an agent',
|
|
200
241
|
match: /MADRE exhausted|local room token budget/i,
|
|
@@ -202,12 +243,12 @@ export const CONDITIONS = [
|
|
|
202
243
|
remedy: 'Continue with another agent, press RAISE LOCAL BUDGET on this card, or type a number in ⚙ CONNECTIONS → LOCAL TOKEN BUDGET PER AGENT (saves on leaving the field). The provider\'s real limits show in each sphere\'s popover when the CLI reports them.',
|
|
203
244
|
fixes: {
|
|
204
245
|
darwin: ['# live: ⚙ CONNECTIONS → LOCAL TOKEN BUDGET PER AGENT → SAVE', '# at launch: PULSE_SOFT_TOKEN_BUDGET=1000000 madre start', '# or ~/.pulse/config.json → {"room":{"softTokenBudget":1000000}}'],
|
|
205
|
-
linux: ['# live: ⚙ CONNECTIONS → LOCAL TOKEN BUDGET PER AGENT → SAVE', 'PULSE_SOFT_TOKEN_BUDGET=1000000 madre start']
|
|
206
|
-
win32: ['# live: ⚙ CONNECTIONS → LOCAL TOKEN BUDGET PER AGENT → SAVE', '$env:PULSE_SOFT_TOKEN_BUDGET="1000000"; madre start'],
|
|
246
|
+
linux: ['# live: ⚙ CONNECTIONS → LOCAL TOKEN BUDGET PER AGENT → SAVE', 'PULSE_SOFT_TOKEN_BUDGET=1000000 madre start']
|
|
207
247
|
},
|
|
208
248
|
},
|
|
209
249
|
{
|
|
210
250
|
id: 'port-in-use',
|
|
251
|
+
code: 'MU-017',
|
|
211
252
|
severity: 'blocking',
|
|
212
253
|
title: 'Port already in use',
|
|
213
254
|
match: /EADDRINUSE|already in use/i,
|
|
@@ -215,12 +256,12 @@ export const CONDITIONS = [
|
|
|
215
256
|
remedy: 'Use a different port, or find and stop the process holding it.',
|
|
216
257
|
fixes: {
|
|
217
258
|
darwin: ['npx @jossuealcala/madre start --port 4318', '# who holds 4317?', 'lsof -nP -iTCP:4317 -sTCP:LISTEN'],
|
|
218
|
-
linux: ['npx @jossuealcala/madre start --port 4318', 'ss -ltnp | grep 4317']
|
|
219
|
-
win32: ['npx @jossuealcala/madre start --port 4318', 'netstat -ano | findstr :4317', '# then: taskkill /PID <pid> /F'],
|
|
259
|
+
linux: ['npx @jossuealcala/madre start --port 4318', 'ss -ltnp | grep 4317']
|
|
220
260
|
},
|
|
221
261
|
},
|
|
222
262
|
{
|
|
223
263
|
id: 'stream-reconnecting',
|
|
264
|
+
code: 'MU-018',
|
|
224
265
|
severity: 'blocking',
|
|
225
266
|
title: 'Live stream keeps reconnecting',
|
|
226
267
|
match: /reconnecting|EventSource|ECONNREFUSED/i,
|
|
@@ -230,6 +271,7 @@ export const CONDITIONS = [
|
|
|
230
271
|
},
|
|
231
272
|
{
|
|
232
273
|
id: 'opencode-server-error',
|
|
274
|
+
code: 'MU-019',
|
|
233
275
|
agent: 'opencode',
|
|
234
276
|
severity: 'transient',
|
|
235
277
|
title: 'OpenCode reported an unexpected server error',
|
|
@@ -240,6 +282,7 @@ export const CONDITIONS = [
|
|
|
240
282
|
},
|
|
241
283
|
{
|
|
242
284
|
id: 'runaway-room',
|
|
285
|
+
code: 'MU-020',
|
|
243
286
|
severity: 'blocking',
|
|
244
287
|
title: 'Too many agents working at once',
|
|
245
288
|
match: /agents are working at once|already answering another turn|no second plan will start|has been running for/i,
|
|
@@ -250,6 +293,7 @@ export const CONDITIONS = [
|
|
|
250
293
|
// ---- capability assistance: how an agent could gain a scope it lacks ----
|
|
251
294
|
{
|
|
252
295
|
id: 'scope-gemini-imageGen',
|
|
296
|
+
code: 'MU-051',
|
|
253
297
|
agent: 'gemini',
|
|
254
298
|
severity: 'informational',
|
|
255
299
|
title: 'Gemini: generating images',
|
|
@@ -260,6 +304,7 @@ export const CONDITIONS = [
|
|
|
260
304
|
},
|
|
261
305
|
{
|
|
262
306
|
id: 'scope-claude-imageGen',
|
|
307
|
+
code: 'MU-052',
|
|
263
308
|
agent: 'claude',
|
|
264
309
|
severity: 'informational',
|
|
265
310
|
title: 'Claude Code: generating images',
|
|
@@ -270,6 +315,7 @@ export const CONDITIONS = [
|
|
|
270
315
|
},
|
|
271
316
|
{
|
|
272
317
|
id: 'scope-opencode-imageGen',
|
|
318
|
+
code: 'MU-053',
|
|
273
319
|
agent: 'opencode',
|
|
274
320
|
severity: 'informational',
|
|
275
321
|
title: 'OpenCode: generating images',
|
|
@@ -280,6 +326,7 @@ export const CONDITIONS = [
|
|
|
280
326
|
},
|
|
281
327
|
{
|
|
282
328
|
id: 'scope-web',
|
|
329
|
+
code: 'MU-021',
|
|
283
330
|
severity: 'informational',
|
|
284
331
|
title: 'Web access for an agent',
|
|
285
332
|
match: /scope-[a-z]+-web/,
|
|
@@ -289,6 +336,7 @@ export const CONDITIONS = [
|
|
|
289
336
|
},
|
|
290
337
|
{
|
|
291
338
|
id: 'slash-commands',
|
|
339
|
+
code: 'MU-022',
|
|
292
340
|
severity: 'informational',
|
|
293
341
|
title: 'Commands and mentions in the field box',
|
|
294
342
|
match: /slash|command|\/git|\/ahp|mention|@agent/i,
|
|
@@ -298,6 +346,7 @@ export const CONDITIONS = [
|
|
|
298
346
|
},
|
|
299
347
|
{
|
|
300
348
|
id: 'modes',
|
|
349
|
+
code: 'MU-023',
|
|
301
350
|
severity: 'informational',
|
|
302
351
|
title: 'Permission modes: #0 GHOST · #1 EXCHANGE · #2 CREATE · #3 CONTROL · #4 AIRLOCK',
|
|
303
352
|
match: /mode|ghost|exchange|control|airlock|deploy|despleg|producci|override|designation|max mode|#[0-4]\b/i,
|
|
@@ -307,6 +356,7 @@ export const CONDITIONS = [
|
|
|
307
356
|
},
|
|
308
357
|
{
|
|
309
358
|
id: 'lease-missing',
|
|
359
|
+
code: 'MU-024',
|
|
310
360
|
severity: 'informational',
|
|
311
361
|
title: 'Creating files: who grants the permission',
|
|
312
362
|
match: /no CREATE lease|lease-missing|read-only and do not modify|permiso de escritura|solo lectura/i,
|
|
@@ -316,6 +366,7 @@ export const CONDITIONS = [
|
|
|
316
366
|
},
|
|
317
367
|
{
|
|
318
368
|
id: 'scope-write',
|
|
369
|
+
code: 'MU-025',
|
|
319
370
|
severity: 'informational',
|
|
320
371
|
title: 'Creating files with an agent',
|
|
321
372
|
match: /scope-[a-z]+-write/,
|
|
@@ -325,6 +376,7 @@ export const CONDITIONS = [
|
|
|
325
376
|
},
|
|
326
377
|
{
|
|
327
378
|
id: 'node-version',
|
|
379
|
+
code: 'MU-026',
|
|
328
380
|
severity: 'blocking',
|
|
329
381
|
title: 'Node.js too old',
|
|
330
382
|
match: /SyntaxError: Unexpected token|ERR_REQUIRE_ESM|engines|Unsupported engine/i,
|
|
@@ -332,12 +384,12 @@ export const CONDITIONS = [
|
|
|
332
384
|
remedy: 'Update Node to the current LTS.',
|
|
333
385
|
fixes: {
|
|
334
386
|
darwin: ['node --version', 'brew install node', '# or: nvm install --lts'],
|
|
335
|
-
linux: ['node --version', 'nvm install --lts', '# or your distro package for Node ≥ 22.5']
|
|
336
|
-
win32: ['node --version', 'winget install OpenJS.NodeJS.LTS'],
|
|
387
|
+
linux: ['node --version', 'nvm install --lts', '# or your distro package for Node ≥ 22.5']
|
|
337
388
|
},
|
|
338
389
|
},
|
|
339
390
|
{
|
|
340
391
|
id: 'memory-unavailable',
|
|
392
|
+
code: 'MU-027',
|
|
341
393
|
severity: 'degraded',
|
|
342
394
|
title: 'Room memory unavailable: turns get only the recent window',
|
|
343
395
|
match: /memory unavailable|memory index failed|memory recall failed|node:sqlite|SQLITE_|database disk image is malformed|memory\.sqlite/i,
|
|
@@ -345,12 +397,12 @@ export const CONDITIONS = [
|
|
|
345
397
|
remedy: 'The index is derived from the ledger: delete memory.sqlite (and its -wal/-shm siblings) and restart; MADRE rebuilds it. Distilled notes live in the same file, so export them from NOSTROMO first if they matter.',
|
|
346
398
|
fixes: {
|
|
347
399
|
darwin: ['node --version # must be ≥ 22.5', 'ls ~/.pulse/rooms/*/memory.sqlite*', 'rm ~/.pulse/rooms/<room>/memory.sqlite* # rebuilt on next start'],
|
|
348
|
-
linux: ['node --version # must be ≥ 22.5', 'ls ~/.pulse/rooms/*/memory.sqlite*', 'rm ~/.pulse/rooms/<room>/memory.sqlite* # rebuilt on next start']
|
|
349
|
-
win32: ['node --version # must be ≥ 22.5', 'dir $env:USERPROFILE\\.pulse\\rooms', 'Remove-Item $env:USERPROFILE\\.pulse\\rooms\\<room>\\memory.sqlite*'],
|
|
400
|
+
linux: ['node --version # must be ≥ 22.5', 'ls ~/.pulse/rooms/*/memory.sqlite*', 'rm ~/.pulse/rooms/<room>/memory.sqlite* # rebuilt on next start']
|
|
350
401
|
},
|
|
351
402
|
},
|
|
352
403
|
{
|
|
353
404
|
id: 'memory-recall',
|
|
405
|
+
code: 'MU-028',
|
|
354
406
|
severity: 'informational',
|
|
355
407
|
title: 'Room memory: what an agent remembers and how',
|
|
356
408
|
match: /\brecall\b|recuerd|\bremember\b|memoria de la sala|<memory>|<memories>/i,
|
|
@@ -360,6 +412,7 @@ export const CONDITIONS = [
|
|
|
360
412
|
},
|
|
361
413
|
{
|
|
362
414
|
id: 'memory-distill',
|
|
415
|
+
code: 'MU-029',
|
|
363
416
|
severity: 'degraded',
|
|
364
417
|
title: 'Distilled memories: the archivist did not run, or failed',
|
|
365
418
|
match: /could not distil|distill|destil|batch skipped|archivist|memory\.distilled|memories? (were|was) not/i,
|
|
@@ -369,6 +422,8 @@ export const CONDITIONS = [
|
|
|
369
422
|
},
|
|
370
423
|
{
|
|
371
424
|
id: 'memory-embeddings',
|
|
425
|
+
code: 'MU-030',
|
|
426
|
+
solvedIn: 'connections',
|
|
372
427
|
severity: 'degraded',
|
|
373
428
|
title: 'Embeddings paused: recall is lexical only',
|
|
374
429
|
match: /embeddings paused|embedding timed out|Gemini embeddings HTTP|batchEmbedContents|LINKS NEED EMBEDDINGS/i,
|
|
@@ -376,12 +431,13 @@ export const CONDITIONS = [
|
|
|
376
431
|
remedy: 'Sign the Gemini CLI in with an API key (/auth) or export GEMINI_API_KEY, then restart. If you do not want embeddings at all, set PULSE_EMBED=0 and the pause message stops.',
|
|
377
432
|
fixes: {
|
|
378
433
|
darwin: ['gemini # /auth → "Use Gemini API key"', ...envExport('GEMINI_API_KEY', 'YOUR_KEY').darwin, 'PULSE_EMBED=0 madre start # lexical only, no messages'],
|
|
379
|
-
linux: ['gemini # /auth → "Use Gemini API key"', ...envExport('GEMINI_API_KEY', 'YOUR_KEY').linux, 'PULSE_EMBED=0 madre start']
|
|
380
|
-
win32: ['gemini # /auth → "Use Gemini API key"', ...envExport('GEMINI_API_KEY', 'YOUR_KEY').win32, '$env:PULSE_EMBED="0"; madre start'],
|
|
434
|
+
linux: ['gemini # /auth → "Use Gemini API key"', ...envExport('GEMINI_API_KEY', 'YOUR_KEY').linux, 'PULSE_EMBED=0 madre start']
|
|
381
435
|
},
|
|
382
436
|
},
|
|
383
437
|
{
|
|
384
438
|
id: 'memory-tools',
|
|
439
|
+
code: 'MU-031',
|
|
440
|
+
solvedIn: 'connections',
|
|
385
441
|
severity: 'degraded',
|
|
386
442
|
title: 'An agent says it cannot search the memory (pulse-memory MCP)',
|
|
387
443
|
match: /pulse-memory|memory_search|memory_recall|memory_notes|memory_timeline|memory_note|mcp.*(failed|error|not found|unavailable)|MCP server/i,
|
|
@@ -389,12 +445,12 @@ export const CONDITIONS = [
|
|
|
389
445
|
remedy: 'Update the CLI, then check that it sees the server. Codex can list servers from a config override; Claude accepts an inline --mcp-config. If a CLI keeps failing, PULSE_MEMORY_TOOLS=0 removes the tools for everyone and the room continues on automatic recall.',
|
|
390
446
|
fixes: {
|
|
391
447
|
darwin: ['codex mcp list', 'claude --version', 'gemini --version', 'PULSE_MEMORY_TOOLS=0 madre start # tools off, recall stays'],
|
|
392
|
-
linux: ['codex mcp list', 'claude --version', 'gemini --version', 'PULSE_MEMORY_TOOLS=0 madre start']
|
|
393
|
-
win32: ['codex mcp list', 'claude --version', 'gemini --version', '$env:PULSE_MEMORY_TOOLS="0"; madre start'],
|
|
448
|
+
linux: ['codex mcp list', 'claude --version', 'gemini --version', 'PULSE_MEMORY_TOOLS=0 madre start']
|
|
394
449
|
},
|
|
395
450
|
},
|
|
396
451
|
{
|
|
397
452
|
id: 'memory-note',
|
|
453
|
+
code: 'MU-032',
|
|
398
454
|
severity: 'informational',
|
|
399
455
|
title: 'Saving a memory on request; GHOST refuses',
|
|
400
456
|
match: /save (a |the |this )?memory|guarda.*memoria|memory saved|off the record.*nothing can be saved|remember this|memory_note/i,
|
|
@@ -404,6 +460,7 @@ export const CONDITIONS = [
|
|
|
404
460
|
},
|
|
405
461
|
{
|
|
406
462
|
id: 'nostromo-access',
|
|
463
|
+
code: 'MU-033',
|
|
407
464
|
severity: 'informational',
|
|
408
465
|
title: 'NOSTROMO: boarding, reading and forgetting',
|
|
409
466
|
match: /nostromo|designation|UNABLE TO COMPUTE|ARCHIVE IS SEALED|forget this memory/i,
|
|
@@ -413,6 +470,7 @@ export const CONDITIONS = [
|
|
|
413
470
|
},
|
|
414
471
|
{
|
|
415
472
|
id: 'ripley',
|
|
473
|
+
code: 'MU-034',
|
|
416
474
|
severity: 'informational',
|
|
417
475
|
title: 'RIPLEY: rendering HTML, SVG and Markdown in the viewer',
|
|
418
476
|
match: /ripley|RIPLEY is off|renders \.html|render(ed|ing)? (the )?(html|svg|markdown)/i,
|
|
@@ -422,6 +480,7 @@ export const CONDITIONS = [
|
|
|
422
480
|
},
|
|
423
481
|
{
|
|
424
482
|
id: 'ollama',
|
|
483
|
+
code: 'MU-035',
|
|
425
484
|
severity: 'informational',
|
|
426
485
|
title: 'Ollama: local embeddings and a local archivist',
|
|
427
486
|
match: /ollama|11434|local model|nomic-embed|qwen|llama3/i,
|
|
@@ -429,12 +488,12 @@ export const CONDITIONS = [
|
|
|
429
488
|
remedy: 'Install Ollama (ollama.com), start it, then in MODULES press RECHECK and PULL the models it suggests. Set PULSE_OLLAMA_MODEL or PULSE_OLLAMA_EMBED_MODEL to prefer others; PULSE_EMBED_PROVIDER=gemini keeps embeddings on Gemini even with Ollama running.',
|
|
430
489
|
fixes: {
|
|
431
490
|
darwin: ['brew install ollama', 'ollama serve', 'ollama pull nomic-embed-text', 'ollama pull qwen2.5:3b'],
|
|
432
|
-
linux: ['curl -fsSL https://ollama.com/install.sh | sh', 'ollama serve', 'ollama pull nomic-embed-text', 'ollama pull qwen2.5:3b']
|
|
433
|
-
win32: ['winget install Ollama.Ollama', 'ollama serve', 'ollama pull nomic-embed-text', 'ollama pull qwen2.5:3b'],
|
|
491
|
+
linux: ['curl -fsSL https://ollama.com/install.sh | sh', 'ollama serve', 'ollama pull nomic-embed-text', 'ollama pull qwen2.5:3b']
|
|
434
492
|
},
|
|
435
493
|
},
|
|
436
494
|
{
|
|
437
495
|
id: 'madre-agent',
|
|
496
|
+
code: 'MU-036',
|
|
438
497
|
severity: 'informational',
|
|
439
498
|
title: '@madre: the room\'s own memory, as an agent',
|
|
440
499
|
match: /@madre|madre-local|needs Ollama running with a chat model/i,
|
|
@@ -442,8 +501,22 @@ export const CONDITIONS = [
|
|
|
442
501
|
remedy: 'Start Ollama and PULL a chat model in MODULES → OLLAMA; the switch @MADRE IN THE ROOM turns the agent off if you do not want it. Ask it "what did we decide about …" or "did we ever discuss …".',
|
|
443
502
|
fixes: same(['ollama serve', 'ollama pull qwen2.5:3b', '@madre what did we decide about the webhook?']),
|
|
444
503
|
},
|
|
504
|
+
{
|
|
505
|
+
id: 'install-needs-admin',
|
|
506
|
+
code: 'MU-037',
|
|
507
|
+
severity: 'informational',
|
|
508
|
+
title: 'Installing a CLI: npm cannot write to the system folder',
|
|
509
|
+
match: /EACCES|EPERM|permission denied|Missing write access|npm ERR!.*sudo|administrator/i,
|
|
510
|
+
diagnosis: 'A global npm install writes into a folder that belongs to the system. With Node installed from its own installer that folder needs an administrator, so npm stops with a permission error. MADRE does not ask for your password: when it sees that wall it installs the CLI into a folder of its own, ~/.pulse/tools, and looks there as well as along PATH. The agent works the same; only the file lives somewhere else.',
|
|
511
|
+
remedy: 'Nothing to do: press INSTALL again and MADRE takes the second way by itself. If you would rather have the CLI everywhere in your terminal, install it yourself with your package manager, or give npm a folder of your own.',
|
|
512
|
+
fixes: {
|
|
513
|
+
darwin: ['# MADRE does this for you; these are the alternatives:', 'brew install --cask claude-code', 'npm config set prefix ~/.npm-global # then add ~/.npm-global/bin to your PATH'],
|
|
514
|
+
linux: ['# MADRE does this for you; these are the alternatives:', 'npm config set prefix ~/.npm-global # then add ~/.npm-global/bin to your PATH']
|
|
515
|
+
},
|
|
516
|
+
},
|
|
445
517
|
{
|
|
446
518
|
id: 'privacy-leak',
|
|
519
|
+
code: 'MU-038',
|
|
447
520
|
severity: 'warning',
|
|
448
521
|
title: 'Privacy: an agent brought its own configuration into the room',
|
|
449
522
|
match: /privacy\.redacted|privacy\.purged|private terms?|\[ENTIDAD-ORG\]|organi[sz]ation(al)? instructions|leak(ed)? (a|the) (name|company|domain)/i,
|
|
@@ -453,6 +526,7 @@ export const CONDITIONS = [
|
|
|
453
526
|
},
|
|
454
527
|
{
|
|
455
528
|
id: 'control-changes',
|
|
529
|
+
code: 'MU-039',
|
|
456
530
|
severity: 'informational',
|
|
457
531
|
title: 'CONTROL: what changed, what was reverted, UNDO',
|
|
458
532
|
match: /control\.changed|forbidden zones?|reverted|UNDO|checkpoint|changed \d+ file/i,
|
|
@@ -460,12 +534,135 @@ export const CONDITIONS = [
|
|
|
460
534
|
remedy: 'Read the list under the reply before moving on. If the change is wrong press UNDO; if the agent should not have had it, lower its MAX MODE in ⚙ CONNECTIONS.',
|
|
461
535
|
fixes: same(['git log --oneline -5 # checkpoints are ordinary commits on a side ref', 'git stash list', '# room → UNDO under the CONTROL notice']),
|
|
462
536
|
},
|
|
537
|
+
{
|
|
538
|
+
id: 'conversations',
|
|
539
|
+
code: 'MU-040',
|
|
540
|
+
severity: 'informational',
|
|
541
|
+
title: 'Conversations: many per project, one memory, one at a time',
|
|
542
|
+
match: /conversation|already open for this project|ROOM_IN_USE|a turn is running in this conversation|new conversation|chats\.json/i,
|
|
543
|
+
diagnosis: 'A project has one memory and many conversations. The archive, the crew, the modules and the privacy list belong to the project and do not start over when you open another thread; a conversation is only the record of one line of work, and everything said in any of them feeds the same archive. The first conversation is the ledger that was always there, so a room that existed before opens exactly as it did. The numbering is the project\'s, not the thread\'s: a new conversation starts where the project got to, which is why a citation like #1411 means the same exchange in all of them. One conversation drives the crew at a time — two threads editing the same working tree is a way to lose work — so opening another while a turn runs is refused until it finishes. And one server per project: a second `madre start` on the same project is refused with the address of the room that is already open, because two servers would hand out the same sequence twice and the archive would quietly keep one of them.',
|
|
544
|
+
remedy: 'The panel on the left of the canvas lists them: open one, or start another at the bottom. Deleting one takes its transcript and nothing else — what the archivist distilled from it is the project\'s memory and stays. If MADRE says the project is already open, use that room and open a conversation inside it instead of starting a second server.',
|
|
545
|
+
fixes: same(['# room → the handle under the bar, on the left → NEW CONVERSATION', '# the address of the open room is in the refusal itself', 'ls ~/.pulse/rooms/*/chats.json # their names and which one is open']),
|
|
546
|
+
},
|
|
547
|
+
{
|
|
548
|
+
id: 'memory-aberrations',
|
|
549
|
+
code: 'MU-041',
|
|
550
|
+
severity: 'informational',
|
|
551
|
+
title: 'Aberrations: what the room established is false',
|
|
552
|
+
match: /aberration|aberraci|eyecat|contradict|refut|false claim|hallucinat|alucina/i,
|
|
553
|
+
diagnosis: 'An aberration is a claim the room established is false, kept on purpose rather than deleted: it is what the local model learns not to repeat. It never travels into a turn, and the note it refutes is taken out of circulation with it — a refuted memory is never recalled again, by words or by meaning. EYECAT watches for two agents saying opposite things about the same subject and raises the pair for a third agent to judge, one that took no part in either side. Confirming files the aberration and quarantines what it refutes; dismissing says the room was right and the pair is never raised again. Forgetting an aberration gives its note back.',
|
|
554
|
+
remedy: 'NOSTROMO shows them as collapsed bodies: open one to read what it took down, or clear it if the room was wrong about being wrong. The three tests report open contradictions under CONSISTENCY.',
|
|
555
|
+
fixes: same(['# room → ◉ NOSTROMO → the dark bodies are aberrations', '# ⚙ CONNECTIONS → MEMORY → THE THREE TESTS → CONSISTENCY']),
|
|
556
|
+
},
|
|
557
|
+
{
|
|
558
|
+
id: 'memory-cascade',
|
|
559
|
+
code: 'MU-042',
|
|
560
|
+
severity: 'informational',
|
|
561
|
+
title: 'Memory used · N by association',
|
|
562
|
+
match: /by association|cascade|spreading activation|keeps arriving with|memory used/i,
|
|
563
|
+
diagnosis: 'Under a reply, MADRE says what the archive handed that turn. Most of it was matched to what you asked, by words and by meaning. A note marked "by association" came along for a different reason: this room keeps carrying it in the same turn as one of the others, so the archive brings it too, however differently it reads. The strength is a ratio over the turns where each was found on its own merits — a memory the room reaches for constantly does not end up attached to everything, and a pair that stops meeting fades on its own. Association adds and never displaces: recall keeps two slots for it and hands back the ones it does not use.',
|
|
564
|
+
remedy: 'Click a note to see it in NOSTROMO, where the wires it travels on are drawn. It is the one part of recall that owes nothing to how a memory reads, so it can be switched off in MEMORY.',
|
|
565
|
+
fixes: same(['# ⚙ CONNECTIONS → MEMORY → CARRY WHAT A MEMORY KEEPS ARRIVING WITH', 'PULSE_RECALL_CASCADE=0 npx @jossuealcala/madre start']),
|
|
566
|
+
},
|
|
567
|
+
{
|
|
568
|
+
id: 'memory-cold',
|
|
569
|
+
code: 'MU-043',
|
|
570
|
+
severity: 'informational',
|
|
571
|
+
title: 'Cold memories: what the archive has had its chances with',
|
|
572
|
+
match: /cold memor|cold zone|never once carried|had its chances/i,
|
|
573
|
+
diagnosis: 'A memory is cold when three things are true at once: no turn has ever carried it, it shares a subject with nothing so nothing can reach it sideways either, and the archive has been opened at least a dozen times since it was written. The third is what makes the label worth acting on — never-recalled is what every memory is on the day it is written. Chances are counted in turns that actually reached into the archive, and only from the day the room started keeping that trail, so what was never written down is not held against the note.',
|
|
574
|
+
remedy: 'NOSTROMO shows COLD · N in its header when there are any; pressing it rings each one on the map. Open one and its card says the number plainly, with FORGET one press away. Asking about it is the other way out: the room writes that question for you under ASK.',
|
|
575
|
+
fixes: same(['# room → ◉ NOSTROMO → COLD', '# room → ◉ NOSTROMO → ASK → PUT IN THE COMPOSER']),
|
|
576
|
+
},
|
|
577
|
+
{
|
|
578
|
+
id: 'maturity-tests',
|
|
579
|
+
code: 'MU-044',
|
|
580
|
+
severity: 'informational',
|
|
581
|
+
title: 'The three tests: whether the archive works',
|
|
582
|
+
match: /three tests|coverage|consistency|held-out|match test|maturity|madurez|ready to be worked in/i,
|
|
583
|
+
diagnosis: 'The six readings count what the archive is made of; they cannot say whether it works, because nothing about a pile of notes says whether the right one comes back when it is needed. COVERAGE takes real questions from this room, runs recall at the exact point each was asked, and scores what came back against the reply that was actually given. CONSISTENCY reads the contradictions EYECAT still holds and whether aberrations are being filed more often lately. MATCH puts real questions a frontier CLI answered back to the local model with this archive behind it. All three measure against a control, because everything in one room is about the same handful of subjects and any two pieces of it read as close to an embedder: to count, what the archive handed over has to beat what it would have handed over for a different question.',
|
|
584
|
+
remedy: 'Read the one sentence and the one instruction at the top of MEMORY; the readings and the tests are a fold below it. The free test is read as the panel opens, the cheap one keeps itself fresh once a day while the embeddings are local, and the slow one is yours to start and to stop. None of them spends a provider turn.',
|
|
585
|
+
fixes: same(['# ⚙ CONNECTIONS → MEMORY → THE THREE TESTS', '# MATCH needs Ollama running with a chat model, and embeddings on']),
|
|
586
|
+
},
|
|
587
|
+
{
|
|
588
|
+
id: 'module-update',
|
|
589
|
+
code: 'MU-045',
|
|
590
|
+
severity: 'common',
|
|
591
|
+
title: 'Updating what a module drives',
|
|
592
|
+
match: /EACCES|permission denied.*npm|npm ERR!.*EACCES|update to \d|newest is|brew upgrade|install -g @playwright/i,
|
|
593
|
+
diagnosis: 'When something newer exists and the module knows how to fetch it, the button is beside the version on its card. It asks twice on purpose: the first press answers with the command, the second runs exactly that, and the output lands in the room line by line. Nothing installs by itself — putting software on your computer unannounced would break the one thing MADRE promises, that a command is seen before it runs. The usual failure is npm refusing a global install with EACCES, which means the global prefix belongs to root; sudo works and leaves you with root-owned files, so a user-owned prefix or a version manager is the better answer.',
|
|
594
|
+
remedy: 'Press the button beside the version, read the command, run it. If npm refuses with EACCES, point npm at a prefix you own or use a version manager, then press it again.',
|
|
595
|
+
fixes: {
|
|
596
|
+
darwin: ['npm config get prefix', 'npm config set prefix ~/.npm-global', 'echo \'export PATH="$HOME/.npm-global/bin:$PATH"\' >> ~/.zshrc', '# or: brew install node # a Homebrew node owns its own prefix'],
|
|
597
|
+
linux: ['npm config get prefix', 'npm config set prefix ~/.npm-global', 'echo \'export PATH="$HOME/.npm-global/bin:$PATH"\' >> ~/.bashrc', '# or use nvm, which owns the prefix it installs into'],
|
|
598
|
+
},
|
|
599
|
+
},
|
|
600
|
+
{
|
|
601
|
+
id: 'module-add',
|
|
602
|
+
code: 'MU-046',
|
|
603
|
+
severity: 'common',
|
|
604
|
+
title: 'Installing a module somebody else wrote',
|
|
605
|
+
match: /already taken|must live under \/api\/x\/|not a module|default export must be a module|module file|\.module\.mjs|add a module/i,
|
|
606
|
+
diagnosis: 'MODULES has + ADD A MODULE for a .mjs somebody wrote. It comes through the same door as everything else: written to a scratch copy, imported there, checked against the house rules, and installed only if it passes. Three things get a file refused, each with the reason: an id that belongs to a module MADRE ships with, a route outside its own corner of the API (/api/x/<id>/), and a file that will not load at all. Said plainly, because it is what it is: a module runs inside MADRE, with your permissions, on this computer. MADRE checks that it loads and stays in its corral; what the code intends is the one thing nobody can check for you.',
|
|
607
|
+
remedy: 'Install it only if you trust where it came from. A module you wrote can also be updated from where you publish it: declare updates: { url } in the file, or install it from a file and MADRE remembers which, and its card carries GET A NEWER FILE.',
|
|
608
|
+
fixes: same(['# MODULES → + ADD A MODULE', 'ls ~/.pulse/modules # every module you installed, and where it came from', '# docs/SDK.md is the whole contract, and docs/sdk/hello-module.mjs is one that works']),
|
|
609
|
+
},
|
|
610
|
+
{
|
|
611
|
+
id: 'local-ready',
|
|
612
|
+
code: 'MU-047',
|
|
613
|
+
severity: 'common',
|
|
614
|
+
title: 'Is the local model ready to be worked in?',
|
|
615
|
+
match: /is .*(local|madre).*(ready|good|any good)|ready to be worked in|measured against this (room|project)|check it against this room|local\.present|not measured/i,
|
|
616
|
+
diagnosis: 'Running is not the same as being of use. @madre being in the room means Ollama has a chat model; whether that model is worth your work on THIS project is a different question, and the room measures it: twelve real exchanges from this room are put back to the local model, and each answer is scored against what the crew actually answered that day — against a control, so that talking about the same project does not count. It takes minutes and spends nothing, because all of it runs on your machine.',
|
|
617
|
+
remedy: 'The room says which of the two it has, once per model, in the line that begins "local · @madre". If nobody has measured it, that line carries CHECK IT AGAINST THIS ROOM; the answer lands in the room when it finishes, with the one thing to do about it. A model that does not pass is not broken — the archive is still thin, and it fills where the work happens. The @madre sphere keeps the reading and its date after the line has scrolled away, and the same test lives in NOSTROMO → THE THREE TESTS.',
|
|
618
|
+
fixes: same([
|
|
619
|
+
'# the line in the room: local · @madre → CHECK IT AGAINST THIS ROOM',
|
|
620
|
+
'# or: NOSTROMO → THE THREE TESTS → the third one',
|
|
621
|
+
'# it needs embeddings on and some turns answered by a CLI agent, which is what it compares against',
|
|
622
|
+
]),
|
|
623
|
+
},
|
|
624
|
+
{
|
|
625
|
+
id: 'code000',
|
|
626
|
+
code: 'MU-048',
|
|
627
|
+
severity: 'blocking',
|
|
628
|
+
title: 'CODE000: the archive is sealed',
|
|
629
|
+
match: /CODE000|THE ARCHIVE IS SEALED|UNABLE TO COMPUTE\. UNABLE TO CLARIFY|designation|intruder/i,
|
|
630
|
+
diagnosis: 'Two doors ask for the project designation before doing something that cannot be undone: boarding NOSTROMO, and purging every private term already recorded. Getting that name wrong is never part of doing the work — you either know the project you are standing in or you are trying names — so MU/TH/UR counts it. Eight wrong names inside five minutes and CODE000 comes down: the archive is sealed for ten minutes, the crew is told in code, and NOSTROMO asks for the designation again.',
|
|
631
|
+
remedy: 'The designation is the name of the project folder this room was opened in, lowercase or not. Nothing was lost: the seal is a refusal to change the archive for ten minutes, not a deletion, and it lifts on its own. Reading the core is not one of those doors and never counts — it is the way in.',
|
|
632
|
+
fixes: same(['basename "$PWD" # the designation this room asks for', '# the seal lifts on its own; MU/TH/UR › NOSTROMO asks for the name again after it does']),
|
|
633
|
+
},
|
|
634
|
+
{
|
|
635
|
+
id: 'core-console',
|
|
636
|
+
code: 'MU-049',
|
|
637
|
+
severity: 'common',
|
|
638
|
+
title: 'The core closed while I was typing in it',
|
|
639
|
+
match: /unable to compute|interface closed|core clos|three strikes|inquiry|order 937|mu\/th\/ur 6000/i,
|
|
640
|
+
diagnosis: 'Inside the core there is a console: MU/TH/UR answers about this room from what the page already has — the blocks of the next briefing and their words, the command that would run it, what has left this machine, how many terms privacy is protecting, where the archive stands. Nothing is asked of the crew and nothing is sent. She answers or she says she cannot, and three inquiries she cannot parse close the frame. The count is on screen from the first one and every refusal names what she would have taken.',
|
|
641
|
+
remedy: 'Nothing happened to the archive: reading what the room says in your name is not an attempt on it. Open the core again — click the star at the centre of NOSTROMO — and the count starts over. HELP lists every inquiry; READ <BLOCK> prints one block of the briefing word for word.',
|
|
642
|
+
fixes: same(['# NOSTROMO → click the core → type HELP', '# READ MEMORIES prints the block the next turn would carry', '# SPECIAL ORDER 937 is answered too']),
|
|
643
|
+
},
|
|
644
|
+
{
|
|
645
|
+
id: 'outbound',
|
|
646
|
+
code: 'MU-050',
|
|
647
|
+
severity: 'common',
|
|
648
|
+
title: 'What leaves this computer, and how to stop each thing that does',
|
|
649
|
+
match: /what left this machine|outbound|leaves? (this )?(computer|machine)|sends? (my )?(code|data)|telemetry|phone home|privacy/i,
|
|
650
|
+
diagnosis: 'MADRE runs here and keeps what it knows in a file you own, and the list that qualifies that sentence is inside the core, under WHAT LEFT THIS MACHINE. Seven addresses, each saying what it carries and where it is switched off: the briefing, which goes to whoever runs the agent you sent the turn to; Gemini embeddings, which carry the text of your memories; the image model, which carries the prompt an agent wrote; the npm registry and GitHub, which get a package name and nothing else; the error collector, which gets a redacted condition and only with your press; and Anthropic, for how much of your Claude plan is left. Ollama is on the list to be seen staying here.',
|
|
651
|
+
remedy: 'Under the list is the log itself: every request this process made, MADRE\'s own and any a module made, because a module runs inside MADRE and cannot opt out of the wrapper. No body, no header and no query value is ever written there — only which parameters were set. An address nothing declares is reported as exactly that.',
|
|
652
|
+
fixes: same([
|
|
653
|
+
'# NOSTROMO → click the core → WHAT LEFT THIS MACHINE',
|
|
654
|
+
'cat ~/.pulse/rooms/*/outbound.jsonl | tail -20 # the same log, on disk',
|
|
655
|
+
'# embeddings, to keep memories on this computer: MU/TH/UR → MEMORY → EMBEDDINGS → OLLAMA',
|
|
656
|
+
'PULSE_UPDATE_CHECK=0 madre start # no version checks at all',
|
|
657
|
+
]),
|
|
658
|
+
},
|
|
463
659
|
];
|
|
464
660
|
|
|
661
|
+
// Which of the two sets of commands to show first. Anything else gets macOS, and the panel says
|
|
662
|
+
// which shells these are written for rather than guessing on the reader's behalf.
|
|
465
663
|
export function detectPlatform(nav = globalThis.navigator) {
|
|
466
664
|
const hint = `${nav?.userAgentData?.platform ?? ''} ${nav?.platform ?? ''} ${nav?.userAgent ?? ''}`.toLowerCase();
|
|
467
665
|
if (/mac|iphone|ipad|darwin/.test(hint)) return 'darwin';
|
|
468
|
-
if (/win/.test(hint)) return 'win32';
|
|
469
666
|
if (/linux|android|x11/.test(hint)) return 'linux';
|
|
470
667
|
return 'darwin';
|
|
471
668
|
}
|
|
@@ -474,13 +671,25 @@ export function detectPlatform(nav = globalThis.navigator) {
|
|
|
474
671
|
// only match their own agent; generic ones match anyone.
|
|
475
672
|
export function diagnose(errorText, agent = null) {
|
|
476
673
|
const text = String(errorText ?? '');
|
|
477
|
-
return CONDITIONS.filter((condition) => (!condition.agent || !agent || condition.agent === agent) && condition.match.test(text));
|
|
674
|
+
return CONDITIONS.filter((condition) => (!condition.agent || !agent || condition.agent === agent) && condition.match.test(text)).map(said);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// A condition reads in the room's language; what it MATCHES does not. The `match` patterns are
|
|
678
|
+
// tested against what a CLI printed, and a CLI prints English whatever the room speaks — so the
|
|
679
|
+
// patterns stay exactly as they are, and only the words a person reads go through the catalogue.
|
|
680
|
+
// Said at call time, because the table is built when this file is imported and the room learns
|
|
681
|
+
// its language after.
|
|
682
|
+
export function said(condition) {
|
|
683
|
+
return { ...condition, title: t(condition.title), diagnosis: t(condition.diagnosis), remedy: t(condition.remedy) };
|
|
478
684
|
}
|
|
685
|
+
export const allConditions = () => CONDITIONS.map(said);
|
|
479
686
|
|
|
480
687
|
export function searchConditions(query) {
|
|
481
688
|
const needle = String(query ?? '').trim().toLowerCase();
|
|
482
|
-
|
|
483
|
-
|
|
689
|
+
const all = allConditions();
|
|
690
|
+
if (!needle) return all;
|
|
691
|
+
// Searched in the language it is read in, plus the id and the agent, which are never translated.
|
|
692
|
+
return all.filter((condition) => [condition.id, condition.title, condition.diagnosis, condition.remedy, condition.agent ?? '']
|
|
484
693
|
.join(' ').toLowerCase().includes(needle));
|
|
485
694
|
}
|
|
486
695
|
|