feat(skill-memory): per-skill cross-session recall + historian auto-extraction#181
feat(skill-memory): per-skill cross-session recall + historian auto-extraction#181iceteaSA wants to merge 12 commits into
Conversation
dc83db6 to
4034019
Compare
There was a problem hiding this comment.
11 issues found across 78 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/plugin/src/features/magic-context/dreamer/task-executor.ts">
<violation number="1" location="packages/plugin/src/features/magic-context/dreamer/task-executor.ts:419">
P2: Re-embed pre-step errors are suppressed, allowing successful task completion reporting despite a failed prerequisite data maintenance step.</violation>
</file>
Note: This PR contains a large number of files. cubic only reviews up to 40 files per PR, so some files may not have been reviewed. cubic prioritizes the most important files to review.
On a pro plan you can use ultrareview for larger PRs.
Re-trigger cubic
There was a problem hiding this comment.
4 issues found across 15 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/plugin/src/features/magic-context/dreamer/task-executor.ts">
<violation number="1" location="packages/plugin/src/features/magic-context/dreamer/task-executor.ts:419">
P2: Re-embed pre-step errors are suppressed, allowing successful task completion reporting despite a failed prerequisite data maintenance step.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
…routing/parser gaps) Council review (deepseek/sonnet/gpt-5.5) of PR cortexkit#181: - Must (consensus rev-2+rev-3): the ctx_skill_note fail-loud guard threw during plugin init when the plugin is disabled (enabled:false OR conflict-disabled) — createSessionHooks returns {magicContext:null} by design, so the unconditional guard crashed the entry module on the disabled path. Gate it on pluginConfig.enabled; pass a throwaway Map to createToolRegistry (which early-returns {} when disabled and never reads it). - Must (rev-3): singular ~/.config/opencode/skill/ global path was misclassified as project tier (opencode's pattern is {skill,skills}/**/SKILL.md) — fixed in deriveSkillTier/deriveSkillSource + the ctx_skill_recall cold-start search list. - Must (rev-3): the frontmatter parser rejected the inline flow-mapping form 'skill-memory: { enabled: true }' — the EXACT form the ctx_skill_recall remediation message and ARCHITECTURE/CONFIGURATION/README advertise. Added inline-mapping parsing so guidance and parser agree. - Should (consensus rev-1+rev-3): recallSkillMemoryBlock swallowed all errors silently — added a log() so FTS/blob corruption is diagnosable (still no-throw). Regression tests: inline frontmatter form (3 cases), singular skill/ global path (2 cases).
…routing/parser gaps) Council review (deepseek/sonnet/gpt-5.5) of PR cortexkit#181: - Must (consensus rev-2+rev-3): the ctx_skill_note fail-loud guard threw during plugin init when the plugin is disabled (enabled:false OR conflict-disabled) — createSessionHooks returns {magicContext:null} by design, so the unconditional guard crashed the entry module on the disabled path. Gate it on pluginConfig.enabled; pass a throwaway Map to createToolRegistry (which early-returns {} when disabled and never reads it). - Must (rev-3): singular ~/.config/opencode/skill/ global path was misclassified as project tier (opencode's pattern is {skill,skills}/**/SKILL.md) — fixed in deriveSkillTier/deriveSkillSource + the ctx_skill_recall cold-start search list. - Must (rev-3): the frontmatter parser rejected the inline flow-mapping form 'skill-memory: { enabled: true }' — the EXACT form the ctx_skill_recall remediation message and ARCHITECTURE/CONFIGURATION/README advertise. Added inline-mapping parsing so guidance and parser agree. - Should (consensus rev-1+rev-3): recallSkillMemoryBlock swallowed all errors silently — added a log() so FTS/blob corruption is diagnosable (still no-throw). Regression tests: inline frontmatter form (3 cases), singular skill/ global path (2 cases).
8f87530 to
00f22d2
Compare
…routing/parser gaps) Council review (deepseek/sonnet/gpt-5.5) of PR cortexkit#181: - Must (consensus rev-2+rev-3): the ctx_skill_note fail-loud guard threw during plugin init when the plugin is disabled (enabled:false OR conflict-disabled) — createSessionHooks returns {magicContext:null} by design, so the unconditional guard crashed the entry module on the disabled path. Gate it on pluginConfig.enabled; pass a throwaway Map to createToolRegistry (which early-returns {} when disabled and never reads it). - Must (rev-3): singular ~/.config/opencode/skill/ global path was misclassified as project tier (opencode's pattern is {skill,skills}/**/SKILL.md) — fixed in deriveSkillTier/deriveSkillSource + the ctx_skill_recall cold-start search list. - Must (rev-3): the frontmatter parser rejected the inline flow-mapping form 'skill-memory: { enabled: true }' — the EXACT form the ctx_skill_recall remediation message and ARCHITECTURE/CONFIGURATION/README advertise. Added inline-mapping parsing so guidance and parser agree. - Should (consensus rev-1+rev-3): recallSkillMemoryBlock swallowed all errors silently — added a log() so FTS/blob corruption is diagnosable (still no-throw). Regression tests: inline frontmatter form (3 cases), singular skill/ global path (2 cases).
00f22d2 to
8db1b8d
Compare
There was a problem hiding this comment.
4 issues found across 78 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/plugin/src/features/magic-context/dreamer/task-executor.ts">
<violation number="1" location="packages/plugin/src/features/magic-context/dreamer/task-executor.ts:419">
P2: Re-embed pre-step errors are suppressed, allowing successful task completion reporting despite a failed prerequisite data maintenance step.</violation>
</file>
Note: This PR contains a large number of files. cubic only reviews up to 40 files per PR, so some files may not have been reviewed. cubic prioritizes the most important files to review.
On a pro plan you can use ultrareview for larger PRs.
Re-trigger cubic
…routing/parser gaps) Council review (deepseek/sonnet/gpt-5.5) of PR cortexkit#181: - Must (consensus rev-2+rev-3): the ctx_skill_note fail-loud guard threw during plugin init when the plugin is disabled (enabled:false OR conflict-disabled) — createSessionHooks returns {magicContext:null} by design, so the unconditional guard crashed the entry module on the disabled path. Gate it on pluginConfig.enabled; pass a throwaway Map to createToolRegistry (which early-returns {} when disabled and never reads it). - Must (rev-3): singular ~/.config/opencode/skill/ global path was misclassified as project tier (opencode's pattern is {skill,skills}/**/SKILL.md) — fixed in deriveSkillTier/deriveSkillSource + the ctx_skill_recall cold-start search list. - Must (rev-3): the frontmatter parser rejected the inline flow-mapping form 'skill-memory: { enabled: true }' — the EXACT form the ctx_skill_recall remediation message and ARCHITECTURE/CONFIGURATION/README advertise. Added inline-mapping parsing so guidance and parser agree. - Should (consensus rev-1+rev-3): recallSkillMemoryBlock swallowed all errors silently — added a log() so FTS/blob corruption is diagnosable (still no-throw). Regression tests: inline frontmatter form (3 cases), singular skill/ global path (2 cases).
041126d to
ef5cba1
Compare
…routing/parser gaps) Council review (deepseek/sonnet/gpt-5.5) of PR cortexkit#181: - Must (consensus rev-2+rev-3): the ctx_skill_note fail-loud guard threw during plugin init when the plugin is disabled (enabled:false OR conflict-disabled) — createSessionHooks returns {magicContext:null} by design, so the unconditional guard crashed the entry module on the disabled path. Gate it on pluginConfig.enabled; pass a throwaway Map to createToolRegistry (which early-returns {} when disabled and never reads it). - Must (rev-3): singular ~/.config/opencode/skill/ global path was misclassified as project tier (opencode's pattern is {skill,skills}/**/SKILL.md) — fixed in deriveSkillTier/deriveSkillSource + the ctx_skill_recall cold-start search list. - Must (rev-3): the frontmatter parser rejected the inline flow-mapping form 'skill-memory: { enabled: true }' — the EXACT form the ctx_skill_recall remediation message and ARCHITECTURE/CONFIGURATION/README advertise. Added inline-mapping parsing so guidance and parser agree. - Should (consensus rev-1+rev-3): recallSkillMemoryBlock swallowed all errors silently — added a log() so FTS/blob corruption is diagnosable (still no-throw). Regression tests: inline frontmatter form (3 cases), singular skill/ global path (2 cases).
9c62039 to
3e28417
Compare
…routing/parser gaps) Council review (deepseek/sonnet/gpt-5.5) of PR cortexkit#181: - Must (consensus rev-2+rev-3): the ctx_skill_note fail-loud guard threw during plugin init when the plugin is disabled (enabled:false OR conflict-disabled) — createSessionHooks returns {magicContext:null} by design, so the unconditional guard crashed the entry module on the disabled path. Gate it on pluginConfig.enabled; pass a throwaway Map to createToolRegistry (which early-returns {} when disabled and never reads it). - Must (rev-3): singular ~/.config/opencode/skill/ global path was misclassified as project tier (opencode's pattern is {skill,skills}/**/SKILL.md) — fixed in deriveSkillTier/deriveSkillSource + the ctx_skill_recall cold-start search list. - Must (rev-3): the frontmatter parser rejected the inline flow-mapping form 'skill-memory: { enabled: true }' — the EXACT form the ctx_skill_recall remediation message and ARCHITECTURE/CONFIGURATION/README advertise. Added inline-mapping parsing so guidance and parser agree. - Should (consensus rev-1+rev-3): recallSkillMemoryBlock swallowed all errors silently — added a log() so FTS/blob corruption is diagnosable (still no-throw). Regression tests: inline frontmatter form (3 cases), singular skill/ global path (2 cases).
…d in frontmatter Counterpart to ctx_skill_recall's enabled-guard (greptile review, PR cortexkit#181): without it, notes for skills that never opted in inserted successfully but were permanently orphaned — recallSkillMemoryBlock returns "" when frontmatter is disabled, while the agent saw a convincing 'Skill note saved' response. Now returns an actionable error before any insert. Red-checked: new regression test fails without the guard (orphan row inserted + 'saved' response), passes with it (no row, 'not enabled').
…d in frontmatter Counterpart to ctx_skill_recall's enabled-guard (greptile review, PR cortexkit#181): without it, notes for skills that never opted in inserted successfully but were permanently orphaned — recallSkillMemoryBlock returns "" when frontmatter is disabled, while the agent saw a convincing 'Skill note saved' response. Now returns an actionable error before any insert. Red-checked: new regression test fails without the guard (orphan row inserted + 'saved' response), passes with it (no row, 'not enabled').
There was a problem hiding this comment.
1 issue found across 2 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/plugin/src/features/magic-context/skill-memory/provenance.test.ts">
<violation number="1" location="packages/plugin/src/features/magic-context/skill-memory/provenance.test.ts:107">
P2: The test "parses a PLAIN filesystem path for a PROJECT skill" verifies that the function returns the correct `tier` and `skillSource`, but it doesn't verify the `resolvedPath` output. Since this is the only test exercising plain-path resolution for a project-level skill, a regression in the path-joining logic — like a missing `/SKILL.md` suffix, incorrect directory concatenation, or broken normalization — would go undetected. The adjacent test for global plain paths (and six other tests covering file:// paths) all assert `resolvedPath`. Consider adding an assertion like `expect(result!.resolvedPath).toBe("${HOME}/projects/foo/.opencode/skills/my-skill/SKILL.md")` to match the coverage pattern of the rest of the test suite.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
| test("parses a PLAIN filesystem path for a PROJECT skill", () => { | ||
| const output = `Base directory for this skill: ${HOME}/projects/foo/.opencode/skills/my-skill`; | ||
| const result = parseSkillProvenance(output, "my-skill"); | ||
| expect(result!.tier).toBe("project"); |
There was a problem hiding this comment.
P2: The test "parses a PLAIN filesystem path for a PROJECT skill" verifies that the function returns the correct tier and skillSource, but it doesn't verify the resolvedPath output. Since this is the only test exercising plain-path resolution for a project-level skill, a regression in the path-joining logic — like a missing /SKILL.md suffix, incorrect directory concatenation, or broken normalization — would go undetected. The adjacent test for global plain paths (and six other tests covering file:// paths) all assert resolvedPath. Consider adding an assertion like expect(result!.resolvedPath).toBe("${HOME}/projects/foo/.opencode/skills/my-skill/SKILL.md") to match the coverage pattern of the rest of the test suite.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/plugin/src/features/magic-context/skill-memory/provenance.test.ts, line 107:
<comment>The test "parses a PLAIN filesystem path for a PROJECT skill" verifies that the function returns the correct `tier` and `skillSource`, but it doesn't verify the `resolvedPath` output. Since this is the only test exercising plain-path resolution for a project-level skill, a regression in the path-joining logic — like a missing `/SKILL.md` suffix, incorrect directory concatenation, or broken normalization — would go undetected. The adjacent test for global plain paths (and six other tests covering file:// paths) all assert `resolvedPath`. Consider adding an assertion like `expect(result!.resolvedPath).toBe("${HOME}/projects/foo/.opencode/skills/my-skill/SKILL.md")` to match the coverage pattern of the rest of the test suite.</comment>
<file context>
@@ -91,4 +91,36 @@ describe("parseSkillProvenance", () => {
+ test("parses a PLAIN filesystem path for a PROJECT skill", () => {
+ const output = `Base directory for this skill: ${HOME}/projects/foo/.opencode/skills/my-skill`;
+ const result = parseSkillProvenance(output, "my-skill");
+ expect(result!.tier).toBe("project");
+ expect(result!.skillSource).toBe("opencode-project");
+ });
</file context>
Per-skill "motor memory": when a skill's SKILL.md declares
`skill-memory: { enabled: true }`, accumulated gotchas/discoveries/fixes/
workflow-steps surface in a <skill-memory> block appended to the skill
tool's RESULT on every load (cache-safe — rides the tool-result tail).
Agents write back via ctx_skill_note; ctx_skill_recall is the explicit
companion to the transparent after-hook.
- migration: skill_memory table (per-skill; tier project/global; UNIQUE on
skill_id/tier/project_identity/normalized_hash) + lookup indexes.
- three-hook augmentation: tool.definition advertises an `intent` param;
tool.execute.before stashes intent (bounded TTL); after-hook parses the
skill's Base directory, reads SKILL.md frontmatter, formats the block.
- flat recency×hit recall + storage layer; ctx_skill_note / ctx_skill_recall.
- opt-in distill-skill-memory dreamer task; agent-prompt guidance; TUI/ctx-status stats.
- docs: ARCHITECTURE / STRUCTURE / CONFIGURATION / README.
Upgrade recall from flat recency×hit to a multi-rung cascade: intent + model-matched embeddings → cosine blend across intent_embedding + delta_embedding (relevance/recency/hit weights tunable per skill via ranking_* frontmatter); intent + no model match → FTS5 fallback over the content-linked skill_memory_fts vtable; empty → flat fallback. - migration: delta_embedding + recall_count columns + skill_memory_fts FTS5 vtable. - embed-on-write in insertSkillMemoryNote; delta-only semantic dedup. - programmatic, no-LLM reembed pre-step for the distill-skill-memory dreamer task. - read-side recall_count (distinct from write-side hit_count). - canonical vector serde + dedup/ranking/FTS query helpers.
…ication (P3a) Foundation for the historian to auto-capture skill notes cross-project. - surface the skill name in the historian chunk as a `TC: skill(<name>)` marker (the keystone — the tool input name was previously dropped). - migration: origin_project + source_type columns; unify global-tier notes under project_identity='*' (collision-merge) so a global note is one row recallable from any repo. - partitionKey helper routes global write/recall/reembed/stats through '*'; recall reads global-tier from '*' (cross-project); reembed sweeps '*'.
Close the loop so the historian writes skill notes during compaction without an agent volunteering ctx_skill_note. - historian prompt emits a <skill_observations> block; parser extracts it; threaded through the validated historian result. - both runners (OpenCode + Pi) promote skill observations post-commit via the shared promoteSkillObservations helper, gated by promotionActive && !discardedLast, writing global '*' notes with source_type='historian'. - self-heal net: initializeDatabase re-creates skill_memory + ensureColumn so an upgraded DB recovers even if a migration row is lost.
- Remove committed <<<<<<< HEAD conflict marker in CONFIGURATION.md (P1)
- Move injectSkillIntentParam before the lastChatContext guard so the intent
param is advertised even on tool.definition flights before first chat.message
- Key intentByCallId by sessionID:callID + prefix-prune on session delete so a
concurrent session's delete can't evict another session's in-flight intents
- Log silent catch in promoteSkillObservations (observability for dropped writes)
- Anchor frontmatter regex to start-of-file (drop m flag) so a later --- rule
can't be misparsed; strip inline # comments from unquoted YAML scalars + block header
- Scope distill report SQL to ('<identity>','*') instead of non-deterministic LIMIT 1
- Don't truncate skill name in TC: skill(<name>) marker (identity key); sanitize
newlines/control chars
- Normalize backslash->slash after fileURLToPath for Windows provenance checks
- FTS self-heal rebuild in initializeDatabase when skill_memory_fts is empty but
skill_memory has rows
- Move ctx_skill_recall _test* DI fields to a separate test-only deps type
- Hoist the shared registryKey dynamic import (one import, both blocks)
Pushback: reembed pre-step errors are already logged (task-executor.ts) — the
non-blocking try/catch is by design (failure leaves notes on the FTS rung).
…2/P3) - P1: recall now unions the skill's own partition with the global '*' partition (recallPartitionPredicate helper) so a PROJECT-LOCAL skill surfaces historian-written global notes — previously orphaned (tier='project' query never matched tier='global'/'*'). Write/dedup paths stay exact-partition. - Escape apostrophes in projectPath before SQL string interpolation in the distill prompt template. - Frontmatter regex tolerates a leading UTF-8 BOM / whitespace (still start-anchored). - TC: skill(<name>) marker emits the name VERBATIM when marker-safe, else drops it — never mutates the identity key (recall keys on raw input.name). - Fix misleading frontmatter test: now actually exercises a '#' inside a quoted scalar (preserved) vs unquoted (comment-stripped). Regression tests: project-local skill recalls a global historian note; agent project note + historian global note both surface for the same skill.
…routing/parser gaps) Council review (deepseek/sonnet/gpt-5.5) of PR cortexkit#181: - Must (consensus rev-2+rev-3): the ctx_skill_note fail-loud guard threw during plugin init when the plugin is disabled (enabled:false OR conflict-disabled) — createSessionHooks returns {magicContext:null} by design, so the unconditional guard crashed the entry module on the disabled path. Gate it on pluginConfig.enabled; pass a throwaway Map to createToolRegistry (which early-returns {} when disabled and never reads it). - Must (rev-3): singular ~/.config/opencode/skill/ global path was misclassified as project tier (opencode's pattern is {skill,skills}/**/SKILL.md) — fixed in deriveSkillTier/deriveSkillSource + the ctx_skill_recall cold-start search list. - Must (rev-3): the frontmatter parser rejected the inline flow-mapping form 'skill-memory: { enabled: true }' — the EXACT form the ctx_skill_recall remediation message and ARCHITECTURE/CONFIGURATION/README advertise. Added inline-mapping parsing so guidance and parser agree. - Should (consensus rev-1+rev-3): recallSkillMemoryBlock swallowed all errors silently — added a log() so FTS/blob corruption is diagnosable (still no-throw). Regression tests: inline frontmatter form (3 cases), singular skill/ global path (2 cases).
- budgetFill now counts per-note XML framing (~20 tokens) so the rendered
<skill-memory> block stays within max_tokens instead of ~13% overshoot (rev-1).
- clamp effective pinned budget to min(max_pinned_tokens, max_tokens) so the
default 4000>1500 can't imply pinned gets more room than the whole block (rev-2).
- ctx_skill_recall: derive tier via dirname(resolvedPath) instead of a fragile
.replace('/SKILL.md','') (rev-2).
Updated the budget-truncation test for the framing-inclusive math.
- provenance.ts: anchor the Base-directory regex to line-start (^…/gm) and take the LAST match — opencode appends the provenance line at the END of tool output, so a skill whose CONTENT echoes 'Base directory for this skill:' (e.g. a skill documenting skill-memory) would otherwise shadow the real line and misdirect recall to a bogus identity. - read-session-formatting.ts: narrow the marker-safe exclusion to CR/LF/tab only — a ')' does not break the single-line TC: skill(<name>) marker and the historian reads it as natural language, so a ')'-containing name is preserved verbatim (identity key) instead of dropped. - ARCHITECTURE.md: update the 'Skill-memory (motor memory)' Key Abstraction to the shipped reality (v50/51/52, multi-rung embedding+FTS recall, global-'*' union) — was stale (v37, 'P2 TODO'). Remove the PR-added duplicate 'Tag Identity (v3.3.1+)' section (upstream owns the lean '## Tag identity'; Tag Identity is unrelated to skill-memory — rebase scope-creep). Regression tests: provenance last-match + mid-line rejection; ')' name preserved + CR/LF/tab still dropped.
…emory off Rebase-onto-v0.29.0 resolution completion. Upstream ab4f01c added a memory.enabled gate that drops ALL ctx_memory mentions from the system prompt when memory is off (ctx_memory is then unregistered). The skill-memory guidance carried a 'those belong in ctx_memory' cross-reference that violated the new contract (buildMagicContextSection memory-gating tests). Parameterized ctxSkillMemoryGuidance(memoryEnabled) so the cross-ref drops when memory is off; skill-memory itself stays ungated (independent store).
…d in frontmatter Counterpart to ctx_skill_recall's enabled-guard (greptile review, PR cortexkit#181): without it, notes for skills that never opted in inserted successfully but were permanently orphaned — recallSkillMemoryBlock returns "" when frontmatter is disabled, while the agent saw a convincing 'Skill note saved' response. Now returns an actionable error before any insert. Red-checked: new regression test fails without the guard (orphan row inserted + 'saved' response), passes with it (no row, 'not enabled').
…opencode #33580) opencode's skill tool changed the 'Base directory for this skill:' line from a file:// URL to a plain filesystem path (upstream #33580). Our parser hard-required file:/// so parseSkillProvenance returned null on current opencode -> skill-load registry never populated -> every agent ctx_skill_note failed with a provenance parse error. Agent-written notes silently stopped 2026-07-02 (only historian-path notes, which bypass this parser, continued). Widen BASE_DIR_REGEX to capture the rest of the line and branch on the value: file:// -> fileURLToPath (legacy/back-compat); otherwise treat as a plain path. Keeps the line-anchor + last-match decoy-rejection invariant. +4 regression tests (plain global, plain project, plain decoy last-match, plain mid-line-ignore); all existing file:// tests unchanged.
Summary
Adds skill-memory — per-skill "motor memory" that gives a skill cross-session recall of its own hard-won lessons (gotchas, discoveries, fixes, workflow steps). When a skill's
SKILL.mddeclaresskill-memory: { enabled: true }, accumulated notes for that skill surface automatically in a<skill-memory>block appended to the skill tool's result on every load — and, with the historian extension, the historian writes those notes automatically during compaction (no agent action required).It's fully opt-in per skill and cache-safe by construction: the block rides the tool-result tail (conversation), never the cached system/m[0] prefix, so it can't bust the prompt cache.
How it works
tool.definitionadvertises anintentparam;tool.execute.beforestashes the intent (bounded TTL); the after-hook parses the skill'sBase directory, reads itsSKILL.mdfrontmatter, and formats the recall block. Lands in the tool RESULT (cache-safe).ctx_skill_note(agent-authored) and the historian (auto-extracted). Both dedup on a normalized hash.intent_embedding+delta_embedding(relevance/recency/hit weights tunable per skill viaranking_*frontmatter); FTS5 fallback over a content-linkedskill_memory_ftsvtable; flat recency×hit fallback.TC: skill(<name>)markers in its chunk and emits a<skill_observations>block; both the OpenCode and Pi runners promote those post-commit as global notes (project_identity='*',source_type='historian'), recallable from any project.Review units (4 commits)
The branch is organized into four coherent, reviewable phases:
skill_memorytable migration, the three-hook augmentation, flat recall + storage,ctx_skill_note/ctx_skill_recalltools, opt-indistill-skill-memorydreamer task, TUI/ctx-statusstats, docs.delta_embedding+recall_countcolumns +skill_memory_ftsFTS5 vtable; embed-on-write; the cosine/FTS recall cascade; a programmatic (no-LLM) reembed pre-step.TC: skill(<name>)marker keystone;origin_project+source_typecolumns; global-tier notes unified underproject_identity='*'(collision-merge);partitionKeyrouting.<skill_observations>, parser, validated-result threading, both runners promote via the sharedpromoteSkillObservationshelper, plus aninitializeDatabaseself-heal net (re-createsskill_memory+ensureColumnso an upgraded DB recovers even if a migration row is lost).Schema / migrations
Three migrations (numbered after the current
masterceiling):skill_memorytable, thendelta_embedding/recall_count/FTS, thenorigin_project/source_type/*unification.LATEST_SUPPORTED_VERSIONbumped in lockstep (theschema-version-fencetest enforces it). Migration bodies areensureColumn/IF NOT EXISTSidempotent.Testing
Full plugin + Pi suites pass (one unrelated pre-existing full-suite ordering flake in
tui-config.test.tsthat passes in isolation),tscclean both packages, lint clean, build produces all bundles. Dedicated coverage for the migrations (coexistence + fence), recall rungs, the tools, FTS triggers/backfill, the'*'collision-merge, and the historian promotion path on both runners. Verified working live: the historian auto-extraction writes genuinesource_type='historian'notes, embeddings populate, and the read-siderecall_countincrements on surfacing.Notes for reviewers
Need help on this PR? Tag
/codesmithwith what you need. Autofix is disabled.Summary by cubic
Adds per-skill cross-session memory with embeddings + FTS ranking and historian auto-extraction. A
<skill-memory>block is appended to eachskilltool result;ctx_skill_notewrites notes andctx_skill_recallrecalls them on demand.New Features
skilltool accepts optionalintent; before-hook stashes it and after-hook injects recall;ctx_skill_notewrites andctx_skill_recallreads; Status/TUI show per-project totals and pinned counts.intent_embedding+delta_embeddingwith FTS5 fallback; read-siderecall_count; re-embed pre-step backfills NULL/stale vectors before the distill run.TC: skill(<name>)markers →<skill_observations>; both runners promote viapromoteSkillObservationsas global'*'notes (source_type='historian').distill-skill-memorydreamer task added to config/CLI/dashboard/schema; runs a programmatic re-embed pre-step and emits a read-only corpus health report; off by default.skill_memorytable + FTS vtable/columns with migrations v50–v52 (LATEST_SUPPORTED_VERSION=52);initializeDatabasere-creates missing objects and rebuildsskill_memory_fts; supports opencode’s plain-path skill provenance.Bug Fixes
'*'so project-local skills surface historian-written notes; writes/dedup remain exact-partition.ctx_skill_notewhenskill-memory.enabledis false or missing; parser accepts inline{ enabled: true }, is start-of-file anchored, tolerates BOM/whitespace, and strips inline#from unquoted scalars.file://; correctly classify singular~/.config/opencode/skill/**asglobal; derive tier viadirname(resolvedPath); normalize Windows paths.ctx_memorycross-reference in skill guidance when memory is off; log recall errors; count per-note XML framing and clamp pinned budget.Written for commit 3825d5f. Summary will update on new commits.
Greptile Summary
Adds skill-memory — per-skill cross-session recall that appends a
<skill-memory>block to eachskilltool result when a skill opts in viaskill-memory: { enabled: true }frontmatter. The feature is fully cache-safe (recall block lands in the tool result tail, never the cached prefix) and includes historian auto-extraction so accumulated lessons are written globally without any agent action.skill_memorytable, FTS5 vtable with content-linked triggers,delta_embedding/recall_count/origin_project/source_typecolumns, and a v52 global-tier collision-merge.initializeDatabasegains a self-heal FTS backfill andensureColumncalls so an upgraded DB recovers even if a migration row is lost.intent_embedding+delta_embedding(rung 1, model-matched) → FTS5 MATCH on(intent, delta)(rung 2) → flat recency×hit (rung 3); pinned notes prepended ahead of any ranked result, all subject to a token-budget fill with a clamped pinned sub-budget.TC: skill(<name>)markers in historian chunks trigger<skill_observations>output, parsed bycompartment-parserand promoted as global'*'-partition notes by both the opencode and Pi runners via a sharedpromoteSkillObservationshelper.Confidence Score: 5/5
Safe to merge — all four issues identified in the previous review round have been addressed with regression tests, and no new defects were found in the core recall, storage, or historian-promotion paths.
The recall cascade, budget-fill logic, dedup paths, FTS triggers, migration idempotency, historian promotion gating, and intent-stash lifecycle were all reviewed in depth. The only findings are a duplicate call to
recallPartitionPredicateinsidesearchSkillMemoryFts(which is pure and functionally harmless) and an API ambiguity in the write-path functions where callers pass a pre-computed partition key to functions that applypartitionKeyinternally (idempotent for all valid inputs). Both are style-level concerns with no current failure path.storage.tshas the minorsearchSkillMemoryFtsdouble-call and write-function API ambiguity noted in comments; all other files are straightforward.Important Files Changed
searchSkillMemoryFtscallsrecallPartitionPredicatetwice instead of destructuring once.Sequence Diagram
%%{init: {'theme': 'neutral'}}%% sequenceDiagram participant Agent participant BeforeHook as tool.execute.before participant SkillTool as skill tool participant AfterHook as tool.execute.after participant Registry as SkillLoadRegistry participant Recall as recallSkillMemoryBlock participant DB as SQLite (skill_memory) participant Historian as Historian runner participant Promote as promoteSkillObservations Agent->>BeforeHook: "skill(name, intent=...)" BeforeHook->>BeforeHook: stashIntent(sessionID:callID, intent) BeforeHook->>SkillTool: forward call SkillTool-->>AfterHook: output with Base directory line AfterHook->>Registry: parseSkillProvenance → set(sessionID:skillId, entry) AfterHook->>AfterHook: getAndDeleteIntent(sessionID:callID) AfterHook->>Recall: recallSkillMemoryBlock(skill, intent, scope, projectIdentity) Recall->>DB: embed intent → cosine rank (rung 1) alt embeddings matched DB-->>Recall: ranked notes else FTS fallback (rung 2) DB-->>Recall: FTS MATCH notes else flat fallback (rung 3) DB-->>Recall: recency x hit notes end Recall->>DB: bumpRecallCountByIds Recall-->>AfterHook: skill-memory block AfterHook->>AfterHook: "output.output += block" AfterHook-->>Agent: tool result with appended skill-memory Agent->>DB: ctx_skill_note → insertSkillMemoryNote or bumpHitCount Historian->>Historian: parse skill_observations from chunk Historian->>Promote: promoteSkillObservations(db, originProject, obs) Promote->>DB: "insertSkillMemoryNote(tier=global, project_identity=*, source_type=historian)"%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%% sequenceDiagram participant Agent participant BeforeHook as tool.execute.before participant SkillTool as skill tool participant AfterHook as tool.execute.after participant Registry as SkillLoadRegistry participant Recall as recallSkillMemoryBlock participant DB as SQLite (skill_memory) participant Historian as Historian runner participant Promote as promoteSkillObservations Agent->>BeforeHook: "skill(name, intent=...)" BeforeHook->>BeforeHook: stashIntent(sessionID:callID, intent) BeforeHook->>SkillTool: forward call SkillTool-->>AfterHook: output with Base directory line AfterHook->>Registry: parseSkillProvenance → set(sessionID:skillId, entry) AfterHook->>AfterHook: getAndDeleteIntent(sessionID:callID) AfterHook->>Recall: recallSkillMemoryBlock(skill, intent, scope, projectIdentity) Recall->>DB: embed intent → cosine rank (rung 1) alt embeddings matched DB-->>Recall: ranked notes else FTS fallback (rung 2) DB-->>Recall: FTS MATCH notes else flat fallback (rung 3) DB-->>Recall: recency x hit notes end Recall->>DB: bumpRecallCountByIds Recall-->>AfterHook: skill-memory block AfterHook->>AfterHook: "output.output += block" AfterHook-->>Agent: tool result with appended skill-memory Agent->>DB: ctx_skill_note → insertSkillMemoryNote or bumpHitCount Historian->>Historian: parse skill_observations from chunk Historian->>Promote: promoteSkillObservations(db, originProject, obs) Promote->>DB: "insertSkillMemoryNote(tier=global, project_identity=*, source_type=historian)"Comments Outside Diff (1)
packages/plugin/src/features/magic-context/skill-memory/recall.ts, line 481-484 (link)catch {}makes recall errors invisibleAny exception thrown inside
recallSkillMemoryBlock— a SQLite error, embedding provider crash, or a coding mistake — is silently swallowed and returns an empty string. From the caller's perspective this is indistinguishable from "no notes exist" or "skill-memory disabled". At minimum asessionLogorconsole.warncall here would let a developer diagnose why the recall block is absent without attaching a debugger.Reviews (10): Last reviewed commit: "fix(skill-memory): accept plain filesyst..." | Re-trigger Greptile