opencode-codex-memory 0.1.5 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,37 @@
1
+ {
2
+ "$schema": "https://opencode.ai/config.json",
3
+ "agent": {
4
+ "memorize": {
5
+ "mode": "subagent",
6
+ "prompt": "You are a memory consolidation agent. Read the workspace diff file and update MEMORY.md, memory_summary.md, and skills/ to reflect the latest memories. Keep memory_summary.md under 10000 chars (2500 tokens). Prune stale entries. Do not access the network.",
7
+ "permission": {
8
+ "bash": "deny",
9
+ "webfetch": "deny",
10
+ "websearch": "deny",
11
+ "task": "deny",
12
+ "todowrite": "deny",
13
+ "read": "allow",
14
+ "edit": "allow",
15
+ "write": "allow",
16
+ "glob": "allow",
17
+ "grep": "allow"
18
+ }
19
+ },
20
+ "memorize-extract": {
21
+ "mode": "subagent",
22
+ "prompt": "You are a memory extraction agent. Read the session transcript and extract raw_memory, rollout_summary, and rollout_slug as JSON. Exclude AGENTS.md/instruction content. Redact secrets.",
23
+ "permission": {
24
+ "bash": "deny",
25
+ "webfetch": "deny",
26
+ "websearch": "deny",
27
+ "task": "deny",
28
+ "todowrite": "deny",
29
+ "read": "allow",
30
+ "write": "deny",
31
+ "edit": "deny",
32
+ "glob": "allow",
33
+ "grep": "allow"
34
+ }
35
+ }
36
+ }
37
+ }
package/dist/src/llm.d.ts CHANGED
@@ -15,5 +15,6 @@ export declare function extractViaSubagent(sessionId: string, transcript: string
15
15
  export declare function consolidateViaSubagent(memoryRoot: string, diffFileName: string, model?: string): Promise<void>;
16
16
  export declare function cleanupOldSubSessions(maxAgeMinutes?: number): Promise<void>;
17
17
  export declare function fillTemplate(tmpl: string, vars: Record<string, string>): string;
18
+ export declare function buildConsolidationPrompt(memoryRoot: string, diffFileName: string): string;
18
19
  /** Parses the stage-1 JSON output. Returns null for the all-empty no-op response. */
19
20
  export declare function parseExtraction(raw: string): ExtractionResult | null;
package/dist/src/llm.js CHANGED
@@ -209,10 +209,46 @@ function buildExtractionInput(sessionId, cwd, transcript) {
209
209
  transcript,
210
210
  });
211
211
  }
212
- function buildConsolidationPrompt(memoryRoot, diffFileName) {
212
+ // codex lib.rs prompt_blocks: rendered into consolidation.md's
213
+ // {{ memory_extensions_* }} placeholders when <memory_root>/extensions exists
214
+ // (prompts.rs build_consolidation_prompt), empty strings otherwise.
215
+ const EXTENSIONS_FOLDER_STRUCTURE = `
216
+ Memory extensions (under {{ memory_extensions_root }}/):
217
+
218
+ - <extension_name>/instructions.md
219
+ - Source-specific guidance for interpreting additional memory signals. If an
220
+ extension folder exists, you must read its instructions.md to determine how to use this memory
221
+ source.
222
+
223
+ If the user has any memory extensions, you MUST read the instructions for each extension to
224
+ determine how to use the memory source. If the workspace diff shows deleted extension resource files,
225
+ remove stale memories derived only from those resources. If it has no extension folders, continue
226
+ with the standard memory inputs only.
227
+ `;
228
+ const EXTENSIONS_PRIMARY_INPUTS = `
229
+ Optional source-specific inputs:
230
+ Under \`{{ memory_extensions_root }}/\`:
231
+
232
+ - \`<extension_name>/instructions.md\`
233
+ - If extension folders exist, read each instructions.md first and follow it when interpreting
234
+ that extension's memory source.
235
+
236
+ If the workspace diff shows deleted memory extension resources, use that extension-specific deletion
237
+ signal to remove stale memories derived only from those resources.
238
+ `;
239
+ export function buildConsolidationPrompt(memoryRoot, diffFileName) {
240
+ const extensionsRoot = path.join(memoryRoot, "extensions");
241
+ let extensionsExist = false;
242
+ try {
243
+ extensionsExist = fs.statSync(extensionsRoot).isDirectory();
244
+ }
245
+ catch { }
246
+ const blockVars = { memory_extensions_root: extensionsRoot };
213
247
  return fillTemplate(readTemplate("consolidation.md"), {
214
248
  memory_root: memoryRoot,
215
249
  phase2_workspace_diff_file: diffFileName,
250
+ memory_extensions_folder_structure: extensionsExist ? fillTemplate(EXTENSIONS_FOLDER_STRUCTURE, blockVars) : "",
251
+ memory_extensions_primary_inputs: extensionsExist ? fillTemplate(EXTENSIONS_PRIMARY_INPUTS, blockVars) : "",
216
252
  });
217
253
  }
218
254
  function readTemplate(name) {
@@ -231,7 +267,8 @@ export function parseExtraction(raw) {
231
267
  if (typeof obj.raw_memory !== "string" || typeof obj.rollout_summary !== "string") {
232
268
  throw new Error("extraction response missing required fields");
233
269
  }
234
- if (!obj.raw_memory.trim() && !obj.rollout_summary.trim()) {
270
+ // codex phase1: either field empty → SucceededNoOutput (not a partial upsert).
271
+ if (!obj.raw_memory.trim() || !obj.rollout_summary.trim()) {
235
272
  return null;
236
273
  }
237
274
  // Guard against the model echoing the format skeleton from the system prompt.
@@ -1,4 +1,4 @@
1
- import { ensureLayout, rebuildRawMemories, writeRolloutSummaries, pruneExtensionResources, writeWorkspaceDiff } from "./workspace.js";
1
+ import { ensureLayout, rebuildRawMemories, writeRolloutSummaries, pruneExtensionResources, writeWorkspaceDiff, validateConsolidationArtifacts, } from "./workspace.js";
2
2
  import { ensureBaseline, captureWorkspaceDiff, resetBaseline, DIFF_ARTIFACT } from "./git-baseline.js";
3
3
  import { consolidateViaSubagent } from "./llm.js";
4
4
  import { invalidateCache } from "./source.js";
@@ -37,9 +37,16 @@ export async function runPhase2(store, opts = DEFAULT_PHASE2_OPTIONS) {
37
37
  writeRolloutSummaries(outputs);
38
38
  pruneExtensionResources(opts.extensionRetentionDays);
39
39
  const diff = await captureWorkspaceDiff();
40
+ // codex: early succeed only when there are no changes AND artifacts are
41
+ // already valid. Invalid/empty summary (e.g. ensureLayout's empty file)
42
+ // falls through so the consolidator can INIT/repair.
40
43
  if (diff.changes.length === 0) {
41
- store.markPhase2Succeeded(claim.ownershipToken, outputs);
42
- return { status: "no_workspace_changes" };
44
+ const valid = validateConsolidationArtifacts();
45
+ if (valid.ok) {
46
+ store.markPhase2Succeeded(claim.ownershipToken, outputs);
47
+ return { status: "no_workspace_changes" };
48
+ }
49
+ console.warn("[opencode-codex-memory] no workspace changes but artifacts invalid; running consolidator:", valid.reason);
43
50
  }
44
51
  writeWorkspaceDiff(diff);
45
52
  let heartbeatLost = false;
@@ -48,8 +55,10 @@ export async function runPhase2(store, opts = DEFAULT_PHASE2_OPTIONS) {
48
55
  heartbeatLost = true;
49
56
  }
50
57
  }, 90_000);
58
+ let agentCompleted = false;
51
59
  try {
52
60
  await consolidateViaSubagent(memoryRoot(), DIFF_ARTIFACT, opts.consolidationModel);
61
+ agentCompleted = true;
53
62
  }
54
63
  finally {
55
64
  clearInterval(heartbeat);
@@ -64,6 +73,17 @@ export async function runPhase2(store, opts = DEFAULT_PHASE2_OPTIONS) {
64
73
  store.markPhase2Failed(claim.ownershipToken, "ownership lost");
65
74
  return { status: "heartbeat_lost" };
66
75
  }
76
+ if (!agentCompleted) {
77
+ store.markPhase2Failed(claim.ownershipToken, "failed_agent");
78
+ return { status: "failed_agent" };
79
+ }
80
+ // codex failed_invalid_artifacts: do not reset baseline on bad output so
81
+ // the next run still sees a diff / can re-INIT.
82
+ const artifacts = validateConsolidationArtifacts();
83
+ if (!artifacts.ok) {
84
+ store.markPhase2Failed(claim.ownershipToken, `failed_invalid_artifacts: ${artifacts.reason}`);
85
+ return { status: "failed_invalid_artifacts" };
86
+ }
67
87
  if (!await resetBaseline()) {
68
88
  store.markPhase2Failed(claim.ownershipToken, "baseline reset failed");
69
89
  return { status: "baseline_reset_failed" };