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 23 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').
c1e5754 to
56c74bc
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.
…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').
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.
…(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.
…runcated Large skills (e.g. delegating at ~53KB) exceed opencode's MAX_BYTES=51200 tool-output truncation. The 'Base directory for this skill:' provenance line sits after the full SKILL.md content, so it lands in the dropped tail → parseSkillProvenance returns null → skillLoadRegistry never populates → ctx_skill_note hard-fails and the transparent <skill-memory> injection no-ops. Add resolveSkillPathByName (shared disk-walk in provenance.ts), wired into the after-hook as a fallback when the primary parse returns null. The skill name is always in the tool args (never truncated), so the fallback never depends on parsing truncatable output. ctx_skill_recall's cold-start walk refactored to reuse the same helper (removes duplication). Cross-family reviewed (M3): APPROVE must=0.
- Fallback only resolves project-tier candidates when the session directory is authoritative (sessionDirectoryBySession hit); a launch-dir guess must not register a wrong same-named project skill (cubic P1). Global tier resolves from HOME regardless. - Ancestor walk for project-tier candidates (nearest-first, bounded at 20 levels, stops at $HOME/root) — sessions rooted in a worktree subdir now find repo-root project skills, matching opencode's discoverSkills walk-up. - DB cleanup (try/finally closeQuietly) in the truncation tests. - Drop redundant test-provider reset in reembed.test.ts.
The fallback's global-dir walk reads $HOME at call time; a developer machine with a same-named global skill would flip the negative-registry assertions. Override HOME to an empty tmpdir for the describe block.
Upstream v0.33.0 added migrations v54-v69 (authority identity, mirror cursors, live-memory resnapshots, mural, message-FTS convergence), colliding with the skill-memory slots. Renumbered skill P1/P2/P3a to v70/71/72; LATEST_SUPPORTED_VERSION 69 -> 72. Also adapts our tests to two upstream contract changes: - executeStatus is async on this branch; upstream's new cortexkit#241 clamp tests needed await (they were added against the sync signature). - the historian output contract now requires the tiered paraphrase structure, so the skill_observations fixtures emit <p1> instead of a flat compartment body (same update upstream made to its e2e fixtures). - promotion.test.ts: upstream's two new embedding tests used the global mock.module('./embedding') this branch removed (CI mock bleed); they now use the non-global provider-factory seam.
resolveSkillPathByName's project-tier ancestor walk diverged from
opencode's real discovery (skill/index.ts calls fsys.up({ start:
directory, stop: worktree }), and that helper has no depth cap and
breaks AFTER checking the stop level):
- the 20-ancestor cap let a deeply nested session dir stop early, miss
the project skill, and fall through to a same-named GLOBAL skill —
registering the wrong tier and path;
- without a worktree boundary the walk kept climbing toward $HOME, so a
skill in a repo ABOVE the worktree could resolve as this project's.
Both corrupt the registry silently rather than failing loudly.
Stop at the worktree root, checked AFTER the pattern checks so the root
level stays inclusive (matching up()'s semantics). Detect it with
existsSync(<dir>/.git) — true for a normal clone's directory and a linked
worktree's file alike. Drop the depth cap; the walk is already bounded by
stripping one segment per step. $HOME/root backstops stay for sessions
outside any repo.
Regression tests red-verified against the old code: deep-nesting returned
null, and the boundary case leaked a parent-repo skill.
Upstream v0.34.0 claimed v73 (todowrite permission verdict) and v74 (detected context-limit provenance), so the three skill-memory migrations renumber v73/74/75 -> v75/76/77 and LATEST_SUPPORTED_VERSION follows to 77. Their test files move with them; upstream's own migrations-v73/74.test.ts are taken as-is. Adapts to four upstream changes: - executeStatus grew a `dreamer` parameter. Ours added `directory` for the skill-memory section; both are kept, dreamer first (upstream's position), directory appended. - The tool.execute.after hook now awaits flushIgnoredMessages and reads `agent` off the input; the skill-memory branch and the callID field are additive alongside it. - getDreamTaskBacklog's switch is exhaustive over DreamTaskName, so distill-skill-memory needed an arm. Returns 0/0 to match its always-eligible gate — the distill pass is whole-corpus maintenance with no per-item queue, and reaching into skill_memory internals would couple the scheduler to a table it does not otherwise touch. - promotion.test.ts: upstream fixed the mock.module bleed itself (7eb943e, our issue cortexkit#279) using the same provider-factory seam, so upstream's file is taken verbatim and our now-redundant version dropped. The three skill-memory test files keep their seam conversions. Two fence assertions relaxed from equality to a floor: migrations-v72 and -v74 asserted LATEST_SUPPORTED_VERSION was exactly their own version, which cannot hold once any migration is appended above them. The lockstep assertion (LATEST_SUPPORTED_VERSION === LATEST_MIGRATION_VERSION) is the invariant that matters and is kept in both.
…fork lane
Upstream v0.34.2 claimed v75 ("persist mural cue validation rejection latches"),
colliding with skill-memory P1 within hours of the last renumber. That is the
seventh renumber for this feature (v38 -> v39 -> v42 -> v54 -> v70 -> v73 -> v75)
and the collision class has twice made the runner skip a real migration body,
needing live-DB surgery to repair.
Upstream shipped the fix in v0.34.1 (docs/migration-version-lanes.md): versions
>= 10000 are reserved for downstream forks sharing context.db, and fork rows are
invisible to the upstream watermark and schema fence. This moves skill-memory
there:
fork 10000 P1 skill_memory table
fork 10001 P2 delta_embedding + recall_count + skill_memory_fts
fork 10002 P3a origin_project + source_type + global '*' unification
Fork migrations live in a new fork-migrations.ts rather than in MIGRATIONS, so
runMigrations() and the fence constant derived from MIGRATIONS stay byte-identical
to upstream; runForkMigrations() runs as a second pass from storage-db.ts's open
path. Selection is by per-row presence, not by the upstream watermark -- that is
what makes the lane immune to the collision-skip. Rationale in the module header.
LATEST_SUPPORTED_VERSION returns to 75, byte-identical to upstream. Divergence in
upstream-owned files is now:
schema-version-fence.test.ts 0 lines (byte-identical)
migrations.ts +7/-2 (two export keywords + comments)
migrations-v10000/1/2.test.ts (renamed from v75/76/77): the co-located
"LATEST_SUPPORTED_VERSION === newest migration" mirrors asserted an UPSTREAM-lane
contract a fork migration does not participate in. Replaced with the contract
that does hold: the migration is present in FORK_MIGRATIONS and the fence stays
below the floor.
storage-db.test.ts: upstream's downstream-rows test hand-seeds floor+0/+1, which
this branch now genuinely owns (P1/P2). Moved the seed to floor+9000/+9001 so the
test still measures what it means -- that hand-inserted downstream rows survive
and stay fence-invisible.
fork-migrations.test.ts covers lane placement, cross-lane uniqueness, idempotent
re-run, the ordering contract, and that fork rows never advance the upstream
watermark. The load-bearing test drives the real openDatabase() path and asserts
the DDL landed, not just the bookkeeping rows -- red-checked by removing both
runForkMigrations calls, which leaves every other test green (a dead-on-arrival
seam) and fails only that one.
Gates: plugin 3728/0, pi 734/0, cli 296 (2 skip), typecheck 0 x3, lint clean,
tui-compiled reproducible, marker sweep clean.
…egime v0.35.0 introduced prompt-surface budget governance (A1 golden + budget fixture + checklist) that pins the agent-facing surface at five tools. Skill-memory adds ctx_skill_note and ctx_skill_recall, so every surface keyed to that list needed them: - ACTIVE_TOOL_IDS and the measurement catalog (buildToolDefinitions) - PROMPT_SURFACE_TOOL_IDS + LIGHT_TOOL_DESCRIPTIONS. This was a real gap, not just bookkeeping: descriptionFor() early-returns the full description for any id outside PROMPT_SURFACE_TOOL_ID_SET, so the light preset was silently serving full-length skill-tool descriptions. - Two authored light descriptions (light-descriptions.ts) - The A1 golden, regenerated -> 7 tools export-agent-surface.ts also never emitted section 3 (the system-prompt hash baseline), so that table was hand-maintained and regenerating the golden dropped it. Verified the hash is just MD5 of the guidance bytes -- reproduces all four committed upstream values byte-for-byte -- and taught the generator to emit it. BUDGET FIXTURE: re-measured, NOT relaxed. mutableProseBaseline 3650 -> 4087 integerLightCeiling 1825 -> 2043 (still floor(0.50 * baseline)) builtInProviderVisible 4560 -> 5260 Same tokenizer identity, same policy expression, same primary variant, same inclusion/exclusion lists; only the measurements moved, because the surface is genuinely larger. The fixture carries a downstreamNote recording upstream's numbers and stating plainly that this is a downstream measurement requiring upstream ratification, not a self-granted budget increase. Note the light surface now measures 1969 tokens, which would have BREACHED the old 1825 ceiling -- the increase is load-bearing, not cosmetic. Pi: skill-memory is OpenCode-only (it hangs off OpenCode's `skill` tool hook trio; Pi has no `skill` tool), so the shared golden lists seven tools while Pi registers five. Pi's parity tests subtract the pair via an explicit OPENCODE_ONLY_GOLDEN_TOOLS set rather than a prefix filter, so a genuinely-shared tool Pi failed to register still fails the test; the set is validated against the golden at read time so it cannot rot. Documented as PARITY.md 8b. Gates: plugin 3777/2, pi 741/0, cli 296 (2 skip), typecheck 0 x3, lint clean, check-prompt-surface --budget green. The 2 plugin failures are upstream's own @OpenTui TDZ errors -- verified identical on a clean upstream/master worktree.
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 recall with intent-aware embeddings and historian auto-extraction. Notes surface in a
<skill-memory>block, withctx_skill_noteto write andctx_skill_recallto read; schema ships via fork‑lane migrations, and prompt/status surfaces now include the skill tools and stats.New Features
SKILL.md(skill-memory: { enabled: true }). Transparent<skill-memory>injection onskillloads; toolsctx_skill_noteandctx_skill_recall; prompt guidance added (A1 golden + light descriptions) and the budget fixture re‑measured.intent_embedding+delta_embedding) with cosine blend, FTS5 fallback, then recency×hit;recall_counttracked.distill-skill-memorytask (opt‑in) re‑embeds NULL/stale vectors first and emits a read‑only health report.TC: skill(<name>)→<skill_observations>→ global'*'notes (source_type='historian'), promoted by both OpenCode and Pi; recall unions project partition with global'*'so project skills see historian notes.skill_memory(+ embeddings/FTS + origin/source/global'*'), withinitializeDatabaseself‑heal (table/FTS/triggers) and a dedicatedrunForkMigrationspass. Status/RPC/TUI surface per‑project skill‑memory stats.Bug Fixes
'*'; error logging on recall; budget math counts per‑note framing and clamps pinned tomin(max_pinned_tokens, max_tokens). FTS rebuilds on self‑heal when empty but rows exist.file://; name‑based fallback when the provenance line is truncated; last‑line, line‑anchored match; Windows path normalization; ancestor walk stops at the worktree root; global discovery handles bothskill/andskills/.skill-memory: { enabled: true }; rejectctx_skill_notewhen disabled; session‑scoped intent stash keyed per‑session with TTL; disabled‑path init guard. Pi parity: skill‑memory tools are OpenCode‑only and are subtracted in Pi parity tests; prompt guidance dropsctx_memorycross‑refs when memory is off.Written for commit 88d9849. Summary will update on new commits.
Greptile Summary
This PR introduces skill-memory — an opt-in per-skill cross-session recall system that appends a
<skill-memory>block to skill tool results and auto-extracts lessons via the historian. All four critical bugs from the previous review round (intent-stash keying, missing FTS rebuild in self-heal, project-local skill recall orphaning,ctx_skill_notewriting disabled-skill notes) are addressed in the current HEAD.recallSkillMemoryBlock;recallPartitionPredicateunions the skill's own partition with the global'*'tier so historian-written notes are surfaced for project-local skills.tool.execute.beforestashes theintentparam (keyed${sessionID}:${callID});tool.execute.afterpopulates theSkillLoadRegistryfrom the provenance line in the skill output and appends the recall block; session cleanup prunes by prefix to avoid cross-session eviction.<skill_observations>parsed bycompartment-parser.ts; both OpenCode and Pi runners callpromoteSkillObservationsunder the samepromotionActive && !discardedLastgate used for facts/primers.Confidence Score: 5/5
searchSkillMemoryFts(same pure function, same args, idempotent result) and an API-contract mismatch inctx-skill-note/tools.tswhere an already-partitioned key is passed where a raw identity is expected (harmless due topartitionKeyidempotency). Neither can produce wrong data with the current implementation.storage.ts(searchSkillMemoryFtsdouble call) andctx-skill-note/tools.ts(partition key passed as project identity) are self-contained and have no runtime impact.Important Files Changed
recallPartitionPredicatecorrectly unions the skill's own partition with the global '*' tier. One style issue:searchSkillMemoryFtscomputes the same predicate object twice instead of caching the result.log()before returning empty string — the previous barecatch {}concern from the pre-fix review is addressed.rankRung1correctly normalises recency and hit_count to [0,1];budgetFillclampseffectiveMaxPinnedtomaxTokens.IF NOT EXISTS/ensureColumn), and a guarded FTSrebuildin v10001.runForkMigrationsvalidates that no version falls below the floor and usesIMMEDIATEtransactions matching upstream isolation semantics.initializeDatabasecreatesskill_memorytable, FTS vtable, and triggers if absent; the guardedrebuild(ftsCount=0 && rowCount>0) correctly fires only on the gap rather than every boot.runForkMigrationsis called in bothopenDatabaseandopenDatabaseAsyncpaths.intentByCallIdstash (composite${sessionID}:${callID}keys),createToolExecuteBeforeHook, andmaybeInjectSkillMemory. Session cleanup viapruneIntentsForSession(prefix prune) prevents cross-session eviction — the previously-flagged bare-callID bug is addressed. Provenance hoisting (single dynamic import shared by both blocks) is also correct.frontmatterConfig?.enabledbefore inserting (previously-flagged orphan-note bug is fixed). Semantic dedup, exact-hash dedup, and concurrent-insert race all handled. Passespart(already-partitioned key) to storage functions that expect rawprojectIdentity— idempotent but violates the storage API contract.findExistingNote+bumpHitCountis correct. Per-itemtry/catchensures one bad observation doesn't block the rest.parseSkillProvenancecorrectly handles bothfile://URLs and plain paths; last-match wins over the entire output.resolveSkillPathByNameancestor walk stops at.git,$HOME, or filesystem root. Session-scoped registry keyed${sessionId}:${skillId}matches the cleanup pattern inonSessionDeleted.skill-memory:. Handles both inline{...}and block forms; FRONTMATTER_REGEX is start-anchored (nomflag). Inline YAML comment stripping and quote handling are correct. Failure modes returnnull(inert).ParsedSkillObservationandskillObservationsparsing viaSKILL_OBS_ITEM_REGEX. The regex correctly anchors to line-start (^withgm) and captures skill-id, kind, and lesson separated by `discoverSkills()order (project shadows global). Correctly guards onfrontmatterConfig?.enabledbefore recall. Test-DI seams use an internal cast that keeps the publicCtxSkillRecallToolDepscontract clean.Sequence Diagram
sequenceDiagram participant Agent participant Hook as tool.execute hooks participant Registry as SkillLoadRegistry participant IntentStash as intentByCallId participant DB as skill_memory (SQLite) participant Historian Agent->>Hook: tool.execute.before(skill, intent) Hook->>IntentStash: stash(sessionID:callID, intent) Agent->>Hook: tool.execute.after(skill output) Hook->>Hook: parseSkillProvenance(output) Hook->>Hook: parseFrontmatterConfig(SKILL.md) Hook->>Registry: set(sessionID:skillId, provenance+config) Hook->>IntentStash: getAndDelete(sessionID:callID) Hook->>DB: recallSkillMemoryBlock(skillId, intent, tier, projectIdentity) DB-->>Hook: "<skill-memory> block (or "")" Hook-->>Agent: "output.output += block" Agent->>DB: ctx_skill_note(skill, kind, delta) DB->>DB: hash-dedup + semantic-dedup DB->>DB: embed(intent) + embed(delta) DB->>DB: insertSkillMemoryNote Historian->>DB: promoteSkillObservations(skillObservations) Note over Historian,DB: tier='global', project_identity='*' DB->>DB: hash-dedup per observationReviews (32): Last reviewed commit: "fix(prompt-surface): admit skill-memory ..." | Re-trigger Greptile