feat(memory): ICM slices 1-3 — memoirs + feedback + importance/decay#580
Merged
Conversation
added 3 commits
May 18, 2026 11:25
Adds importance/access_count/last_accessed_at/weight columns on observations and threads them through the read+write path: - New importance tier per observation (default medium). critical/high pin their weight to baseWeight; medium/low decay as baseWeight / (1 + access_count * 0.1) when read. - Storage.recordAccess increments access_count and recomputes weight in a single transactional UPDATE. - Storage.pruneLowDecay (and matching colonyq memory prune --min-weight / --dry-run) removes near-zero-weight medium/low rows; critical/high are never affected. - search and get_observations MCP responses include importance + weight (additive — older callers ignore them). - task_post MCP tool accepts optional importance. Write-amplification strategy: (b) debounced 50ms batch. The MemoryStore read paths queue returned ids into a Set, start a single setTimeout, and flush one transactional UPDATE. This guarantees every access is counted (unlike threshold-sampling) while keeping write amplification at most one extra UPDATE per ~50ms hot loop and making `close()` deterministic by flushing pending bookkeeping before the SQLite handle goes away. Schema bump 14 -> 15. Migration is forward-only via COLUMN_MIGRATIONS so existing DBs upgrade in place.
Adds the cross-agent "AI predicted X, real answer was Y" feedback lane specified in docs/icm-integration-plan.md slice 2 so a future agent can search prior corrections by topic before repeating a known mistake. - packages/storage migration 015 introduces `feedback` + a porter- unicode61 `feedback_fts` virtual table with the standard ai/ad/au triggers, plus indexes on `topic` and `created_at`. Importance is a four-level CHECK enum defaulting to `medium`. - packages/core MemoryStore exposes `recordFeedback`, `searchFeedback`, `getFeedback`, and `feedbackStats`. All three prose fields (prediction/correction/context) route through `prepareMemoryText`, the same redact-then-compress path observations use, so the compression invariant holds at the write boundary. - apps/mcp-server registers `feedback_record`, `feedback_search`, and `feedback_stats` with progressive-disclosure return shapes (id, topic, importance, FTS5 score, snippet, created_at). docs/mcp.md carries the contract. Follow-up out of scope here: a pre-tool-use hook that auto-surfaces prior corrections on inbound prompts. Tracking separately so this slice can ship behind a manual query surface first.
Brings the slice 1 memoirs work (originally drafted on the local main
checkout) onto agent/claude/icm-slice-3-importance-decay so all three
ICM slices ship as one integrated branch:
- 015-icm-memoirs.ts — typed knowledge graphs (slice 1)
- 016-icm-feedback.ts — feedback record/search/stats (slice 2, was 015)
- 017-icm-importance-decay.ts — importance + decay (slice 3, was 015)
Schema bump 14 → 17. SCHEMA_SQL keeps every ICM table as
`CREATE TABLE IF NOT EXISTS` (fresh DBs) and existing DBs continue to
pick up slice 3 columns via COLUMN_MIGRATIONS. The migration files are
canonical references (the runtime path stays SCHEMA_SQL + COLUMN_MIGRATIONS).
Memoirs:
- `memoirs`, `memoir_concepts`, `memoir_relations` tables; FTS5 mirror
over name/content/labels with the standard `(ai, ad, au)` triggers.
- Nine relation types (part_of/depends_on/related_to/contradicts/refines/
alternative_to/caused_by/instance_of/superseded_by). Self-loops
forbidden; `(source, target, relation)` is unique.
- `MemoryStore.{createMemoir, addConcept, refineConcept, linkConcepts,
searchConcepts, inspectConcept, listMemoirs}` — content flows through
`prepareMemoryText` so the compression invariant matches observations.
- Seven MCP tools (`memoir_create / list / add_concept / refine / link /
search / inspect`) — progressive disclosure: `memoir_search` returns
compact hits, `memoir_inspect` returns body + BFS neighbourhood.
Verified:
- pnpm typecheck — green across all 17 workspaces.
- pnpm --filter @colony/storage test — 193 passed, 1 skipped (perf,
via COLONY_SKIP_PERF=1; flaky on local disk per test comment).
- pnpm --filter @colony/mcp-server test — 302 passed.
- pnpm --filter @colony/core test — 259 passed; 1 pre-existing failure
in test/savings-reference.test.ts (also fails on 2d831bb baseline,
unrelated to ICM).
…e-3-importance-decay # Conflicts: # docs/mcp.md # packages/core/src/memory-store.ts # packages/storage/src/schema.ts # packages/storage/src/storage.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Unifies the ICM integration on one branch. The three slices from
docs/icm-integration-plan.md(memoirs, feedback, importance + temporal decay) were drafted in parallel, all targeting migration 015 — this PR lands them in canonical order on a single, testable branch.Slice 1 — memoirs (typed knowledge graphs)
memoirs,memoir_concepts,memoir_relations; FTS5 mirror over name/content/labels with the standard(ai, ad, au)triggers.part_of,depends_on,related_to,contradicts,refines,alternative_to,caused_by,instance_of,superseded_by); self-loops forbidden;(source, target, relation)unique.MemoryStore.{createMemoir, addConcept, refineConcept, linkConcepts, searchConcepts, inspectConcept, listMemoirs}— concept content routes throughprepareMemoryText(same redact → compress pipeline as observations).memoir_create / list / add_concept / refine / link / search / inspect) with progressive disclosure:memoir_searchreturns compact hits;memoir_inspectreturns body + BFS neighbourhood.Slice 2 — feedback record/search/stats
feedback(topic, prediction, correction, optional context, importance, created_by) +feedback_ftsvirtual table.MemoryStore.recordFeedback / searchFeedback / getFeedback / feedbackStats— prediction/correction/context bodies pass throughprepareMemoryText, so the compression invariant holds at the write boundary.feedback_record / feedback_search / feedback_statswith compact-hit progressive disclosure.Slice 3 — observation importance + temporal decay
observations:importance(critical | high | medium | low, defaultmedium),access_count,last_accessed_at,weight.Storage.recordAccess(ids): single transactional UPDATE that incrementsaccess_count, stampslast_accessed_at, and recomputesweight. Critical/high pin tobaseWeight(importance); medium/low decay asbaseWeight / (1 + access_count * 0.1).Storage.pruneLowDecay({ minWeight })+Storage.countLowDecayCandidates: delete / count medium-or-low rows below the threshold. Critical/high are never affected.search,semantic_search,get_observationscarryimportance+weightin each row (additive).task_postaccepts optionalimportance.colony memory prune [--min-weight <n>] [--dry-run].Migration ordering
015-icm-memoirs.ts016-icm-feedback.ts017-icm-importance-decay.tsSCHEMA_SQLkeeps every ICM table asCREATE TABLE IF NOT EXISTS(fresh DBs); existing DBs pick up slice 3's column additions viaCOLUMN_MIGRATIONS. The numbered migration files remain canonical references — the runtime path staysSCHEMA_SQL + COLUMN_MIGRATIONS.Write-amplification strategy (slice 3)
Debounced 50ms batch via
setTimeout: read paths queue returned ids into aSet, the first queue kicks asetTimeout, and the timer flushes the coalesced set in one transactional UPDATE. Every access is counted (unlike threshold sampling), write amplification is capped at one UPDATE per ~50ms hot window, andMemoryStore.close()deterministically flushes pending ids.unref()keeps the timer from holding Node alive. Readonly stores skip the buffer entirely.Verification
Includes the slice 3 perf-smoke receipt that was already in the original PR body:
Reference
docs/icm-integration-plan.md🤖 Generated with Claude Code