Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
db7eae2
feat(config): memory.external schema — backend, recall sub-block, use…
Jun 11, 2026
5d0cbec
feat(memory): external memory backend (Hindsight) + creation tee
Jun 11, 2026
0f2bba1
fix(embedding): gate compartment P1 embedding on the embedding provid…
Jun 11, 2026
b7be664
feat(memory): session-start external recall — frozen snapshot, cache-…
Jun 11, 2026
dc35a0b
feat(memory): W2 corrective propagation + verify action + global-scop…
Jun 11, 2026
d9277ad
feat(status): external-memory section in ctx-status (W3)
Jun 11, 2026
4737cae
feat(core): child->parent session registry — root-session resolution …
Jun 11, 2026
53222fb
feat(tui): External Memory section in the status dialog
Jun 11, 2026
25167d7
fix(tui): detect local dev paths in tui.json magic-context entry
Jun 11, 2026
e39349f
fix(config): don't treat user config as untrusted project config
Jun 11, 2026
b7397c3
perf(models-dev): cache-first boot — seed from persisted cache, refre…
Jun 12, 2026
f83c068
fix(rebase): complete v0.24 resolution — embedding consumer shape, mi…
Jun 14, 2026
aae73ad
fix(memory): tighten external-recall embedding model-guard + regressi…
Jun 14, 2026
78bedb7
feat(pi): arm external-memory backend + ctx-memory tee/W2/verify/glob…
Jun 14, 2026
026b856
feat(pi): external-memory session recall + m[0]/m[1] render + ctx-sea…
Jun 14, 2026
cfccc86
style: apply biome lint fixes to external-memory wiring
Jun 14, 2026
2a8cd71
fix(pi): exclude external m[1] delta from pressure refold (cache parity)
Jun 14, 2026
a518920
fix(pi): render profile slice in external m[1] delta
Jun 14, 2026
c07b550
fix(memory): make external-recall model-guard test exercise real dedup
Jun 14, 2026
5b95697
docs(external-memory): document external memory backend (ARCHITECTURE…
Jun 16, 2026
d2e0a6f
docs(external-memory): clarify profile slice merges into user-profile…
Jun 16, 2026
db3213e
fix(db): eliminate 'database is locked' plugin-load failures
Jun 19, 2026
b462755
fix(concurrency): centralize write transactions with bounded SQLITE_B…
Jun 20, 2026
3688e25
fix(dashboard): classify memory.external in ConfigEditor coverage man…
Jun 20, 2026
1abddf6
fix(e2e): set busy_timeout in memory-injection seedMemory
Jun 20, 2026
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
213 changes: 207 additions & 6 deletions ARCHITECTURE.md

Large diffs are not rendered by default.

71 changes: 70 additions & 1 deletion CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,69 @@ Cross-session memory settings. All memories are scoped to the current project (i
| `injection_budget_tokens` | `number` (500–20000) | `4000` | Token budget for memory injection into `<session-history>`. |
| `auto_promote` | `boolean` | `true` | Promote eligible session facts to project memories automatically after historian or `/ctx-recomp` runs. When `false`, historian and recomp do not write any new memories — agents can still create memories explicitly via `ctx_memory write`, and existing memories continue to be injected and searched normally. |
| `retrieval_count_promotion_threshold` | `number` | `3` | Retrievals needed before a memory is auto-promoted to permanent. |
| `external` | `object` | See below | **User-config-only.** Long-term memory backend (Hindsight) — tees curated writes OUT and recalls them BACK once per session, plus an explicit-only `ctx_search` source. A cloned repo cannot redirect the endpoint or read a user's personal memory store. |

### `memory.external`

The `memory.external` block controls the **external memory backend** (Hindsight): a long-term companion store that holds curated memories OUT of the project (so they survive across projects, harness restarts, and (in future) the user's whole fleet) and recalls them BACK once per session. The local SQLite store remains the source of truth; the external store is a long-term companion.

**Security:** this entire block is **user-config-only**. `stripUnsafeProjectConfigFields()` in `src/config/project-security.ts` drops it from project-level config (parallel to `auto_update` and `sqlite`) — a cloned repo cannot redirect the endpoint, exfiltrate a user's personal memory store, or read a user's external memory by editing `magic-context.jsonc` in a project root. Set the block in `~/.config/opencode/magic-context.jsonc` (OpenCode) or `~/.pi/agent/magic-context.jsonc` (Pi).

```jsonc
{
"memory": {
"external": {
"provider": "off", // "off" (default) or "hindsight"
"endpoint": "http://10.0.0.1:8889", // required when provider is hindsight; Hindsight base URL
"api_key": "{env:HINDSIGHT_API_KEY}", // optional bearer token
"project_bank": "mc-{name}-{id8}", // project bank name template; {name}=project basename, {id8}=first 8 chars of the project identity hash
"main_bank": "user-memories", // required when provider is hindsight; bank for user + global scope. Assumed to pre-exist; never created or modified by the plugin
"retain_sources": ["historian", "agent", "dreamer"], // which creation points tee (gate write-side, not read-side)
"tags": [], // static tags attached to every retained item
"recall": { // see `memory.external.recall` below
"enabled": true,
"timeout_ms": 3000,
"max_tokens": 2048,
"dedup_threshold": 0.85,
"global_tags": [],
"global_from_prompt": false,
"search": true,
"mental_models": true,
"profile_mental_models": ["user-preferences"]
}
}
}
}
```

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `provider` | `"hindsight"` \| `"off"` | `"off"` | Backend implementation. `"off"` disables the whole feature. `"hindsight"` activates tee-on-write + once-per-session recall + the explicit `ctx_search` source. |
| `endpoint` | `string` | — | Required when `provider: "hindsight"`. Hindsight base URL (e.g. `http://10.0.0.1:8889`). Trailing slashes are stripped. SSRF-guarded. |
| `api_key` | `string` | — | Optional bearer token. Supports `{env:VAR}` substitution. Never logged. |
| `project_bank` | `string` (template) | `"mc-{name}-{id8}"` | Project-bank name template. Placeholders: `{name}` = sanitized project basename, `{id8}` = first 8 chars of the project identity hash. Created on first retain (PUT with the project bank mission). |
| `main_bank` | `string` | — | Required when `provider: "hindsight"`. Bank for `user` and `global` scope items. **Assumed to pre-exist; the plugin never creates or modifies it.** |
| `retain_sources` | `string[]` | `["historian","agent","dreamer"]` | Which creation points tee to the external backend. `historian` = fact promotion in `src/features/magic-context/memory/promotion.ts`. `agent` = `ctx_memory` `write` / `update` in `src/tools/ctx-memory/tools.ts`. `dreamer` = user-memory promotion in `src/features/magic-context/user-memory/review-user-memories.ts`. Read paths (recall, search, mental-models) and W2 correctives (archive/update/verify corrective propagation) are NOT gated by this list. |
| `tags` | `string[]` | `[]` | Static tags attached to every retained item (in addition to the always-present `source:magic-context`, `category:<X>`, and the per-scope `project:<id>` / `scope:user` / `scope:global` tags). |
| `recall` | `object` | See below | Unified read path — session-start recall + `ctx_search` external source. |

### `memory.external.recall`

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `enabled` | `boolean` | `true` | Session-start recall from the external backend, merged into the context injection. Late results ride the m[1] `<external-memory>` delta; the first m[0] render awaits up to `timeout_ms` (cache-cold path). |
| `timeout_ms` | `number` (500–15000) | `3000` | Max wait for recall at the first render of a session (when the cache is already cold). Late results arrive as an m[1] delta on later passes. |
| `max_tokens` | `number` (256–8192) | `2048` | Per-slice token budget — project / profile / global each get this cap. The external block is NOT charged to the history or local-memory budget; it is bounded solely by this × 3 slices. |
| `dedup_threshold` | `number` (0.5–0.99) | `0.85` | Cosine similarity above which a recalled item is dropped as a duplicate of a local memory (or active user memory). Hash-only fallback when embeddings are unavailable. |
| `global_tags` | `string[]` | `[]` | Tag filter for the global (main-bank) recall slice, matched with `tags_match: "any"` (untagged content INCLUDED). `[]` = no filter sent (full autoRecall replacement). |
| `global_from_prompt` | `boolean` | `false` | Include an excerpt of the session's first user prompt in the global-slice recall query (the project name is always included). The first prompt is fixed for the session, so the query — and the frozen recall snapshot — stays deterministic across crash-recovery re-fires. |
| `search` | `boolean` | `true` | Expose the `ctx_search` `"external"` source (project + main bank). **Explicit-only** — the auto-search hot path (every user prompt hint) NEVER hits it, even when this is `true`. |
| `mental_models` | `boolean` | `true` | Use Hindsight mental models as the fast path for the project and profile recall slices (single GET, server-refreshed), falling back to full recall when absent/empty. The global slice always uses full recall. |
| `profile_mental_models` | `string[]` | `["user-preferences"]` | Main-bank mental-model names (case-insensitive) used for the profile slice. The main bank is never modified by the plugin — create these manually. Project-bank mental models are seeded by the plugin (`project-conventions`, `project-decisions`) on the first successful retain. |

**Engine-agnostic interface.** `external-memory-provider.ts` defines a neutral `ExternalMemoryBackend` interface (mirrors the `EmbeddingProvider` pattern). Hindsight is the only shipped implementation today. Document identity is `mc:<scopeKey>:<category>:<hash>` (content-derived → idempotent re-retains upsert on the server; retries never duplicate). Bank routing: `scope: "project"` → `project_bank` (per project); `scope: "user"` and `scope: "global"` → `main_bank`. Project items carry `project:<id>` + `project-name:<basename>` tags; globals carry `scope:global` plus `origin-project:*` provenance tags and a `context` field that names the originating project so Hindsight's fact extractor links it as an entity.

**Failure semantics.** Hindsight calls are fire-and-forget; failures are logged, never thrown. The impl carries a circuit breaker (3 fails in 60s → open 5min → half-open probe) so a hung endpoint can't drag every plugin operation through its timeout. A 422 (memory-defense rejected content) is treated as a hard no and never retried; 404 on a missing bank is benign (returns empty for the slice). The bearer token is never logged. Cross-harness: OpenCode and Pi share the same banks (the impl is a single, neutral interface and the project bank name is project-derived, so both harnesses resolve the same bank).

---

Expand Down Expand Up @@ -652,7 +715,13 @@ Tier boundaries are hardcoded to keep behavior predictable and prevent cache-bus
"injection_budget_tokens": 4000,
"auto_promote": true,
"auto_search": { "enabled": true, "score_threshold": 0.6, "min_prompt_chars": 20 },
"git_commit_indexing": { "enabled": false, "since_days": 365, "max_commits": 2000 }
"git_commit_indexing": { "enabled": false, "since_days": 365, "max_commits": 2000 },
"external": { // USER-LEVEL ONLY — stripped from project config
"provider": "hindsight",
"endpoint": "http://10.0.0.1:8889",
"api_key": "{env:HINDSIGHT_API_KEY}",
"main_bank": "user-memories"
}
},

"sidekick": {
Expand Down
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ Because it runs during idle time, the dreamer pairs well with local models, even

*The right memory at the right moment.* Every turn, active project memories and the compacted session history are injected automatically and cache-stably. On demand, the agent reaches for:

- **`ctx_search`**: one query across three layers at once: project **memories**, raw **conversation** history, and indexed **git commits**. Semantic embeddings with full-text fallback.
- **`ctx_search`**: one query across four layers at once: project **memories**, raw **conversation** history, indexed **git commits**, and (opt-in) the long-term **external** memory backend. Semantic embeddings with full-text fallback.

```
ctx_search(query="why did we pick event sourcing for orders")
Expand All @@ -191,15 +191,17 @@ Because it runs during idle time, the dreamer pairs well with local models, even

Recall works **across sessions** (a new session inherits everything) and **across harnesses** (write a memory in OpenCode, retrieve it in Pi).

> **Long-term memory** *(opt-in, off by default)* tees curated writes (historian promotions, `ctx_memory` writes, dreamer user-memory promotions) to a Hindsight backend and recalls them BACK once per session as a `<external-memory>` block — across sessions, across harnesses, and across projects for the global slice. Same `ctx_search` "external" source (explicit-only, never on the auto-search hot path). User-config-only; a repo cannot redirect the endpoint. Configure under `memory.external`; see [CONFIGURATION.md](./CONFIGURATION.md#memoryexternal).
>
> **Auto search hints** *(on by default)* run a background `ctx_search` each turn and whisper a "vague recall" when something relevant exists — like almost remembering a note you took. It appends only compact fragments, never full content; set `memory.auto_search.enabled: false` to turn it off. **Git commit indexing** *(opt-in)* makes your project history semantically searchable as a fourth `ctx_search` source — enable with `memory.git_commit_indexing.enabled: true`.

### Agent tools at a glance

| Tool | Section | What it does |
|------|-------|-------------|
| `ctx_reduce` | Context | Queue stale tagged content for removal, cache-aware |
| `ctx_memory` | Capture | Write or delete durable cross-session memories |
| `ctx_search` | Recall | Search memories, conversation history, and git commits |
| `ctx_memory` | Capture | Write or delete durable cross-session memories (project scope, plus `global` scope to the external main bank when configured) |
| `ctx_search` | Recall | Search memories, conversation history, git commits, and (explicit-only) the external long-term memory backend |
| `ctx_expand` | Recall | Decompress a history range back to the transcript |
| `ctx_note` | Recall | Deferred intentions and dreamer-evaluated smart notes |

Expand Down
Loading