diff --git a/internal/setup/plugins/opencode/engram.ts b/internal/setup/plugins/opencode/engram.ts index c5567087..0e8ea89e 100644 --- a/internal/setup/plugins/opencode/engram.ts +++ b/internal/setup/plugins/opencode/engram.ts @@ -89,7 +89,10 @@ Also search memory PROACTIVELY when: ### SESSION CLOSE PROTOCOL (mandatory) Before ending a session or saying "done" / "that's it", you MUST: -1. Call \`mem_session_summary\` with this structure: +1. Call \`mem_session_summary\` with the structure below. +2. Call \`mem_session_end\` with the active session id and an optional summary of work completed. + +\`mem_session_summary\` structure: ## Goal [What we were working on this session] @@ -133,6 +136,9 @@ async function engramFetch( headers: opts.body ? { "Content-Type": "application/json" } : undefined, body: opts.body ? JSON.stringify(opts.body) : undefined, }) + // Treat non-2xx as failure so callers can avoid false-positive side effects + // (e.g. marking a session known when POST /sessions did not succeed). + if (!res.ok) return null return await res.json() } catch { // Engram server not running — silently fail @@ -225,8 +231,7 @@ export const Engram: Plugin = async (ctx) => { if (!sessionId || knownSessions.has(sessionId)) return // Do not register sub-agent sessions in Engram (issue #116). if (subAgentSessions.has(sessionId)) return - knownSessions.add(sessionId) - await engramFetch("/sessions", { + const created = await engramFetch("/sessions", { method: "POST", body: { id: sessionId, @@ -234,6 +239,10 @@ export const Engram: Plugin = async (ctx) => { directory: ctx.directory, }, }) + // Only mark known after confirmed create so failed requests can retry. + if (created != null) { + knownSessions.add(sessionId) + } } // Try to start engram server if not running @@ -316,8 +325,24 @@ export const Engram: Plugin = async (ctx) => { const info = (event.properties as any)?.info const sessionId = info?.id if (sessionId) { + if (knownSessions.has(sessionId)) { + // session.deleted fires once — retry once inline, and only clear + // knownSessions after a confirmed /end (do not pretend it closed). + let ended = await engramFetch( + `/sessions/${encodeURIComponent(sessionId)}/end`, + { method: "POST" } + ) + if (ended == null) { + ended = await engramFetch( + `/sessions/${encodeURIComponent(sessionId)}/end`, + { method: "POST" } + ) + } + if (ended != null) { + knownSessions.delete(sessionId) + } + } toolCounts.delete(sessionId) - knownSessions.delete(sessionId) subAgentSessions.delete(sessionId) lastNudgeTime.delete(sessionId) } diff --git a/internal/setup/setup.go b/internal/setup/setup.go index 31333c1a..db0fa4a3 100644 --- a/internal/setup/setup.go +++ b/internal/setup/setup.go @@ -176,7 +176,10 @@ Also search memory PROACTIVELY when: ### SESSION CLOSE PROTOCOL (mandatory) Before ending a session or saying "done" / "listo" / "that's it", you MUST: -1. Call mem_session_summary with this structure: +1. Call mem_session_summary with the structure below. +2. Call mem_session_end with the active session id and an optional summary of work completed. + +mem_session_summary structure: ## Goal [What we were working on this session] diff --git a/plugin/codex/skills/memory/SKILL.md b/plugin/codex/skills/memory/SKILL.md index d0ce3f50..ca164f03 100644 --- a/plugin/codex/skills/memory/SKILL.md +++ b/plugin/codex/skills/memory/SKILL.md @@ -90,7 +90,10 @@ Also search memory PROACTIVELY when: ## SESSION CLOSE PROTOCOL (mandatory) Before ending a session or saying "done" / "that's it", you MUST: -1. Call `mem_session_summary` with this structure: +1. Call `mem_session_summary` with the structure below. +2. Call `mem_session_end` with the active session id and an optional summary of work completed. + +`mem_session_summary` structure: ## Goal [What we were working on this session] diff --git a/plugin/opencode/engram.ts b/plugin/opencode/engram.ts index c5567087..0e8ea89e 100644 --- a/plugin/opencode/engram.ts +++ b/plugin/opencode/engram.ts @@ -89,7 +89,10 @@ Also search memory PROACTIVELY when: ### SESSION CLOSE PROTOCOL (mandatory) Before ending a session or saying "done" / "that's it", you MUST: -1. Call \`mem_session_summary\` with this structure: +1. Call \`mem_session_summary\` with the structure below. +2. Call \`mem_session_end\` with the active session id and an optional summary of work completed. + +\`mem_session_summary\` structure: ## Goal [What we were working on this session] @@ -133,6 +136,9 @@ async function engramFetch( headers: opts.body ? { "Content-Type": "application/json" } : undefined, body: opts.body ? JSON.stringify(opts.body) : undefined, }) + // Treat non-2xx as failure so callers can avoid false-positive side effects + // (e.g. marking a session known when POST /sessions did not succeed). + if (!res.ok) return null return await res.json() } catch { // Engram server not running — silently fail @@ -225,8 +231,7 @@ export const Engram: Plugin = async (ctx) => { if (!sessionId || knownSessions.has(sessionId)) return // Do not register sub-agent sessions in Engram (issue #116). if (subAgentSessions.has(sessionId)) return - knownSessions.add(sessionId) - await engramFetch("/sessions", { + const created = await engramFetch("/sessions", { method: "POST", body: { id: sessionId, @@ -234,6 +239,10 @@ export const Engram: Plugin = async (ctx) => { directory: ctx.directory, }, }) + // Only mark known after confirmed create so failed requests can retry. + if (created != null) { + knownSessions.add(sessionId) + } } // Try to start engram server if not running @@ -316,8 +325,24 @@ export const Engram: Plugin = async (ctx) => { const info = (event.properties as any)?.info const sessionId = info?.id if (sessionId) { + if (knownSessions.has(sessionId)) { + // session.deleted fires once — retry once inline, and only clear + // knownSessions after a confirmed /end (do not pretend it closed). + let ended = await engramFetch( + `/sessions/${encodeURIComponent(sessionId)}/end`, + { method: "POST" } + ) + if (ended == null) { + ended = await engramFetch( + `/sessions/${encodeURIComponent(sessionId)}/end`, + { method: "POST" } + ) + } + if (ended != null) { + knownSessions.delete(sessionId) + } + } toolCounts.delete(sessionId) - knownSessions.delete(sessionId) subAgentSessions.delete(sessionId) lastNudgeTime.delete(sessionId) }