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 16 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).
Tip: Review your code locally with the cubic CLI to iterate faster.
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).
…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').
…ath provenance case cubic P2 (PR cortexkit#181): the 'PLAIN filesystem path for a PROJECT skill' test asserted tier/skillSource but not resolvedPath — the only plain-path project-skill test, so a path-join regression (missing /SKILL.md suffix, bad concat) would go undetected. Added the resolvedPath assertion to match the coverage pattern of every sibling test.
…ath provenance case cubic P2 (PR cortexkit#181): the 'PLAIN filesystem path for a PROJECT skill' test asserted tier/skillSource but not resolvedPath — the only plain-path project-skill test, so a path-join regression (missing /SKILL.md suffix, bad concat) would go undetected. Added the resolvedPath assertion to match the coverage pattern of every sibling test.
…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').
…ath provenance case cubic P2 (PR cortexkit#181): the 'PLAIN filesystem path for a PROJECT skill' test asserted tier/skillSource but not resolvedPath — the only plain-path project-skill test, so a path-join regression (missing /SKILL.md suffix, bad concat) would go undetected. Added the resolvedPath assertion to match the coverage pattern of every sibling test.
There was a problem hiding this comment.
1 issue found across 4 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/reembed.test.ts">
<violation number="1" location="packages/plugin/src/features/magic-context/skill-memory/reembed.test.ts:53">
P3: Redundant `_setTestProviderFactoryForProject(null)` call before `installReembedTestProvider()` -- the `installReembedTestProvider()` lambda immediately overwrites the factory anyway. The `_resetProjectEmbeddingRegistryForTests()` already clears the factory to null, so this extra call is a no-op. Looks like a fragment from an earlier refactor; consider dropping the middle call to keep the setup intent clear.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
| describe("reembed", () => { | ||
| test("reembedStaleSkillNotes fills NULL embeddings (bounded, idempotent)", async () => { | ||
| _resetProjectEmbeddingRegistryForTests(); | ||
| _setTestProviderFactoryForProject(null); |
There was a problem hiding this comment.
P3: Redundant _setTestProviderFactoryForProject(null) call before installReembedTestProvider() -- the installReembedTestProvider() lambda immediately overwrites the factory anyway. The _resetProjectEmbeddingRegistryForTests() already clears the factory to null, so this extra call is a no-op. Looks like a fragment from an earlier refactor; consider dropping the middle call to keep the setup intent clear.
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/reembed.test.ts, line 53:
<comment>Redundant `_setTestProviderFactoryForProject(null)` call before `installReembedTestProvider()` -- the `installReembedTestProvider()` lambda immediately overwrites the factory anyway. The `_resetProjectEmbeddingRegistryForTests()` already clears the factory to null, so this extra call is a no-op. Looks like a fragment from an earlier refactor; consider dropping the middle call to keep the setup intent clear.</comment>
<file context>
@@ -1,71 +1,116 @@
+describe("reembed", () => {
+ test("reembedStaleSkillNotes fills NULL embeddings (bounded, idempotent)", async () => {
+ _resetProjectEmbeddingRegistryForTests();
+ _setTestProviderFactoryForProject(null);
+ installReembedTestProvider();
+
</file context>
2000076 to
8b3af4e
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').
…ath provenance case cubic P2 (PR cortexkit#181): the 'PLAIN filesystem path for a PROJECT skill' test asserted tier/skillSource but not resolvedPath — the only plain-path project-skill test, so a path-join regression (missing /SKILL.md suffix, bad concat) would go undetected. Added the resolvedPath assertion to match the coverage pattern of every sibling test.
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.
… v50 collision Upstream v0.31.0 added its own migration v50 (ctx-wrapup durable marker), colliding with skill-memory's v50/51/52. Renumbered skill migrations to v51 (skill_memory table) / v52 (embeddings+FTS) / v53 (historian extraction), bumped LATEST_SUPPORTED_VERSION to 53, and rotated the migration test files (v42/v51/v52 -> v51/v52/v53) with corrected internal version refs + fence assertions.
…ath provenance case cubic P2 (PR cortexkit#181): the 'PLAIN filesystem path for a PROJECT skill' test asserted tier/skillSource but not resolvedPath — the only plain-path project-skill test, so a path-join regression (missing /SKILL.md suffix, bad concat) would go undetected. Added the resolvedPath assertion to match the coverage pattern of every sibling test.
- Regenerate src/tui-compiled/index.tsx via the new v0.31 build:tui step so the compiled TUI carries the skill-memory render block (source src/tui had it; the committed artifact was stale — 0 refs → 2). Satisfies the check:tui-compiled CI gate. - ARCHITECTURE/STRUCTURE: dreamer maintain-docs refresh — migration list v1–v53 (v50 wrapup marker, skill v51/52/53), plain-path provenance (#33580), and the ctx_skill_note enabled-guard. mc:protected untouched.
…(use provider-factory seam) Bun mock.module is process-global and mock.restore() cannot undo it cross-file in Bun 1.3.14. The skill-memory test files (reembed, recall, ctx-skill-note) and promotion.test.ts each globally mocked the embedding barrel, which bled into ctx-memory's provider-coordination tests (5s timeouts) and into each other under CI worker sharding. Converted all four files to the non-global seam that ctx-memory's own tests use: _setTestProviderFactoryForProject + registerProjectEmbedding with mandatory afterEach reset. Zero mock.module calls for any embedding barrel remain in the test suite.
8b3af4e to
bc53e1d
Compare
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 embedding + FTS ranking and historian auto-extraction. Each
skillresult now appends a<skill-memory>block;ctx_skill_note/ctx_skill_recallmanage notes, and thedistill-skill-memorytask pre-embeds and emits a read-only corpus health report (TUI/ctx-statusshow per-project stats).New Features
skilltool advertises optionalintent; before/after hooks stash intent and inject recall; enable viaSKILL.md(skill-memory: { enabled: true }). Prompt guidance added.intent_embedding+delta_embeddingwith FTS5 fallback;recall_counttracked; a pre‑step re‑embeds NULL/stale vectors beforedistill-skill-memory.TC: skill(<name>)markers →<skill_observations>; both OpenCode and Pi promote as global'*'notes (source_type='historian').distill-skill-memory(off by default) added to config/CLI/dashboard; runs the re‑embed pre‑step and outputs a health summary; TUI/ctx-statusdisplay totals/skills/pinned.skill_memory+ FTS andorigin_project/source_type;initializeDatabasecan create/repair tables and rebuildskill_memory_fts.Bug Fixes
'*'so project‑local skills surface historian notes; writes/dedup remain exact‑partition.{ enabled: true }; start‑of‑file anchored with BOM/whitespace tolerance and#handling; accept plain paths andfile://; take the last, line‑anchored “Base directory” match; normalize Windows paths.sessionId:callId, TTL + bounded size); log recall errors; escape apostrophes in SQL; count per‑note XML framing and clamp pinned budget.ctx_skill_notewhen disabled;ctx_skill_recallcold‑start search and tier derivation fixed; injectedintentschema forskill; compiled TUI refreshed; embedding tests use the provider‑factory seam to prevent cross‑file bleed.Written for commit bc53e1d. Summary will update on new commits.
Greptile Summary
This PR introduces skill-memory — a fully opt-in, per-skill cross-session recall system. When a skill's
SKILL.mddeclaresskill-memory: { enabled: true }, accumulated lessons are recalled automatically as a<skill-memory>block appended to the skill tool result. The historian extension auto-extracts observations during compaction without requiring any agent action.skill_memorytable, FTS5 virtual table with triggers, and historian provenance columns. Recall uses a 4-rung cascade (cosine embedding → FTS5 → flat recency×hit) with a cross-partitionrecallPartitionPredicateunion ensuring historian notes surface for project-local skills. All issues from prior review threads are addressed.promoteSkillObservationswrites historian-extracted lessons as globaltier='global'/project_identity='*'notes; both OpenCode and Pi runners promote via the shared helper with the samepromotionActive && !discardedLastguard used for facts and primers.ctx_skill_noterecords agent-authored notes with exact-hash and semantic dedup;ctx_skill_recallprovides explicit recall with registry-first resolution and disk-fallback for cold sessions.Confidence Score: 5/5
Safe to merge. All four prior review threads were properly addressed and remaining findings are minor design trade-offs or style nits that do not affect correctness.
The prior review identified four concrete defects (session-scoped intent stash, FTS rebuild on self-heal, historian cross-partition recall, disabled-skill note guard) — all confirmed fixed. New findings are a redundant function call (style), a known cross-partition dedup trade-off, and a multi-model embedding consistency gap for global historian notes. None cause data loss or incorrect behavior in single-model deployments, and all have graceful fallbacks.
packages/plugin/src/features/magic-context/skill-memory/reembed.ts — global-note re-embed using the calling project's model version is worth revisiting if per-project embedding model configuration is ever adopted. packages/plugin/src/features/magic-context/skill-memory/storage.ts — cross-partition dedup blind spot in findExistingNote warrants a follow-up if duplicate lesson surfacing is reported by users.
Important Files Changed
recallPartitionPredicateunion logic for cross-partition recall. Minor:searchSkillMemoryFtscallsrecallPartitionPredicatetwice unnecessarily. Write/dedup paths intentionally stay exact-partition.budgetFillcorrectly clamps pinned sub-budget against total tokens. Error handling now useslog()as noted in prior review.'*'partition notes with best-effort per-item error handling. Hash-dedup is exact-partition only (intentional design choice).skill_memorytable, FTS5 vtable + triggers, and historian provenance columns. All useensureColumn/IF NOT EXISTSfor idempotency.LATEST_SUPPORTED_VERSIONbumped to 53.tool.execute.beforehook (stashes intent keyedsessionId:callId), extendstool.execute.afterto populateskillLoadRegistryand inject the<skill-memory>recall block. Intent map uses composite session-scoped keys as fixed in prior review.ctx_skill_notetool with correctenabledguard (added per prior review), exact-hash and semantic dedup, embedding on write. Registry-lookup gated on frontmatterConfig.enabled before any DB write.initializeDatabasepath addsskill_memorytable + FTS vtable + triggers, includes the guarded FTS rebuild (fixed per prior review), andensureColumncalls for P2/P3 columns.LATEST_SUPPORTED_VERSIONbumped to 53.IS NOT ?. Includes global'*'partition rows using the calling project's embedding provider, which may cause model-version mismatches across multi-model deployments.Sequence Diagram
%%{init: {'theme': 'neutral'}}%% sequenceDiagram participant Agent participant skill tool participant tool.execute.before participant tool.execute.after participant SkillLoadRegistry participant recallSkillMemoryBlock participant DB as SQLite DB Agent->>skill tool: skill(name, intent?) skill tool->>tool.execute.before: fires pre-validation tool.execute.before->>tool.execute.before: stashIntent(sessionId:callId, intent) skill tool-->>Agent: skill result (Base directory: ...) skill tool->>tool.execute.after: fires post-execution tool.execute.after->>tool.execute.after: parseSkillProvenance(output) tool.execute.after->>tool.execute.after: parseFrontmatterConfig(SKILL.md) tool.execute.after->>SkillLoadRegistry: set(sessionId:skillId, provenance+config) tool.execute.after->>tool.execute.after: getAndDeleteIntent(sessionId:callId) tool.execute.after->>recallSkillMemoryBlock: recall(skill, intent, tier, projectIdentity) recallSkillMemoryBlock->>DB: embedTextForProject(intent) recallSkillMemoryBlock->>DB: getRankingCandidates (cosine rung) alt embeddings match recallSkillMemoryBlock->>DB: rankRung1 + budgetFill else FTS fallback recallSkillMemoryBlock->>DB: searchSkillMemoryFts else flat fallback recallSkillMemoryBlock->>DB: getSkillMemoryNotes end recallSkillMemoryBlock->>DB: bumpRecallCountByIds recallSkillMemoryBlock-->>tool.execute.after: skill-memory block tool.execute.after->>tool.execute.after: append block to output.output Note over Agent,DB: Historian auto-extraction Historian->>promoteSkillObservations: skillObservations[] promoteSkillObservations->>DB: "INSERT tier=global project_identity=*"%%{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 skill tool participant tool.execute.before participant tool.execute.after participant SkillLoadRegistry participant recallSkillMemoryBlock participant DB as SQLite DB Agent->>skill tool: skill(name, intent?) skill tool->>tool.execute.before: fires pre-validation tool.execute.before->>tool.execute.before: stashIntent(sessionId:callId, intent) skill tool-->>Agent: skill result (Base directory: ...) skill tool->>tool.execute.after: fires post-execution tool.execute.after->>tool.execute.after: parseSkillProvenance(output) tool.execute.after->>tool.execute.after: parseFrontmatterConfig(SKILL.md) tool.execute.after->>SkillLoadRegistry: set(sessionId:skillId, provenance+config) tool.execute.after->>tool.execute.after: getAndDeleteIntent(sessionId:callId) tool.execute.after->>recallSkillMemoryBlock: recall(skill, intent, tier, projectIdentity) recallSkillMemoryBlock->>DB: embedTextForProject(intent) recallSkillMemoryBlock->>DB: getRankingCandidates (cosine rung) alt embeddings match recallSkillMemoryBlock->>DB: rankRung1 + budgetFill else FTS fallback recallSkillMemoryBlock->>DB: searchSkillMemoryFts else flat fallback recallSkillMemoryBlock->>DB: getSkillMemoryNotes end recallSkillMemoryBlock->>DB: bumpRecallCountByIds recallSkillMemoryBlock-->>tool.execute.after: skill-memory block tool.execute.after->>tool.execute.after: append block to output.output Note over Agent,DB: Historian auto-extraction Historian->>promoteSkillObservations: skillObservations[] promoteSkillObservations->>DB: "INSERT tier=global project_identity=*"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 (16): Last reviewed commit: "fix(test): stop global embedding mock.mo..." | Re-trigger Greptile