@onlooker-community/ecosystem 0.19.0 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/.claude-plugin/marketplace.json +26 -0
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.release-please-manifest.json +4 -2
  4. package/CHANGELOG.md +14 -0
  5. package/docs/memory-architecture.md +102 -0
  6. package/package.json +3 -3
  7. package/plugins/curator/.claude-plugin/plugin.json +14 -0
  8. package/plugins/curator/CHANGELOG.md +10 -0
  9. package/plugins/curator/README.md +55 -0
  10. package/plugins/curator/config.json +41 -0
  11. package/plugins/curator/docs/adr/001-staleness-tiers.md +100 -0
  12. package/plugins/curator/docs/design.md +311 -0
  13. package/plugins/curator/hooks/hooks.json +15 -0
  14. package/plugins/curator/scripts/hooks/curator-session-start.sh +343 -0
  15. package/plugins/curator/scripts/lib/curator-checks.sh +155 -0
  16. package/plugins/curator/scripts/lib/curator-config.sh +67 -0
  17. package/plugins/curator/scripts/lib/curator-emit.sh +61 -0
  18. package/plugins/curator/scripts/lib/curator-memory-reader.sh +225 -0
  19. package/plugins/curator/scripts/lib/curator-project-key.sh +82 -0
  20. package/plugins/curator/scripts/lib/curator-storage.sh +176 -0
  21. package/plugins/curator/scripts/lib/curator-ulid.sh +43 -0
  22. package/plugins/historian/docs/adr/001-local-embeddings-only.md +96 -0
  23. package/plugins/historian/docs/design.md +317 -0
  24. package/plugins/librarian/.claude-plugin/plugin.json +14 -0
  25. package/plugins/librarian/CHANGELOG.md +10 -0
  26. package/plugins/librarian/README.md +51 -0
  27. package/plugins/librarian/config.json +52 -0
  28. package/plugins/librarian/docs/adr/001-propose-dont-auto-write.md +87 -0
  29. package/plugins/librarian/docs/design.md +301 -0
  30. package/plugins/librarian/hooks/hooks.json +26 -0
  31. package/plugins/librarian/scripts/hooks/librarian-session-end.sh +312 -0
  32. package/plugins/librarian/scripts/hooks/librarian-session-start.sh +103 -0
  33. package/plugins/librarian/scripts/lib/librarian-archivist-reader.sh +67 -0
  34. package/plugins/librarian/scripts/lib/librarian-classifier.sh +139 -0
  35. package/plugins/librarian/scripts/lib/librarian-config.sh +74 -0
  36. package/plugins/librarian/scripts/lib/librarian-durability.sh +77 -0
  37. package/plugins/librarian/scripts/lib/librarian-emit.sh +72 -0
  38. package/plugins/librarian/scripts/lib/librarian-project-key.sh +83 -0
  39. package/plugins/librarian/scripts/lib/librarian-storage.sh +222 -0
  40. package/plugins/librarian/scripts/lib/librarian-ulid.sh +50 -0
  41. package/release-please-config.json +32 -0
  42. package/test/bats/curator-session-start.bats +316 -0
  43. package/test/bats/librarian-session-end.bats +182 -0
  44. package/test/bats/librarian-session-start.bats +136 -0
@@ -124,6 +124,32 @@
124
124
  "license": "MIT",
125
125
  "keywords": ["security", "prompt-injection", "rule-of-two", "safety", "content-gate", "untrusted-content"],
126
126
  "tags": ["safety", "security"]
127
+ },
128
+ {
129
+ "name": "librarian",
130
+ "source": "./plugins/librarian",
131
+ "description": "Consolidation layer between archivist's per-session artifacts and the user's durable typed memory store. Reads archivist artifacts at SessionEnd, applies a durability filter, classifies survivors via Haiku into the four memory types (user, feedback, project, reference), and queues proposals for explicit confirmation via /librarian review. Auto-promotion is opt-in. Requires the ecosystem plugin.",
132
+ "author": {
133
+ "name": "Onlooker Community"
134
+ },
135
+ "homepage": "https://onlooker.dev",
136
+ "repository": "https://github.com/onlooker-community/ecosystem",
137
+ "license": "MIT",
138
+ "keywords": ["memory", "consolidation", "promotion", "classifier", "auto-memory", "session"],
139
+ "tags": ["memory", "context-engineering"]
140
+ },
141
+ {
142
+ "name": "curator",
143
+ "source": "./plugins/curator",
144
+ "description": "Maintenance layer for the typed auto-memory store. At every SessionStart, runs four cheap heuristic checks against the memories at ~/.claude/projects/<encoded>/memory/ — date_decayed (ISO-8601 dates past the grace period), path_broken (path-shaped references that don't resolve under the repo root), broken_index (MEMORY.md pointing at missing files), and orphaned_memory (files in the dir not referenced from MEMORY.md). Surfaces findings via /curator review; never edits the memory store directly. Parallel to cartographer (which audits hand-maintained instruction files), curator audits the auto-memory substrate. Requires the ecosystem plugin.",
145
+ "author": {
146
+ "name": "Onlooker Community"
147
+ },
148
+ "homepage": "https://onlooker.dev",
149
+ "repository": "https://github.com/onlooker-community/ecosystem",
150
+ "license": "MIT",
151
+ "keywords": ["memory", "audit", "staleness", "findings", "auto-memory", "decay"],
152
+ "tags": ["memory", "context-engineering"]
127
153
  }
128
154
  ]
129
155
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ecosystem",
3
- "version": "0.19.0",
3
+ "version": "0.21.0",
4
4
  "description": "Observability substrate for Claude Code. Provides the shared ~/.onlooker/ storage root, canonical schema-validated event emission, session and tool tracking hooks, and prompt rules. Required by all other Onlooker plugins.",
5
5
  "author": {
6
6
  "name": "Onlooker Community",
@@ -1,5 +1,5 @@
1
1
  {
2
- ".": "0.19.0",
2
+ ".": "0.21.0",
3
3
  "plugins/archivist": "0.1.0",
4
4
  "plugins/tribunal": "1.0.1",
5
5
  "plugins/echo": "0.2.0",
@@ -8,5 +8,7 @@
8
8
  "plugins/compass": "0.2.0",
9
9
  "plugins/scribe": "0.2.0",
10
10
  "plugins/counsel": "0.2.0",
11
- "plugins/warden": "0.2.0"
11
+ "plugins/warden": "0.2.0",
12
+ "plugins/librarian": "0.1.0",
13
+ "plugins/curator": "0.1.0"
12
14
  }
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.21.0](https://github.com/onlooker-community/ecosystem/compare/ecosystem-v0.20.0...ecosystem-v0.21.0) (2026-06-04)
4
+
5
+
6
+ ### Features
7
+
8
+ * **curator:** introduce plugin with cheap-tier checks :microscope: ([#57](https://github.com/onlooker-community/ecosystem/issues/57)) ([7f9fa18](https://github.com/onlooker-community/ecosystem/commit/7f9fa18bbde29c8b5bd1eaad185bd4c5595a3762))
9
+
10
+ ## [0.20.0](https://github.com/onlooker-community/ecosystem/compare/ecosystem-v0.19.0...ecosystem-v0.20.0) (2026-06-04)
11
+
12
+
13
+ ### Features
14
+
15
+ * **librarian:** land plugin end-to-end with memory layer designs :seedling: ([#55](https://github.com/onlooker-community/ecosystem/issues/55)) ([d4821ef](https://github.com/onlooker-community/ecosystem/commit/d4821efabfeb587e460e898d7db8f92fcc3f2c61))
16
+
3
17
  ## [0.19.0](https://github.com/onlooker-community/ecosystem/compare/ecosystem-v0.18.0...ecosystem-v0.19.0) (2026-06-02)
4
18
 
5
19
 
@@ -0,0 +1,102 @@
1
+ # Memory Architecture
2
+
3
+ This document describes how the Onlooker ecosystem manages memory across timescales — from the in-session working context, through compaction-resistant session memory, into durable cross-session knowledge, and back out as just-in-time recall.
4
+
5
+ It is the companion to [`architecture.md`](architecture.md), which describes the substrate. This doc describes how the memory-flavored plugins compose on top of it.
6
+
7
+ ---
8
+
9
+ ## The four timescales
10
+
11
+ | Timescale | Lifetime | Substrate | Plugin |
12
+ |-----------|----------|-----------|--------|
13
+ | Working | Within one model turn | Conversation context window | (the model itself) |
14
+ | Session | One Claude Code session, across compactions | `~/.onlooker/archivist/<project-key>/` | **archivist** |
15
+ | Durable | Across sessions, weeks–months | `~/.claude/projects/<encoded-project>/memory/` | **librarian**, **curator** |
16
+ | Episodic | Across sessions, retrieved on similarity | `~/.onlooker/historian/<project-key>/` | **historian** |
17
+
18
+ Each plugin operates on exactly one substrate. They communicate by events, not by reaching across substrates.
19
+
20
+ ---
21
+
22
+ ## The pipeline
23
+
24
+ ```mermaid
25
+ flowchart LR
26
+ session[Active session]
27
+ session -->|PreCompact| archivist[(archivist artifacts<br/>per session)]
28
+ archivist -->|SessionEnd| librarian{librarian:<br/>worth keeping?}
29
+ librarian -->|propose| memstore[(typed memory store<br/>user / feedback / project / reference)]
30
+ memstore -->|SessionStart audit| curator{curator:<br/>still valid?}
31
+ curator -->|prune proposals| memstore
32
+
33
+ session -->|SessionEnd| historian[(historian vector store<br/>transcript embeddings)]
34
+ historian -->|UserPromptSubmit similarity| session
35
+ memstore -->|SessionStart inject| session
36
+ archivist -->|SessionStart inject| session
37
+ ```
38
+
39
+ ### Flow of a fact
40
+
41
+ A non-obvious project fact ("the auth migration is driven by legal, not tech debt") goes through:
42
+
43
+ 1. **Captured during session** — surfaces in the conversation, lands in the transcript.
44
+ 2. **Extracted at compaction by archivist** — written as a `decisions/<ulid>.json` artifact when context fills.
45
+ 3. **Reinjected by archivist next session** — appears in `additionalContext` if it ranks within the recency/pinning budget. The fact survives session boundaries but lives in a per-session, recency-ranked space.
46
+ 4. **Promoted by librarian** — at session end (or on demand), librarian decides "this should be a `project` memory" and writes it to the typed memory store with provenance pointing back to the archivist artifact.
47
+ 5. **Audited by curator** — periodically, curator checks whether the **Why:** is still load-bearing (has the legal review concluded? did the migration land?). If stale, curator surfaces a prune proposal.
48
+ 6. **Recalled by historian (separately)** — if a future session encounters a similar problem ("we're being asked to rip out X middleware again"), historian retrieves the transcript chunk from the original session where this was discussed, providing fuller context than the distilled project memory can carry.
49
+
50
+ ---
51
+
52
+ ## Boundaries between memory plugins
53
+
54
+ The memory plugins overlap in spirit but operate on different substrates and answer different questions. Misunderstanding these boundaries is the most likely failure mode.
55
+
56
+ | Plugin | Question it answers | Storage | Trigger |
57
+ |--------|---------------------|---------|---------|
58
+ | archivist | "What did we decide / try / leave open in *this* session?" | `~/.onlooker/archivist/<project-key>/` | PreCompact, SessionStart |
59
+ | librarian | "Which of those decisions deserves to live forever?" | `~/.claude/projects/<encoded-project>/memory/` (writes proposals; user confirms) | SessionEnd, scheduled |
60
+ | curator | "Which of our durable memories are now wrong, stale, or contradictory?" | reads & proposes prunes against the typed memory store | SessionStart (cheap), scheduled (LLM) |
61
+ | historian | "Have we seen something like this before, and if so, what happened?" | `~/.onlooker/historian/<project-key>/` (embeddings) | SessionEnd, UserPromptSubmit |
62
+ | scribe | "What's a readable artifact of what we did?" | scribe's own output dir | session end |
63
+ | cartographer | "Are the instruction files (CLAUDE.md, AGENTS.md, rules/) internally consistent?" | reads instruction files; emits findings | SessionStart, instruction-file writes |
64
+ | counsel | "What does the *event log* across all plugins tell us to improve?" | reads JSONL log; produces brief | weekly |
65
+
66
+ **The two easiest confusions:**
67
+
68
+ 1. **cartographer vs. curator.** Cartographer audits the *instruction files* the user maintains by hand (CLAUDE.md, AGENTS.md, `.claude/rules/`). Curator audits the *typed auto-memory store* the librarian writes into. Same shape of audit, different substrate. They are parallel, not redundant.
69
+
70
+ 2. **archivist vs. librarian.** Archivist keeps everything from a session, ranked by recency, and reinjects into the next one. Librarian promotes a small subset into the typed memory store where it will be reinjected *every* session, with classification (user/feedback/project/reference) and provenance.
71
+
72
+ ---
73
+
74
+ ## Shared invariants
75
+
76
+ All three new memory plugins follow the ecosystem conventions:
77
+
78
+ - **Project keying** for plugin-owned storage (`~/.onlooker/<plugin>/<project-key>/`) uses the SHA256-of-remote-URL scheme from [`architecture.md`](architecture.md#project-keying), so cross-clone consistency is preserved for plugin state. Note: the typed memory store the librarian writes into is keyed by Claude Code's per-checkout path encoding (`~/.claude/projects/<encoded-project>/memory/`), which is a different scheme — see [librarian ADR-001](../plugins/librarian/docs/adr/001-propose-dont-auto-write.md) for how this asymmetry is handled.
79
+ - **No cross-plugin runtime dependencies.** Librarian degrades gracefully if archivist artifacts are absent (no proposals; emits `librarian.scan.skipped`). Curator degrades gracefully if the memory store is empty. Historian degrades gracefully if no past transcripts are indexed.
80
+ - **Event emission via `onlooker-event.mjs`.** Event types: `librarian.*`, `curator.*`, `historian.*`. Registered in `@onlooker-community/schema` before first emission.
81
+ - **Fail-soft on missing `~/.onlooker/`.** Plugins must not block a session they were not invited to.
82
+ - **Context-budget awareness.** Reinjection at `SessionStart` competes for the same budget archivist already uses. Each plugin's design lays out its budget cap explicitly; the ecosystem does not yet enforce a global ceiling. See [Open Questions](#open-questions) below.
83
+
84
+ ---
85
+
86
+ ## Open Questions
87
+
88
+ 1. **Global reinjection ceiling.** Archivist, librarian (via the typed memory store it writes into), historian, counsel, and cartographer all surface things at `SessionStart`. Each respects its own cap, but there is no shared budget. A `governor.context_budget.*` event series could attribute context spend per plugin and provide a soft global cap. Currently each plugin sets its own ceiling independently.
89
+
90
+ 2. **Memory store path scheme divergence.** Claude Code's typed memory store uses per-checkout path encoding; the ecosystem uses SHA256-of-remote-URL for cross-clone sharing. Librarian writes to the former (so the user sees promotions in the same place as their hand-written memories), but its own operational state (proposals queue, provenance log) lives at `~/.onlooker/librarian/<project-key>/` under the ecosystem scheme. This asymmetry is handled in [librarian ADR-001](../plugins/librarian/docs/adr/001-propose-dont-auto-write.md) but remains an awkwardness worth revisiting if Claude Code adopts a remote-derived key.
91
+
92
+ 3. **Promotion provenance after deletion.** When the user manually deletes a promoted memory, librarian should learn from that — at minimum, don't re-propose the same content. Sketched in librarian's design as the "tombstone" mechanism but not yet decided.
93
+
94
+ 4. **Historian and secrets.** Embedding entire transcripts means tokens, paths, and other sensitive strings that appeared in a past conversation become recallable. Deletion semantics (purge by session_id, by date range, by pattern) need to be defined before historian goes beyond a design sketch.
95
+
96
+ ---
97
+
98
+ ## Plugin design documents
99
+
100
+ - [Librarian](../plugins/librarian/docs/design.md) — consolidates archivist artifacts into the typed memory store
101
+ - [Curator](../plugins/curator/docs/design.md) — detects stale, contradictory, and decayed memories
102
+ - [Historian](../plugins/historian/docs/design.md) — episodic retrieval over past session transcripts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onlooker-community/ecosystem",
3
- "version": "0.19.0",
3
+ "version": "0.21.0",
4
4
  "description": "Agents, skills, hooks, commands, rules, and MCP configurations that power [Onlooker](https://onlooker.dev)",
5
5
  "author": {
6
6
  "name": "Onlooker Community",
@@ -19,14 +19,14 @@
19
19
  "onlooker-install": "install.sh"
20
20
  },
21
21
  "dependencies": {
22
- "@onlooker-community/schema": "^2.4.0"
22
+ "@onlooker-community/schema": "^2.5.0"
23
23
  },
24
24
  "scripts": {
25
25
  "postinstall": "echo '\\n onlooker-ecosystem installed!\\n Run: npx onlooker-install typescript\\n Docs: https://github.com/onlooker-community/ecosystem\\n'",
26
26
  "test": "npm run test:bats && npm run test:schema",
27
27
  "test:bats": "bats test/bats",
28
28
  "test:schema": "node --test test/node/*.test.mjs",
29
- "test:shellcheck": "shellcheck -S error -x install.sh scripts/common.sh scripts/hooks/*.sh scripts/lib/*.sh plugins/archivist/scripts/hooks/*.sh plugins/archivist/scripts/lib/*.sh plugins/tribunal/scripts/hooks/*.sh plugins/tribunal/scripts/lib/*.sh plugins/echo/scripts/hooks/*.sh plugins/echo/scripts/lib/*.sh plugins/governor/scripts/hooks/*.sh plugins/governor/scripts/lib/*.sh plugins/compass/scripts/hooks/*.sh plugins/compass/scripts/lib/*.sh plugins/scribe/scripts/hooks/*.sh plugins/scribe/scripts/lib/*.sh plugins/counsel/scripts/hooks/*.sh plugins/counsel/scripts/lib/*.sh plugins/warden/scripts/hooks/*.sh plugins/warden/scripts/lib/*.sh",
29
+ "test:shellcheck": "shellcheck -S error -x install.sh scripts/common.sh scripts/hooks/*.sh scripts/lib/*.sh plugins/archivist/scripts/hooks/*.sh plugins/archivist/scripts/lib/*.sh plugins/tribunal/scripts/hooks/*.sh plugins/tribunal/scripts/lib/*.sh plugins/echo/scripts/hooks/*.sh plugins/echo/scripts/lib/*.sh plugins/governor/scripts/hooks/*.sh plugins/governor/scripts/lib/*.sh plugins/compass/scripts/hooks/*.sh plugins/compass/scripts/lib/*.sh plugins/scribe/scripts/hooks/*.sh plugins/scribe/scripts/lib/*.sh plugins/counsel/scripts/hooks/*.sh plugins/counsel/scripts/lib/*.sh plugins/warden/scripts/hooks/*.sh plugins/warden/scripts/lib/*.sh plugins/librarian/scripts/hooks/*.sh plugins/librarian/scripts/lib/*.sh plugins/curator/scripts/hooks/*.sh plugins/curator/scripts/lib/*.sh",
30
30
  "lint:references": "node scripts/lint/check-references.mjs",
31
31
  "lint:manifests": "node scripts/lint/check-manifests.mjs",
32
32
  "coverage:node": "node scripts/coverage/run-coverage.mjs",
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "curator",
3
+ "version": "0.1.0",
4
+ "description": "Maintenance layer for the user's typed auto-memory store. At every SessionStart, runs four cheap heuristic checks (date_decayed, path_broken, broken_index, orphaned_memory) against the memories at ~/.claude/projects/<encoded>/memory/ inside a wall-clock budget. Surfaces findings as a one-line pointer to /curator review; never edits the memory store directly. Builds on the Onlooker ecosystem plugin.",
5
+ "author": {
6
+ "name": "Onlooker Community",
7
+ "url": "https://onlooker.dev"
8
+ },
9
+ "homepage": "https://onlooker.dev",
10
+ "repository": "https://github.com/onlooker-community/ecosystem",
11
+ "license": "MIT",
12
+ "skills": [],
13
+ "agents": []
14
+ }
@@ -0,0 +1,10 @@
1
+ # Changelog
2
+
3
+ ## [0.1.0](https://github.com/onlooker-community/ecosystem/compare/curator-v0.0.1...curator-v0.1.0) (2026-06-04)
4
+
5
+
6
+ ### Features
7
+
8
+ * **curator:** introduce plugin with cheap-tier checks :microscope: ([#57](https://github.com/onlooker-community/ecosystem/issues/57)) ([7f9fa18](https://github.com/onlooker-community/ecosystem/commit/7f9fa18bbde29c8b5bd1eaad185bd4c5595a3762))
9
+
10
+ ## Changelog
@@ -0,0 +1,55 @@
1
+ # Curator
2
+
3
+ Maintenance layer for the user's typed auto-memory store.
4
+
5
+ At every `SessionStart`, Curator runs cheap heuristic checks against the memories at `~/.claude/projects/<encoded-project>/memory/` — stale ISO-8601 dates past the grace period, broken path references, broken `MEMORY.md` index entries, and orphaned memory files — and surfaces findings as a one-line pointer to `/curator review`. Curator never edits the memory store directly.
6
+
7
+ The weekly LLM-backed contradiction sweep described in [`docs/design.md`](docs/design.md) is a future capability — see the **Status** section below for what's actually shipped today.
8
+
9
+ Curator is a sibling plugin to [`ecosystem`](../../) and assumes the Onlooker observability substrate (`~/.onlooker/`) is present. It is parallel to [`cartographer`](../cartographer) (which audits hand-maintained instruction files like `CLAUDE.md`) — same audit shape, different substrate.
10
+
11
+ ## How it works
12
+
13
+ | Hook | What Curator does |
14
+ |------|---------------------|
15
+ | `SessionStart` | Runs the four cheap-tier checks (date_decayed, path_broken, broken_index, orphaned_memory) against the memory store, inside a wall-clock budget (`cheap_checks.wall_clock_budget_ms`, default 500ms). Writes new findings under `~/.onlooker/curator/<project-key>/findings/` keyed by ULID, deduping repeat findings via `deduped_hash`. Injects a one-line `additionalContext` pointer when open findings exist. |
16
+
17
+ ## Activation
18
+
19
+ Curator is **off by default**. Enable per-project in `.claude/settings.json`:
20
+
21
+ ```json
22
+ {
23
+ "curator": {
24
+ "enabled": true
25
+ }
26
+ }
27
+ ```
28
+
29
+ Or globally in `~/.claude/settings.json`. See [`config.json`](config.json) for the full set of tunable defaults.
30
+
31
+ ## Storage layout
32
+
33
+ ```text
34
+ ~/.onlooker/curator/<project-key>/
35
+ ├── manifest.json # project metadata
36
+ ├── last_llm_sweep.json # watermark for the weekly LLM pass
37
+ ├── last_cheap_scan.json # watermark for the per-session check
38
+ └── findings/<ulid>.json # one finding per file
39
+ ```
40
+
41
+ ## Status
42
+
43
+ This plugin ships **scaffolding + four cheap-tier checks (date_decayed, path_broken, broken_index, orphaned_memory) + SessionStart surfacer**. Deferred to follow-up landings:
44
+
45
+ - **LLM contradiction sweep** — design and the watermark plumbing (`last_llm_sweep.json`) are in place; the Haiku pair-evaluation loop is not implemented. `llm_sweep.enabled` defaults to `false` and is a no-op until the sweep ships.
46
+ - **`/curator review` interactive walkthrough** — accept / prune / edit / reclassify / acknowledge / defer for surfaced findings.
47
+ - **Usage tracker** (zero-recall-window findings) — depends on a substrate-level `memory.recalled` emitter that doesn't exist yet. `usage_tracker.enabled` defaults to `false`; see [`docs/design.md`](docs/design.md) Open Question #1.
48
+ - **Symbol reference check** — backtick-wrapped identifiers grep'd against the repo. Not yet wired.
49
+
50
+ ## Requirements
51
+
52
+ - The `ecosystem` plugin installed (for `~/.onlooker/` substrate).
53
+ - `jq` for JSON manipulation.
54
+ - `python3` for date math and path resolution.
55
+ - `git` to resolve the project key and (for the reference check) the repo root.
@@ -0,0 +1,41 @@
1
+ {
2
+ "plugin_name": "curator",
3
+ "storage_path": "~/.onlooker",
4
+ "curator": {
5
+ "enabled": false,
6
+ "memory_store_path": "${HOME}/.claude/projects/${CLAUDE_PROJECT_ENCODED}/memory",
7
+ "cheap_checks": {
8
+ "enabled": true,
9
+ "wall_clock_budget_ms": 500,
10
+ "skip_if_session_age_under_seconds": 5
11
+ },
12
+ "date_check": {
13
+ "enabled": true,
14
+ "date_grace_period_days": 14
15
+ },
16
+ "reference_check": {
17
+ "enabled": true,
18
+ "check_urls": false,
19
+ "url_allowlist": []
20
+ },
21
+ "usage_tracker": {
22
+ "enabled": false,
23
+ "usage_window_days": 30,
24
+ "_note": "Disabled until the substrate memory.recalled emitter ships. Setting to true today is a no-op."
25
+ },
26
+ "llm_sweep": {
27
+ "enabled": false,
28
+ "_note": "Disabled until the Haiku pair-evaluation loop ships. Setting to true today is a no-op.",
29
+ "model": "claude-haiku-4-5-20251001",
30
+ "temperature": 0.2,
31
+ "max_output_tokens": 96,
32
+ "interval_days": 7,
33
+ "max_pair_evaluations_per_sweep": 50,
34
+ "contradiction_similarity_threshold": 0.4
35
+ },
36
+ "surfacer": {
37
+ "max_pointer_chars": 200,
38
+ "skip_when_zero": true
39
+ }
40
+ }
41
+ }
@@ -0,0 +1,100 @@
1
+ # ADR-001: Two-Tier Staleness Checks — Cheap Every Session, LLM Weekly
2
+
3
+ - Status: Accepted
4
+ - Date: 2026-06-02
5
+ - Deciders: Meagan
6
+ - Tags: curator, memory, performance, rate-limiting
7
+
8
+ ## Context and Problem Statement
9
+
10
+ Curator audits the typed memory store for staleness, broken references, contradictions, and decayed dates. The natural place to surface findings is at `SessionStart` — that's when the user is paged in for the day's work and most receptive to a one-line "Curator: 2 findings to review" pointer.
11
+
12
+ But `SessionStart` fires on every session, including every restart after compaction and every new branch checkout. Whatever curator runs at `SessionStart` runs *a lot*. Two pressures push against each other:
13
+
14
+ - The checks need to be cheap enough not to delay session startup or accumulate hidden cost.
15
+ - The most valuable check (contradiction detection between memory pairs) requires an LLM call per candidate pair, and the candidate set grows with the memory store.
16
+
17
+ A single check tier — either "all checks every session" or "all checks on a manual trigger" — fails the gradient. The first is too expensive at scale; the second is too easy to forget, and findings rot quietly.
18
+
19
+ ## Decision Drivers
20
+
21
+ - **`SessionStart` is a hot path.** Cartographer's design explicitly documents that audits run as a detached background process to avoid blocking sessions. Curator inherits the same constraint.
22
+ - **Cheap checks are nearly free.** Date pattern matching, file-exists checks on path references, and `rg` calls for symbol references all sit in the millisecond range. They can run every session inside a wall-clock budget without observable latency.
23
+ - **LLM contradiction checks scale poorly.** Pairwise comparison is O(N²) on the candidate set even after similarity-filtering. A 100-memory store today may have ~10 candidate pairs; a 500-memory store has more. Running this every session is paying a recurring cost that grows with the user's investment in the system — the worst kind of cost curve.
24
+ - **The signal decays slowly.** A contradiction between two memories does not become more or less true between Monday and Tuesday. Weekly cadence captures real changes (new memories, edited memories) without burning compute on a static problem.
25
+ - **User-pull beats system-push for big sweeps.** A manual `/curator scan` skill exists for users who want to force a full sweep — that's the right surface for "I just landed a big refactor; re-check everything."
26
+ - **Cartographer precedent.** Cartographer runs a periodic background audit and surfaces findings as events. Curator's tiered cadence is the moral equivalent: cheap checks are the "always-on" surface, the LLM sweep is the "periodic" surface.
27
+
28
+ ## Considered Options
29
+
30
+ 1. **One tier: all checks every session.** Simple, predictable, expensive at scale. Wall-clock budget acts as a ceiling but degrades coverage as the store grows.
31
+ 2. **One tier: all checks on manual trigger only.** Cheap and predictable but loses the always-on signal users want from `SessionStart`. Findings rot.
32
+ 3. **Two tiers: cheap checks every session, LLM sweep at most once per N days.** Preserves the always-on cheap signal and amortizes the expensive sweep over time.
33
+ 4. **Three tiers: cheap every session, mid-cost (e.g., rg-based symbol re-check) daily, LLM weekly.** Finer granularity. Adds operational complexity (multiple watermarks, multiple skip-reason cases) without an obvious payoff at current memory-store sizes.
34
+ 5. **Adaptive cadence.** Run the LLM sweep when the memory store has changed by more than X% since last sweep. Conceptually elegant but introduces a change-detection layer that itself needs validation.
35
+
36
+ ## Decision
37
+
38
+ We adopt **Option 3: two tiers with a watermark-gated LLM sweep at most once per `llm_sweep_interval_days` (default: 7)**.
39
+
40
+ The cheap tier runs on every `SessionStart` inside a `wall_clock_budget_ms` budget (default: 500ms). It performs:
41
+
42
+ - Date pattern parsing and grace-period checks.
43
+ - Path reference existence checks.
44
+ - Symbol reference `rg` checks (capped at `max_symbol_checks_per_session`).
45
+ - Usage tracker reads from the JSONL log (when the `memory.recalled` emitter ships).
46
+
47
+ If the cheap tier runs over budget, curator emits `curator.scan.skipped` with `reason: "over_budget"` and exits without partial results. The wall-clock budget exists to make budget overruns visible as events rather than as silent slowdowns.
48
+
49
+ The LLM tier runs only when `now - last_llm_sweep_at >= llm_sweep_interval_days`. The watermark lives at `~/.onlooker/curator/<project-key>/last_llm_sweep.json`. When the gate opens:
50
+
51
+ - Pairwise Jaccard similarity is computed across all memories.
52
+ - Pairs above `contradiction_similarity_threshold` (default: 0.4) with opposing sentiment markers proceed to LLM evaluation.
53
+ - Up to `max_pair_evaluations_per_sweep` (default: 50) calls are made, watermark advances regardless of whether the cap is hit.
54
+
55
+ The manual `/curator scan` skill bypasses both rate gates. Useful for "I just landed N memories; re-check now."
56
+
57
+ Option 4 was rejected because the user-visible benefit of a third tier is unclear at current memory-store sizes (typically tens of entries, not hundreds). It can be added later without changing the architecture if scale changes the calculus.
58
+
59
+ Option 5 was rejected as a default because change-detection introduces a calibration problem (what counts as significant change?) on top of the staleness-detection problem. It is plausible as a future optimization layered on Option 3 (e.g., advance the LLM watermark when the memory store has changed by less than a threshold, to defer the next sweep).
60
+
61
+ ## Consequences
62
+
63
+ ### Positive
64
+
65
+ - `SessionStart` latency stays bounded by the cheap-check wall-clock budget — typically well under 500ms.
66
+ - The expensive LLM sweep runs at a cadence that matches the rate-of-change of the underlying signal (memories don't contradict each other on a per-minute basis).
67
+ - A manual override (`/curator scan`) gives users the always-available "scan now" surface without making it the default.
68
+ - Budget overruns are observable: `curator.scan.skipped` with `reason: "over_budget"` is a leading indicator that the memory store has grown enough to need tuning.
69
+ - The watermark-gated sweep also caps cost: a user with a single high-traffic project pays at most N LLM-sweep calls per `llm_sweep_interval_days`.
70
+
71
+ ### Negative
72
+
73
+ - A contradiction introduced today may go up to 7 days before being flagged. This is the cost of weekly cadence. Mitigation: the manual `/curator scan` exists; users who notice they just added two conflicting memories can force a check.
74
+ - The two-tier model is slightly more complex than one tier. Two watermarks (cheap-tier last-run, LLM-tier last-sweep), two budget knobs, two skip reasons. The added complexity is small relative to the cost savings.
75
+ - The cheap tier's wall-clock budget interacts badly with very large memory stores. At ≥200 memories, the cheap tier may itself need a per-memory cap (e.g., "scan only memories touched in the last N days"). Not yet a problem; flagged as a future open question.
76
+
77
+ ### Neutral
78
+
79
+ - The choice of 7 days for `llm_sweep_interval_days` is a guess. Users who care can override. A future calibration could measure how often pair-similarity-with-opposing-sentiment results change verdict between consecutive sweeps; if the rate is low, the interval could grow.
80
+
81
+ ## Implementation Notes
82
+
83
+ - The cheap-tier wall-clock budget is checked between sub-tasks, not within them. A single `rg` call that itself runs over budget is allowed to finish; the gate prevents *the next* sub-task from starting.
84
+ - The LLM watermark is updated *before* the sweep begins, not after. This prevents a sweep that crashes midway from being retried immediately on the next `SessionStart`. The downside: a crashed sweep delays the next full check by the interval. Acceptable — better than a crash-retry loop.
85
+ - Findings dedup uses `deduped_hash`. For `contradiction` findings the hash includes both memory bodies' content hashes, so an edit to either body re-opens the finding for re-evaluation on the next sweep.
86
+ - Manual `/curator scan` updates the watermark like an automatic sweep — a user who runs the skill on Monday and Tuesday gets two LLM sweeps in two days, which is fine because they explicitly asked for it.
87
+ - The cheap-tier and LLM-tier are independently configurable via `cheap_checks.enabled` and `llm_sweep.enabled`. Users can run either tier alone (e.g., LLM sweep off in a budget-sensitive project).
88
+
89
+ ## Validation
90
+
91
+ - A memory store of ~50 entries should produce cheap-check sweeps under 200ms on a typical macOS dev laptop. If `curator.scan.skipped` with `reason: "over_budget"` fires at this size, the wall-clock budget needs tuning, not the design.
92
+ - An LLM sweep at ~50 memories should make ≤20 pair-evaluation calls in the common case. The `max_pair_evaluations_per_sweep` cap of 50 should not be reached. If it is reached regularly, similarity threshold and sentiment-marker filtering need tuning.
93
+ - Findings open for more than two LLM-sweep intervals without user action should produce a `curator.finding.aged_unhandled` summary event — an integration point for counsel's weekly brief.
94
+
95
+ ## References
96
+
97
+ - Cartographer design — precedent for background audits with per-finding events (`plugins/cartographer/docs/design.md`)
98
+ - Compass ADR-001 — precedent for explicit budget gates and skip-reason events (`plugins/compass/docs/adr/001-evaluate-prompts-in-context.md`)
99
+ - Memory architecture overview (`docs/memory-architecture.md`)
100
+ - Curator design (`../design.md`)