Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions .lore/reference/_infrastructure/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ Configuration manages per-vault settings via `.memory-loop.json`. Each vault can

| Field | Type | Default | Purpose |
|-------|------|---------|---------|
| `discussionModel` | "opus" \| "sonnet" \| "haiku" | "opus" | Model for conversations |
| `discussionModel` | string (key from global model registry) | undefined (uses pi-agent fallback) | Model for conversations |

### Inspiration

Expand Down Expand Up @@ -166,7 +166,7 @@ Zod schema enforces:
- `recentDiscussions`: int, 1-20
- `badges`: array max 5, text max 20 chars
- `order`: int, min 1
- `discussionModel`: enum ["opus", "sonnet", "haiku"]
- `discussionModel`: any string; validation against registry happens at session creation.

Invalid values return 400 error with message displayed inline in dialog.

Expand All @@ -192,6 +192,25 @@ This means `VaultInfo` always has resolved values; the frontend doesn't need to
| [Spaced Repetition](../spaced-repetition.md) | cardsEnabled |
| [Think](../think.md) | discussionModel |

## Global Config File

A daemon-level config file defines the model registry available across all vaults.

**Path**: `MEMORY_LOOP_CONFIG` env var overrides, otherwise `${VAULTS_DIR}/memory-loop-config.json`

**Format**:
```json
{
"models": {
"<name>": { "provider": "...", "modelId": "..." }
}
}
```

**Endpoint**: `GET /api/models` returns `{ "models": [{ "name": "...", "provider": "...", "modelId": "..." }] }`

**Behavior when absent or malformed**: daemon logs a warning, continues with empty registry. `GET /api/models` returns `{ "models": [] }`.

## Notes

- Title/subtitle from config override CLAUDE.md extraction
Expand Down
5 changes: 3 additions & 2 deletions .lore/reference/think.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,9 @@ Claude can ask structured questions:
## Model Selection

**Config**: `.memory-loop.json` → `discussionModel`
**Options**: `"opus"` | `"sonnet"` | `"haiku"`
**Default**: `"opus"`
**Options**: any key defined in the global model registry (see Global Config)
**Default**: none — unset vaults fall back to the pi-agent default
**Resolution**: at session creation, the daemon looks up the vault's `discussionModel` string in the runtime registry. Unknown names log a warning and fall back to pi-agent default.

Passed to Claude SDK when creating session.

Expand Down
196 changes: 196 additions & 0 deletions .lore/work/notes/configurable-discussion-models.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Notes: configurable discussion model registry</title>
<meta name="date" content="2026-05-21">
<meta name="status" content="complete">
<meta name="tags" content="notes, model-config, global-config, discussion, pi-agent">
<meta name="modules" content="session-manager, daemon-config, vault-config, config-editor-dialog">
<meta name="related" content=".lore/work/plans/configurable-discussion-models.html, .lore/work/specs/configurable-discussion-models.html">
<style>
* { box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
font-size: 15px;
line-height: 1.6;
color: #1a1a1a;
max-width: 900px;
margin: 0 auto;
padding: 2rem 1.5rem 5rem;
}
h1 { font-size: 1.5rem; margin-bottom: 0.25rem; }
h2 { font-size: 1.1rem; margin-top: 2rem; border-bottom: 1px solid #ddd; padding-bottom: 0.3rem; }

.banner {
display: flex;
align-items: center;
gap: 1rem;
padding: 1rem 1.25rem;
border-radius: 8px;
margin: 1.5rem 0;
font-size: 0.9rem;
}
.banner.in-progress { background: #eff6ff; border: 2px solid #3b82f6; }
.banner.complete { background: #f0fdf4; border: 2px solid #22c55e; }
.banner.failed { background: #fef2f2; border: 2px solid #ef4444; }
.banner-state {
font-weight: 700;
font-size: 1rem;
white-space: nowrap;
}

.phase-list { list-style: none; padding: 0; margin: 1rem 0; }
.phase-item {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.5rem 0.75rem;
border: 1px solid #e5e7eb;
border-radius: 6px;
margin: 0.4rem 0;
cursor: pointer;
user-select: none;
}
.phase-item:hover { background: #f9fafb; }
.chip {
display: inline-block;
padding: 2px 10px;
border-radius: 12px;
font-size: 0.72rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.05em;
white-space: nowrap;
}
.chip-pending { background: #f3f4f6; color: #6b7280; }
.chip-progress { background: #dbeafe; color: #1d4ed8; }
.chip-done { background: #d1fae5; color: #065f46; }
.chip-failed { background: #fee2e2; color: #991b1b; }

.phase-label { flex: 1; font-size: 0.9rem; }

.log-panel {
display: none;
background: #f8fafc;
border: 1px solid #e5e7eb;
border-top: none;
padding: 0.75rem 1rem;
font-size: 0.85rem;
color: #374151;
border-radius: 0 0 6px 6px;
margin-top: -4px;
}
.log-panel.open { display: block; }
.log-panel p { margin: 0.35rem 0; }
.log-panel code { background: #e5e7eb; padding: 1px 4px; border-radius: 3px; font-size: 0.8rem; }
.log-panel ul { padding-left: 1.2rem; margin: 0.35rem 0; }
.log-panel li { margin: 0.2rem 0; }

.empty { color: #9ca3af; font-style: italic; font-size: 0.85rem; }
</style>
</head>
<body>

<h1>Notes: configurable discussion model registry</h1>
<p style="color:#888;font-size:0.85rem;">Plan: <a href="../plans/configurable-discussion-models.html">configurable-discussion-models.html</a></p>

<div class="banner complete" id="banner">
<div class="banner-state">COMPLETE</div>
<div>All 6 phases done. 2042 tests pass, 0 failures. Typecheck and lint clean. No non-conformances against spec.</div>
</div>

<h2>Phase Progress</h2>

<ul class="phase-list">
<li>
<div class="phase-item" onclick="toggle('log-1')">
<span class="chip chip-done" id="chip-1">done</span>
<span class="phase-label">Phase 1 — Create <code>global-config.ts</code> + tests</span>
</div>
<div class="log-panel" id="log-1">
<p>✓ Created <code>daemon/src/global-config.ts</code> — registry with loadGlobalConfig, getRegistry, configureRegistryForTesting, _resetRegistryForTesting.</p>
<p>✓ Created <code>daemon/src/__tests__/global-config.test.ts</code> — 5/5 tests pass.</p>
<p>✓ Typecheck clean.</p>
</div>
</li>
<li>
<div class="phase-item" onclick="toggle('log-2')">
<span class="chip chip-done" id="chip-2">done</span>
<span class="phase-label">Phase 2 — Daemon startup wiring + <code>GET /models</code> route</span>
</div>
<div class="log-panel" id="log-2">
<p>✓ Wired <code>await loadGlobalConfig()</code> after <code>initVaultCache()</code> in <code>daemon/src/index.ts</code>.</p>
<p>✓ Created <code>daemon/src/routes/models.ts</code> with <code>getModelsHandler</code>.</p>
<p>✓ Registered <code>GET /models</code> in <code>daemon/src/router.ts</code>.</p>
<p>✓ Created <code>daemon/src/routes/__tests__/models.test.ts</code> — 2/2 tests pass.</p>
<p>✓ Typecheck clean.</p>
</div>
</li>
<li>
<div class="phase-item" onclick="toggle('log-3')">
<span class="chip chip-done" id="chip-3">done</span>
<span class="phase-label">Phase 3 — Shared type purge (atomic breaking change)</span>
</div>
<div class="log-panel" id="log-3">
<p>✓ Removed VALID_DISCUSSION_MODELS, DiscussionModelLocal, DEFAULT_DISCUSSION_MODEL, resolveDiscussionModel() from packages/shared/src/vault-config.ts.</p>
<p>✓ Removed their exports from packages/shared/src/index.ts.</p>
<p>✓ Removed DiscussionModelSchema, DiscussionModel from packages/shared/src/schemas/protocol.ts; updated both fields to z.string().optional().</p>
<p>✓ Cascading: removed re-exports from packages/shared/src/schemas/index.ts; updated VaultInfo.discussionModel type in schemas/types.ts.</p>
<p>✓ Simplified vault-config.ts enum guard to plain string check; updated vault-manager.ts to pass config.discussionModel directly.</p>
<p>✓ Rewrote resolveModelForPiAgent() in session-manager.ts to use getRegistry() lookup; removed DISCUSSION_MODEL_MAP.</p>
<p>✓ Cascading: Phase 3 agent also fixed vault-config.test.ts (removed deleted imports, updated discussionModel tests) and fixed ConfigEditorDialog.tsx local type.</p>
<p>✓ Typecheck clean. grep shows zero matches for all removed symbols.</p>
</div>
</li>
<li>
<div class="phase-item" onclick="toggle('log-4')">
<span class="chip chip-done" id="chip-4">done</span>
<span class="phase-label">Phase 4 — Next.js layer (proxy route, daemon client, frontend)</span>
</div>
<div class="log-panel" id="log-4">
<p>✓ Created nextjs/app/api/models/route.ts — proxy to daemon /models.</p>
<p>✓ Created nextjs/lib/daemon/models.ts — ModelEntry interface + getModels().</p>
<p>✓ Updated nextjs/lib/daemon/index.ts barrel export.</p>
<p>✓ Updated ConfigEditorDialog.tsx: added availableModels state + useEffect fetch, dynamic select with unknown-model handling.</p>
<p>✓ Typecheck clean.</p>
</div>
</li>
<li>
<div class="phase-item" onclick="toggle('log-5')">
<span class="chip chip-done" id="chip-5">done</span>
<span class="phase-label">Phase 5 — Update existing tests</span>
</div>
<div class="log-panel" id="log-5">
<p>✓ session-manager.test.ts: added registry imports + _resetRegistryForTesting to afterEach; injected haiku registry for "uses vault config model" test; updated "no discussionModel" assertion to toBeUndefined().</p>
<p>✓ ConfigEditorDialog.test.tsx: added global fetch mock (opus/sonnet/haiku); updated assertions from descriptive labels to plain name keys; added waitFor for async useEffect; added unknown-model test.</p>
<p>✓ vault-config.test.ts already done by Phase 3 agent.</p>
<p>✓ Full suite: 2042 tests, 0 failures.</p>
</div>
</li>
<li>
<div class="phase-item" onclick="toggle('log-6')">
<span class="chip chip-done" id="chip-6">done</span>
<span class="phase-label">Phase 6 — Documentation updates</span>
</div>
<div class="log-panel" id="log-6">
<p>✓ configuration.md: updated discussionModel type/default, updated validation note, added Global Config File section.</p>
<p>✓ think.md: updated Model Selection section to reference registry instead of hardcoded enum.</p>
<p>✓ Typecheck clean.</p>
</div>
</li>
</ul>

<h2>Decisions &amp; Divergences</h2>
<ul>
<li>Pre-existing change to <code>session-manager.ts</code>: DISCUSSION_MODEL_MAP had model IDs updated (claude-opus-4-7, claude-sonnet-4-6, claude-haiku-4-6) and two fallback entries added (basic, text). Phase 3 removes DISCUSSION_MODEL_MAP entirely, so these changes are subsumed.</li>
</ul>

<script>
function toggle(id) {
const panel = document.getElementById(id);
panel.classList.toggle('open');
}
</script>
</body>
</html>
113 changes: 113 additions & 0 deletions .lore/work/notes/simplify-configurable-discussion-models.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Simplify: configurable discussion model registry</title>
<meta name="date" content="2026-05-21">
<meta name="status" content="complete">
<meta name="tags" content="simplify, model-config, global-config, discussion">
<meta name="modules" content="session-manager, daemon-config, vault-config, config-editor-dialog">
<meta name="related" content=".lore/work/notes/configurable-discussion-models.html">
<style>
* { box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
font-size: 15px; line-height: 1.6; color: #1a1a1a;
max-width: 900px; margin: 0 auto; padding: 2rem 1.5rem 5rem;
}
h1 { font-size: 1.4rem; margin-bottom: 0.25rem; }
h2 { font-size: 1.05rem; margin-top: 1.8rem; border-bottom: 1px solid #ddd; padding-bottom: 0.25rem; }

.banner {
display: flex; align-items: center; gap: 1rem;
padding: 0.85rem 1.1rem; border-radius: 7px; margin: 1.25rem 0; font-size: 0.9rem;
}
.banner.in-progress { background: #eff6ff; border: 2px solid #3b82f6; }
.banner.complete { background: #f0fdf4; border: 2px solid #22c55e; }
.banner-state { font-weight: 700; font-size: 0.95rem; white-space: nowrap; }

.file-list { list-style: none; padding: 0; margin: 0.75rem 0; }
.file-row {
display: flex; align-items: center; gap: 0.6rem;
padding: 0.4rem 0.7rem; border: 1px solid #e5e7eb; border-radius: 5px;
margin: 0.3rem 0; cursor: pointer; user-select: none; font-size: 0.88rem;
}
.file-row:hover { background: #f9fafb; }
.file-name { flex: 1; font-family: monospace; font-size: 0.82rem; }

.chip {
display: inline-block; padding: 1px 9px; border-radius: 10px;
font-size: 0.69rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; white-space: nowrap;
}
.chip-pending { background: #f3f4f6; color: #6b7280; }
.chip-simplified { background: #d1fae5; color: #065f46; }
.chip-unchanged { background: #f0f9ff; color: #0369a1; }
.chip-failed { background: #fee2e2; color: #991b1b; }
.chip-running { background: #dbeafe; color: #1d4ed8; }

.detail {
display: none; background: #f8fafc; border: 1px solid #e5e7eb; border-top: none;
padding: 0.6rem 0.9rem; font-size: 0.83rem; color: #374151;
border-radius: 0 0 5px 5px; margin-top: -3px;
}
.detail.open { display: block; }
.detail p { margin: 0.25rem 0; }

.escalation { background: #fef2f2; border: 2px solid #ef4444; border-radius: 6px; padding: 0.8rem 1rem; margin: 1rem 0; font-size: 0.88rem; }
</style>
</head>
<body>

<h1>Simplify: configurable discussion model registry</h1>

<div class="banner in-progress" id="banner">
<div class="banner-state">IN PROGRESS</div>
<div>Cleanup pass on all 23 changed files from the implementation. Three parallel simplifier groups, then test and review.</div>
</div>

<h2>Group A — Daemon core (7 files)</h2>
<ul class="file-list">
<li><div class="file-row" onclick="toggle('d-global-config')"><span class="chip chip-running">running</span><span class="file-name">daemon/src/global-config.ts</span></div><div class="detail" id="d-global-config"></div></li>
<li><div class="file-row" onclick="toggle('d-index')"><span class="chip chip-running">running</span><span class="file-name">daemon/src/index.ts</span></div><div class="detail" id="d-index"></div></li>
<li><div class="file-row" onclick="toggle('d-router')"><span class="chip chip-running">running</span><span class="file-name">daemon/src/router.ts</span></div><div class="detail" id="d-router"></div></li>
<li><div class="file-row" onclick="toggle('d-session-manager')"><span class="chip chip-running">running</span><span class="file-name">daemon/src/session-manager.ts</span></div><div class="detail" id="d-session-manager"></div></li>
<li><div class="file-row" onclick="toggle('d-routes-models')"><span class="chip chip-running">running</span><span class="file-name">daemon/src/routes/models.ts</span></div><div class="detail" id="d-routes-models"></div></li>
<li><div class="file-row" onclick="toggle('d-vault-config')"><span class="chip chip-running">running</span><span class="file-name">daemon/src/vault/vault-config.ts</span></div><div class="detail" id="d-vault-config"></div></li>
<li><div class="file-row" onclick="toggle('d-vault-manager')"><span class="chip chip-running">running</span><span class="file-name">daemon/src/vault/vault-manager.ts</span></div><div class="detail" id="d-vault-manager"></div></li>
</ul>

<h2>Group B — Daemon tests (4 files)</h2>
<ul class="file-list">
<li><div class="file-row" onclick="toggle('t-global-config')"><span class="chip chip-running">running</span><span class="file-name">daemon/src/__tests__/global-config.test.ts</span></div><div class="detail" id="t-global-config"></div></li>
<li><div class="file-row" onclick="toggle('t-session-manager')"><span class="chip chip-running">running</span><span class="file-name">daemon/src/__tests__/session-manager.test.ts</span></div><div class="detail" id="t-session-manager"></div></li>
<li><div class="file-row" onclick="toggle('t-vault-config')"><span class="chip chip-running">running</span><span class="file-name">daemon/src/vault/__tests__/vault-config.test.ts</span></div><div class="detail" id="t-vault-config"></div></li>
<li><div class="file-row" onclick="toggle('t-models-route')"><span class="chip chip-running">running</span><span class="file-name">daemon/src/routes/__tests__/models.test.ts</span></div><div class="detail" id="t-models-route"></div></li>
</ul>

<h2>Group C — Shared + Next.js (12 files)</h2>
<ul class="file-list">
<li><div class="file-row" onclick="toggle('s-shared-index')"><span class="chip chip-running">running</span><span class="file-name">packages/shared/src/index.ts</span></div><div class="detail" id="s-shared-index"></div></li>
<li><div class="file-row" onclick="toggle('s-schemas-index')"><span class="chip chip-running">running</span><span class="file-name">packages/shared/src/schemas/index.ts</span></div><div class="detail" id="s-schemas-index"></div></li>
<li><div class="file-row" onclick="toggle('s-protocol')"><span class="chip chip-running">running</span><span class="file-name">packages/shared/src/schemas/protocol.ts</span></div><div class="detail" id="s-protocol"></div></li>
<li><div class="file-row" onclick="toggle('s-types')"><span class="chip chip-running">running</span><span class="file-name">packages/shared/src/schemas/types.ts</span></div><div class="detail" id="s-types"></div></li>
<li><div class="file-row" onclick="toggle('s-vault-config')"><span class="chip chip-running">running</span><span class="file-name">packages/shared/src/vault-config.ts</span></div><div class="detail" id="s-vault-config"></div></li>
<li><div class="file-row" onclick="toggle('n-config-editor')"><span class="chip chip-running">running</span><span class="file-name">nextjs/components/vault/ConfigEditorDialog.tsx</span></div><div class="detail" id="n-config-editor"></div></li>
<li><div class="file-row" onclick="toggle('n-config-editor-test')"><span class="chip chip-running">running</span><span class="file-name">nextjs/components/vault/__tests__/ConfigEditorDialog.test.tsx</span></div><div class="detail" id="n-config-editor-test"></div></li>
<li><div class="file-row" onclick="toggle('n-daemon-index')"><span class="chip chip-running">running</span><span class="file-name">nextjs/lib/daemon/index.ts</span></div><div class="detail" id="n-daemon-index"></div></li>
<li><div class="file-row" onclick="toggle('n-daemon-models')"><span class="chip chip-running">running</span><span class="file-name">nextjs/lib/daemon/models.ts</span></div><div class="detail" id="n-daemon-models"></div></li>
<li><div class="file-row" onclick="toggle('n-api-models')"><span class="chip chip-running">running</span><span class="file-name">nextjs/app/api/models/route.ts</span></div><div class="detail" id="n-api-models"></div></li>
</ul>

<h2>Test &amp; Review</h2>
<ul class="file-list">
<li><div class="file-row"><span class="chip chip-pending">pending</span><span class="file-name">bun run test (full suite)</span></div></li>
<li><div class="file-row"><span class="chip chip-pending">pending</span><span class="file-name">code review — behavior preservation check</span></div></li>
</ul>

<script>
function toggle(id) {
document.getElementById(id).classList.toggle('open');
}
</script>
</body>
</html>
Loading
Loading