@ai-agent-forge/plugin-memory 0.85.0

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.
Files changed (148) hide show
  1. package/README.md +65 -0
  2. package/agent-forge.json +11 -0
  3. package/dist/capability.d.ts +182 -0
  4. package/dist/capability.d.ts.map +1 -0
  5. package/dist/capability.js +2565 -0
  6. package/dist/capability.js.map +1 -0
  7. package/dist/entry.d.ts +36 -0
  8. package/dist/entry.d.ts.map +1 -0
  9. package/dist/entry.js +154 -0
  10. package/dist/entry.js.map +1 -0
  11. package/dist/index.d.ts +49 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +49 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/memory/assistant-card.d.ts +31 -0
  16. package/dist/memory/assistant-card.d.ts.map +1 -0
  17. package/dist/memory/assistant-card.js +108 -0
  18. package/dist/memory/assistant-card.js.map +1 -0
  19. package/dist/memory/candidates.d.ts +65 -0
  20. package/dist/memory/candidates.d.ts.map +1 -0
  21. package/dist/memory/candidates.js +100 -0
  22. package/dist/memory/candidates.js.map +1 -0
  23. package/dist/memory/code-memory.d.ts +89 -0
  24. package/dist/memory/code-memory.d.ts.map +1 -0
  25. package/dist/memory/code-memory.js +104 -0
  26. package/dist/memory/code-memory.js.map +1 -0
  27. package/dist/memory/compaction-sequencer.d.ts +63 -0
  28. package/dist/memory/compaction-sequencer.d.ts.map +1 -0
  29. package/dist/memory/compaction-sequencer.js +129 -0
  30. package/dist/memory/compaction-sequencer.js.map +1 -0
  31. package/dist/memory/continuation.d.ts +44 -0
  32. package/dist/memory/continuation.d.ts.map +1 -0
  33. package/dist/memory/continuation.js +49 -0
  34. package/dist/memory/continuation.js.map +1 -0
  35. package/dist/memory/curation.d.ts +58 -0
  36. package/dist/memory/curation.d.ts.map +1 -0
  37. package/dist/memory/curation.js +68 -0
  38. package/dist/memory/curation.js.map +1 -0
  39. package/dist/memory/egress-policy.d.ts +50 -0
  40. package/dist/memory/egress-policy.d.ts.map +1 -0
  41. package/dist/memory/egress-policy.js +71 -0
  42. package/dist/memory/egress-policy.js.map +1 -0
  43. package/dist/memory/embedding-provider.d.ts +70 -0
  44. package/dist/memory/embedding-provider.d.ts.map +1 -0
  45. package/dist/memory/embedding-provider.js +164 -0
  46. package/dist/memory/embedding-provider.js.map +1 -0
  47. package/dist/memory/embedding-reranker.d.ts +56 -0
  48. package/dist/memory/embedding-reranker.d.ts.map +1 -0
  49. package/dist/memory/embedding-reranker.js +109 -0
  50. package/dist/memory/embedding-reranker.js.map +1 -0
  51. package/dist/memory/foundation.d.ts +168 -0
  52. package/dist/memory/foundation.d.ts.map +1 -0
  53. package/dist/memory/foundation.js +487 -0
  54. package/dist/memory/foundation.js.map +1 -0
  55. package/dist/memory/host-module-import.d.ts +25 -0
  56. package/dist/memory/host-module-import.d.ts.map +1 -0
  57. package/dist/memory/host-module-import.js +41 -0
  58. package/dist/memory/host-module-import.js.map +1 -0
  59. package/dist/memory/ledger.d.ts +58 -0
  60. package/dist/memory/ledger.d.ts.map +1 -0
  61. package/dist/memory/ledger.js +315 -0
  62. package/dist/memory/ledger.js.map +1 -0
  63. package/dist/memory/lifecycle.d.ts +124 -0
  64. package/dist/memory/lifecycle.d.ts.map +1 -0
  65. package/dist/memory/lifecycle.js +201 -0
  66. package/dist/memory/lifecycle.js.map +1 -0
  67. package/dist/memory/memory-network.d.ts +55 -0
  68. package/dist/memory/memory-network.d.ts.map +1 -0
  69. package/dist/memory/memory-network.js +70 -0
  70. package/dist/memory/memory-network.js.map +1 -0
  71. package/dist/memory/model-cache-hygiene.d.ts +18 -0
  72. package/dist/memory/model-cache-hygiene.d.ts.map +1 -0
  73. package/dist/memory/model-cache-hygiene.js +38 -0
  74. package/dist/memory/model-cache-hygiene.js.map +1 -0
  75. package/dist/memory/preference-disambiguator.d.ts +43 -0
  76. package/dist/memory/preference-disambiguator.d.ts.map +1 -0
  77. package/dist/memory/preference-disambiguator.js +81 -0
  78. package/dist/memory/preference-disambiguator.js.map +1 -0
  79. package/dist/memory/preference-lifecycle.d.ts +66 -0
  80. package/dist/memory/preference-lifecycle.d.ts.map +1 -0
  81. package/dist/memory/preference-lifecycle.js +129 -0
  82. package/dist/memory/preference-lifecycle.js.map +1 -0
  83. package/dist/memory/preference-promotion.d.ts +87 -0
  84. package/dist/memory/preference-promotion.d.ts.map +1 -0
  85. package/dist/memory/preference-promotion.js +102 -0
  86. package/dist/memory/preference-promotion.js.map +1 -0
  87. package/dist/memory/preference-resolver.d.ts +44 -0
  88. package/dist/memory/preference-resolver.d.ts.map +1 -0
  89. package/dist/memory/preference-resolver.js +107 -0
  90. package/dist/memory/preference-resolver.js.map +1 -0
  91. package/dist/memory/purge-journal.d.ts +76 -0
  92. package/dist/memory/purge-journal.d.ts.map +1 -0
  93. package/dist/memory/purge-journal.js +130 -0
  94. package/dist/memory/purge-journal.js.map +1 -0
  95. package/dist/memory/purge.d.ts +90 -0
  96. package/dist/memory/purge.d.ts.map +1 -0
  97. package/dist/memory/purge.js +138 -0
  98. package/dist/memory/purge.js.map +1 -0
  99. package/dist/memory/recall-agent.d.ts +84 -0
  100. package/dist/memory/recall-agent.d.ts.map +1 -0
  101. package/dist/memory/recall-agent.js +199 -0
  102. package/dist/memory/recall-agent.js.map +1 -0
  103. package/dist/memory/recall-index.d.ts +87 -0
  104. package/dist/memory/recall-index.d.ts.map +1 -0
  105. package/dist/memory/recall-index.js +222 -0
  106. package/dist/memory/recall-index.js.map +1 -0
  107. package/dist/memory/recall-packet.d.ts +121 -0
  108. package/dist/memory/recall-packet.d.ts.map +1 -0
  109. package/dist/memory/recall-packet.js +156 -0
  110. package/dist/memory/recall-packet.js.map +1 -0
  111. package/dist/memory/scheduler-api.d.ts +99 -0
  112. package/dist/memory/scheduler-api.d.ts.map +1 -0
  113. package/dist/memory/scheduler-api.js +93 -0
  114. package/dist/memory/scheduler-api.js.map +1 -0
  115. package/dist/memory/scheduler.d.ts +55 -0
  116. package/dist/memory/scheduler.d.ts.map +1 -0
  117. package/dist/memory/scheduler.js +91 -0
  118. package/dist/memory/scheduler.js.map +1 -0
  119. package/dist/memory/store.d.ts +107 -0
  120. package/dist/memory/store.d.ts.map +1 -0
  121. package/dist/memory/store.js +208 -0
  122. package/dist/memory/store.js.map +1 -0
  123. package/dist/memory/suite-memory.d.ts +208 -0
  124. package/dist/memory/suite-memory.d.ts.map +1 -0
  125. package/dist/memory/suite-memory.js +288 -0
  126. package/dist/memory/suite-memory.js.map +1 -0
  127. package/dist/memory/transfer.d.ts +142 -0
  128. package/dist/memory/transfer.d.ts.map +1 -0
  129. package/dist/memory/transfer.js +210 -0
  130. package/dist/memory/transfer.js.map +1 -0
  131. package/dist/memory/vector-index.d.ts +39 -0
  132. package/dist/memory/vector-index.d.ts.map +1 -0
  133. package/dist/memory/vector-index.js +136 -0
  134. package/dist/memory/vector-index.js.map +1 -0
  135. package/dist/memory/write-budget.d.ts +33 -0
  136. package/dist/memory/write-budget.d.ts.map +1 -0
  137. package/dist/memory/write-budget.js +45 -0
  138. package/dist/memory/write-budget.js.map +1 -0
  139. package/dist/testing/memory-testkit.d.ts +149 -0
  140. package/dist/testing/memory-testkit.d.ts.map +1 -0
  141. package/dist/testing/memory-testkit.js +438 -0
  142. package/dist/testing/memory-testkit.js.map +1 -0
  143. package/dist/utils/sync-sleep.d.ts +2 -0
  144. package/dist/utils/sync-sleep.d.ts.map +1 -0
  145. package/dist/utils/sync-sleep.js +11 -0
  146. package/dist/utils/sync-sleep.js.map +1 -0
  147. package/package.json +56 -0
  148. package/plugin.json +10 -0
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Memory Foundation — purge gate, replica registry, and barrier (1C.6c).
3
+ *
4
+ * D-035 semantics: the first-party V1 runs ONE real canonical replica. The
5
+ * registry and barrier stay generic (they confirm whatever set was frozen),
6
+ * but capabilities that require the distributed protocol — registering or
7
+ * reviving replicas while a purge batch is frozen — return an explicit
8
+ * `unsupported` instead of faking multi-replica acks.
9
+ *
10
+ * Purge flow: authorize batch (freeze replicaSetRevision + replica set) →
11
+ * idempotently purge every frozen replica → barrier confirms all → completed.
12
+ * Any replica failure keeps the batch `purge_eligible` with a structured
13
+ * per-replica error; nothing unknown is counted as purged.
14
+ */
15
+ export interface MemoryReplicaV1 {
16
+ readonly replicaId: string;
17
+ readonly kind: "canonical" | "index" | "cache" | "wal" | "backup";
18
+ readonly ownerScope: string;
19
+ readonly contractVersion: string;
20
+ readonly healthy: boolean;
21
+ }
22
+ export type MemoryPurgeBatchStateV1 = "authorized" | "completed" | "purge_eligible";
23
+ /**
24
+ * Structured purge authorization provenance (记忆系统设计 §10). A trusted
25
+ * user configuration or Profile policy service must issue it:
26
+ * - mode "user-immediate" requires a non-empty confirmationRef;
27
+ * - mode "profile-policy" requires a non-empty policyServiceRef;
28
+ * - mode "retention-expiry" requires no extra reference.
29
+ * Plain conversation turns may only create PENDING requests, never an
30
+ * authorization ref.
31
+ */
32
+ export interface MemoryPurgeAuthorizationRefV1 {
33
+ readonly mode: "user-immediate" | "retention-expiry" | "profile-policy";
34
+ readonly issuedAt: number;
35
+ readonly issuedBy: string;
36
+ readonly confirmationRef?: string;
37
+ readonly policyServiceRef?: string;
38
+ }
39
+ export interface MemoryPurgeBatchV1 {
40
+ readonly batchId: string;
41
+ readonly memoryIds: readonly string[];
42
+ readonly reason: string;
43
+ readonly authorizedBy: string;
44
+ /** Structured authorization provenance (记忆系统设计 §10). */
45
+ readonly authorizationRef?: MemoryPurgeAuthorizationRefV1;
46
+ readonly replicaSetRevision: number;
47
+ readonly replicas: readonly MemoryReplicaV1[];
48
+ readonly state: MemoryPurgeBatchStateV1;
49
+ readonly confirmedReplicas?: readonly string[];
50
+ readonly failedReplicas?: readonly {
51
+ readonly replicaId: string;
52
+ readonly error: string;
53
+ }[];
54
+ }
55
+ /** D-035 explicit-unsupported marker for capabilities the V1 single replica cannot serve. */
56
+ export declare class MemoryPurgeUnsupportedError extends Error {
57
+ readonly code = "memory_purge_unsupported";
58
+ constructor(reason: string);
59
+ }
60
+ export interface MemoryReplicaRegistryV1 {
61
+ register(replica: MemoryReplicaV1): void;
62
+ get(replicaId: string): MemoryReplicaV1 | undefined;
63
+ freeze(): {
64
+ readonly replicaSetRevision: number;
65
+ readonly replicas: readonly MemoryReplicaV1[];
66
+ };
67
+ unfreeze(): void;
68
+ readonly frozen: boolean;
69
+ }
70
+ export declare function createMemoryReplicaRegistry(): MemoryReplicaRegistryV1;
71
+ export interface MemoryPurgeGateV1 {
72
+ authorizeBatch(input: {
73
+ readonly memoryIds: readonly string[];
74
+ readonly reason: string;
75
+ readonly authorizedBy: string;
76
+ readonly authorizationRef?: MemoryPurgeAuthorizationRefV1;
77
+ }): MemoryPurgeBatchV1;
78
+ /**
79
+ * Runs the frozen replica set through the injected purge function and the
80
+ * barrier. `purgeReplica` throws to signal a replica-level failure.
81
+ */
82
+ executeBatch(batchId: string, purgeReplica: (replica: MemoryReplicaV1, memoryIds: readonly string[]) => void): MemoryPurgeBatchV1;
83
+ batchState(batchId: string): MemoryPurgeBatchV1 | undefined;
84
+ }
85
+ export declare function createMemoryPurgeGate(options: {
86
+ readonly registry: MemoryReplicaRegistryV1;
87
+ readonly now?: () => number;
88
+ readonly batchIdFactory?: () => string;
89
+ }): MemoryPurgeGateV1;
90
+ //# sourceMappingURL=purge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"purge.d.ts","sourceRoot":"","sources":["../../src/memory/purge.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Memory Foundation — purge gate, replica registry, and barrier (1C.6c).\n *\n * D-035 semantics: the first-party V1 runs ONE real canonical replica. The\n * registry and barrier stay generic (they confirm whatever set was frozen),\n * but capabilities that require the distributed protocol — registering or\n * reviving replicas while a purge batch is frozen — return an explicit\n * `unsupported` instead of faking multi-replica acks.\n *\n * Purge flow: authorize batch (freeze replicaSetRevision + replica set) →\n * idempotently purge every frozen replica → barrier confirms all → completed.\n * Any replica failure keeps the batch `purge_eligible` with a structured\n * per-replica error; nothing unknown is counted as purged.\n */\n\nexport interface MemoryReplicaV1 {\n\treadonly replicaId: string;\n\treadonly kind: \"canonical\" | \"index\" | \"cache\" | \"wal\" | \"backup\";\n\treadonly ownerScope: string;\n\treadonly contractVersion: string;\n\treadonly healthy: boolean;\n}\n\nexport type MemoryPurgeBatchStateV1 = \"authorized\" | \"completed\" | \"purge_eligible\";\n\n/**\n * Structured purge authorization provenance (记忆系统设计 §10). A trusted\n * user configuration or Profile policy service must issue it:\n * - mode \"user-immediate\" requires a non-empty confirmationRef;\n * - mode \"profile-policy\" requires a non-empty policyServiceRef;\n * - mode \"retention-expiry\" requires no extra reference.\n * Plain conversation turns may only create PENDING requests, never an\n * authorization ref.\n */\nexport interface MemoryPurgeAuthorizationRefV1 {\n\treadonly mode: \"user-immediate\" | \"retention-expiry\" | \"profile-policy\";\n\treadonly issuedAt: number;\n\treadonly issuedBy: string;\n\treadonly confirmationRef?: string;\n\treadonly policyServiceRef?: string;\n}\n\nexport interface MemoryPurgeBatchV1 {\n\treadonly batchId: string;\n\treadonly memoryIds: readonly string[];\n\treadonly reason: string;\n\treadonly authorizedBy: string;\n\t/** Structured authorization provenance (记忆系统设计 §10). */\n\treadonly authorizationRef?: MemoryPurgeAuthorizationRefV1;\n\treadonly replicaSetRevision: number;\n\treadonly replicas: readonly MemoryReplicaV1[];\n\treadonly state: MemoryPurgeBatchStateV1;\n\treadonly confirmedReplicas?: readonly string[];\n\treadonly failedReplicas?: readonly { readonly replicaId: string; readonly error: string }[];\n}\n\nfunction assertNonEmptyString(value: unknown, label: string): asserts value is string {\n\tif (typeof value !== \"string\" || value.trim().length === 0) throw new Error(`${label} must be a non-empty string`);\n}\n\n/** D-035 explicit-unsupported marker for capabilities the V1 single replica cannot serve. */\nexport class MemoryPurgeUnsupportedError extends Error {\n\treadonly code = \"memory_purge_unsupported\";\n\tconstructor(reason: string) {\n\t\tsuper(`Memory purge unsupported: ${reason}`);\n\t\tthis.name = \"MemoryPurgeUnsupportedError\";\n\t}\n}\n\nexport interface MemoryReplicaRegistryV1 {\n\tregister(replica: MemoryReplicaV1): void;\n\tget(replicaId: string): MemoryReplicaV1 | undefined;\n\tfreeze(): { readonly replicaSetRevision: number; readonly replicas: readonly MemoryReplicaV1[] };\n\tunfreeze(): void;\n\treadonly frozen: boolean;\n}\n\nexport function createMemoryReplicaRegistry(): MemoryReplicaRegistryV1 {\n\tconst replicas = new Map<string, MemoryReplicaV1>();\n\tlet replicaSetRevision = 0;\n\tlet frozen = false;\n\treturn {\n\t\tregister(replica) {\n\t\t\tassertNonEmptyString(replica.replicaId, \"Replica.replicaId\");\n\t\t\tif (frozen) {\n\t\t\t\tthrow new MemoryPurgeUnsupportedError(\n\t\t\t\t\t\"a purge batch is frozen; new or rebuilt replicas must pass the barrier before serving recall\",\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (replicas.has(replica.replicaId)) throw new Error(`Replica already registered: ${replica.replicaId}`);\n\t\t\treplicas.set(replica.replicaId, Object.freeze({ ...replica }));\n\t\t},\n\t\tget(replicaId) {\n\t\t\treturn replicas.get(replicaId);\n\t\t},\n\t\tfreeze() {\n\t\t\tfrozen = true;\n\t\t\treplicaSetRevision += 1;\n\t\t\treturn {\n\t\t\t\treplicaSetRevision,\n\t\t\t\treplicas: Object.freeze([...replicas.values()].map((replica) => Object.freeze({ ...replica }))),\n\t\t\t};\n\t\t},\n\t\tunfreeze() {\n\t\t\tfrozen = false;\n\t\t},\n\t\tget frozen() {\n\t\t\treturn frozen;\n\t\t},\n\t};\n}\n\nexport interface MemoryPurgeGateV1 {\n\tauthorizeBatch(input: {\n\t\treadonly memoryIds: readonly string[];\n\t\treadonly reason: string;\n\t\treadonly authorizedBy: string;\n\t\treadonly authorizationRef?: MemoryPurgeAuthorizationRefV1;\n\t}): MemoryPurgeBatchV1;\n\t/**\n\t * Runs the frozen replica set through the injected purge function and the\n\t * barrier. `purgeReplica` throws to signal a replica-level failure.\n\t */\n\texecuteBatch(\n\t\tbatchId: string,\n\t\tpurgeReplica: (replica: MemoryReplicaV1, memoryIds: readonly string[]) => void,\n\t): MemoryPurgeBatchV1;\n\tbatchState(batchId: string): MemoryPurgeBatchV1 | undefined;\n}\n\nexport function createMemoryPurgeGate(options: {\n\treadonly registry: MemoryReplicaRegistryV1;\n\treadonly now?: () => number;\n\treadonly batchIdFactory?: () => string;\n}): MemoryPurgeGateV1 {\n\tconst now = options.now ?? (() => Date.now());\n\tconst batches = new Map<string, MemoryPurgeBatchV1>();\n\tlet batchSequence = 0;\n\treturn {\n\t\tauthorizeBatch({ memoryIds, reason, authorizedBy, authorizationRef }) {\n\t\t\tassertNonEmptyString(reason, \"Purge batch reason\");\n\t\t\tassertNonEmptyString(authorizedBy, \"Purge batch authorizedBy\");\n\t\t\tif (!Array.isArray(memoryIds) || memoryIds.length === 0) {\n\t\t\t\tthrow new Error(\"Purge batch requires at least one memoryId\");\n\t\t\t}\n\t\t\tif (authorizationRef !== undefined) {\n\t\t\t\tif (\n\t\t\t\t\tauthorizationRef.mode === \"user-immediate\" &&\n\t\t\t\t\t(typeof authorizationRef.confirmationRef !== \"string\" ||\n\t\t\t\t\t\tauthorizationRef.confirmationRef.trim().length === 0)\n\t\t\t\t) {\n\t\t\t\t\tthrow new Error(\"user-immediate purge authorization requires a non-empty confirmationRef\");\n\t\t\t\t}\n\t\t\t\tif (\n\t\t\t\t\tauthorizationRef.mode === \"profile-policy\" &&\n\t\t\t\t\t(typeof authorizationRef.policyServiceRef !== \"string\" ||\n\t\t\t\t\t\tauthorizationRef.policyServiceRef.trim().length === 0)\n\t\t\t\t) {\n\t\t\t\t\tthrow new Error(\"profile-policy purge authorization requires a non-empty policyServiceRef\");\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst frozenSet = options.registry.freeze();\n\t\t\tconst batch: MemoryPurgeBatchV1 = Object.freeze({\n\t\t\t\tbatchId: options.batchIdFactory?.() ?? `purge-${++batchSequence}-${now()}`,\n\t\t\t\tmemoryIds: Object.freeze([...memoryIds]),\n\t\t\t\treason,\n\t\t\t\tauthorizedBy,\n\t\t\t\t...(authorizationRef === undefined ? {} : { authorizationRef }),\n\t\t\t\treplicaSetRevision: frozenSet.replicaSetRevision,\n\t\t\t\treplicas: frozenSet.replicas,\n\t\t\t\tstate: \"authorized\",\n\t\t\t});\n\t\t\tbatches.set(batch.batchId, batch);\n\t\t\treturn batch;\n\t\t},\n\t\texecuteBatch(batchId, purgeReplica) {\n\t\t\tconst batch = batches.get(batchId);\n\t\t\tif (!batch) throw new Error(`Unknown purge batch: ${batchId}`);\n\t\t\t// Completed batches are idempotent no-ops; purge_eligible batches allow\n\t\t\t// an idempotent retry (replica purge is expected to be re-runnable).\n\t\t\tif (batch.state === \"completed\") return batch;\n\t\t\tconst confirmedReplicas: string[] = [];\n\t\t\tconst failedReplicas: { replicaId: string; error: string }[] = [];\n\t\t\tfor (const replica of batch.replicas) {\n\t\t\t\ttry {\n\t\t\t\t\tpurgeReplica(replica, batch.memoryIds);\n\t\t\t\t\tconfirmedReplicas.push(replica.replicaId);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tfailedReplicas.push({\n\t\t\t\t\t\treplicaId: replica.replicaId,\n\t\t\t\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Barrier: every frozen replica must confirm; partial failure keeps\n\t\t\t// the batch purge_eligible for an idempotent retry — never reported\n\t\t\t// as success.\n\t\t\tconst state: MemoryPurgeBatchStateV1 = failedReplicas.length === 0 ? \"completed\" : \"purge_eligible\";\n\t\t\tconst next: MemoryPurgeBatchV1 = Object.freeze({\n\t\t\t\t...batch,\n\t\t\t\tstate,\n\t\t\t\tconfirmedReplicas: Object.freeze(confirmedReplicas),\n\t\t\t\t...(failedReplicas.length > 0 ? { failedReplicas: Object.freeze(failedReplicas) } : {}),\n\t\t\t});\n\t\t\tbatches.set(batchId, next);\n\t\t\toptions.registry.unfreeze();\n\t\t\treturn next;\n\t\t},\n\t\tbatchState(batchId) {\n\t\t\treturn batches.get(batchId);\n\t\t},\n\t};\n}\n"]}
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Memory Foundation — purge gate, replica registry, and barrier (1C.6c).
3
+ *
4
+ * D-035 semantics: the first-party V1 runs ONE real canonical replica. The
5
+ * registry and barrier stay generic (they confirm whatever set was frozen),
6
+ * but capabilities that require the distributed protocol — registering or
7
+ * reviving replicas while a purge batch is frozen — return an explicit
8
+ * `unsupported` instead of faking multi-replica acks.
9
+ *
10
+ * Purge flow: authorize batch (freeze replicaSetRevision + replica set) →
11
+ * idempotently purge every frozen replica → barrier confirms all → completed.
12
+ * Any replica failure keeps the batch `purge_eligible` with a structured
13
+ * per-replica error; nothing unknown is counted as purged.
14
+ */
15
+ function assertNonEmptyString(value, label) {
16
+ if (typeof value !== "string" || value.trim().length === 0)
17
+ throw new Error(`${label} must be a non-empty string`);
18
+ }
19
+ /** D-035 explicit-unsupported marker for capabilities the V1 single replica cannot serve. */
20
+ export class MemoryPurgeUnsupportedError extends Error {
21
+ code = "memory_purge_unsupported";
22
+ constructor(reason) {
23
+ super(`Memory purge unsupported: ${reason}`);
24
+ this.name = "MemoryPurgeUnsupportedError";
25
+ }
26
+ }
27
+ export function createMemoryReplicaRegistry() {
28
+ const replicas = new Map();
29
+ let replicaSetRevision = 0;
30
+ let frozen = false;
31
+ return {
32
+ register(replica) {
33
+ assertNonEmptyString(replica.replicaId, "Replica.replicaId");
34
+ if (frozen) {
35
+ throw new MemoryPurgeUnsupportedError("a purge batch is frozen; new or rebuilt replicas must pass the barrier before serving recall");
36
+ }
37
+ if (replicas.has(replica.replicaId))
38
+ throw new Error(`Replica already registered: ${replica.replicaId}`);
39
+ replicas.set(replica.replicaId, Object.freeze({ ...replica }));
40
+ },
41
+ get(replicaId) {
42
+ return replicas.get(replicaId);
43
+ },
44
+ freeze() {
45
+ frozen = true;
46
+ replicaSetRevision += 1;
47
+ return {
48
+ replicaSetRevision,
49
+ replicas: Object.freeze([...replicas.values()].map((replica) => Object.freeze({ ...replica }))),
50
+ };
51
+ },
52
+ unfreeze() {
53
+ frozen = false;
54
+ },
55
+ get frozen() {
56
+ return frozen;
57
+ },
58
+ };
59
+ }
60
+ export function createMemoryPurgeGate(options) {
61
+ const now = options.now ?? (() => Date.now());
62
+ const batches = new Map();
63
+ let batchSequence = 0;
64
+ return {
65
+ authorizeBatch({ memoryIds, reason, authorizedBy, authorizationRef }) {
66
+ assertNonEmptyString(reason, "Purge batch reason");
67
+ assertNonEmptyString(authorizedBy, "Purge batch authorizedBy");
68
+ if (!Array.isArray(memoryIds) || memoryIds.length === 0) {
69
+ throw new Error("Purge batch requires at least one memoryId");
70
+ }
71
+ if (authorizationRef !== undefined) {
72
+ if (authorizationRef.mode === "user-immediate" &&
73
+ (typeof authorizationRef.confirmationRef !== "string" ||
74
+ authorizationRef.confirmationRef.trim().length === 0)) {
75
+ throw new Error("user-immediate purge authorization requires a non-empty confirmationRef");
76
+ }
77
+ if (authorizationRef.mode === "profile-policy" &&
78
+ (typeof authorizationRef.policyServiceRef !== "string" ||
79
+ authorizationRef.policyServiceRef.trim().length === 0)) {
80
+ throw new Error("profile-policy purge authorization requires a non-empty policyServiceRef");
81
+ }
82
+ }
83
+ const frozenSet = options.registry.freeze();
84
+ const batch = Object.freeze({
85
+ batchId: options.batchIdFactory?.() ?? `purge-${++batchSequence}-${now()}`,
86
+ memoryIds: Object.freeze([...memoryIds]),
87
+ reason,
88
+ authorizedBy,
89
+ ...(authorizationRef === undefined ? {} : { authorizationRef }),
90
+ replicaSetRevision: frozenSet.replicaSetRevision,
91
+ replicas: frozenSet.replicas,
92
+ state: "authorized",
93
+ });
94
+ batches.set(batch.batchId, batch);
95
+ return batch;
96
+ },
97
+ executeBatch(batchId, purgeReplica) {
98
+ const batch = batches.get(batchId);
99
+ if (!batch)
100
+ throw new Error(`Unknown purge batch: ${batchId}`);
101
+ // Completed batches are idempotent no-ops; purge_eligible batches allow
102
+ // an idempotent retry (replica purge is expected to be re-runnable).
103
+ if (batch.state === "completed")
104
+ return batch;
105
+ const confirmedReplicas = [];
106
+ const failedReplicas = [];
107
+ for (const replica of batch.replicas) {
108
+ try {
109
+ purgeReplica(replica, batch.memoryIds);
110
+ confirmedReplicas.push(replica.replicaId);
111
+ }
112
+ catch (error) {
113
+ failedReplicas.push({
114
+ replicaId: replica.replicaId,
115
+ error: error instanceof Error ? error.message : String(error),
116
+ });
117
+ }
118
+ }
119
+ // Barrier: every frozen replica must confirm; partial failure keeps
120
+ // the batch purge_eligible for an idempotent retry — never reported
121
+ // as success.
122
+ const state = failedReplicas.length === 0 ? "completed" : "purge_eligible";
123
+ const next = Object.freeze({
124
+ ...batch,
125
+ state,
126
+ confirmedReplicas: Object.freeze(confirmedReplicas),
127
+ ...(failedReplicas.length > 0 ? { failedReplicas: Object.freeze(failedReplicas) } : {}),
128
+ });
129
+ batches.set(batchId, next);
130
+ options.registry.unfreeze();
131
+ return next;
132
+ },
133
+ batchState(batchId) {
134
+ return batches.get(batchId);
135
+ },
136
+ };
137
+ }
138
+ //# sourceMappingURL=purge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"purge.js","sourceRoot":"","sources":["../../src/memory/purge.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Memory Foundation — purge gate, replica registry, and barrier (1C.6c).\n *\n * D-035 semantics: the first-party V1 runs ONE real canonical replica. The\n * registry and barrier stay generic (they confirm whatever set was frozen),\n * but capabilities that require the distributed protocol — registering or\n * reviving replicas while a purge batch is frozen — return an explicit\n * `unsupported` instead of faking multi-replica acks.\n *\n * Purge flow: authorize batch (freeze replicaSetRevision + replica set) →\n * idempotently purge every frozen replica → barrier confirms all → completed.\n * Any replica failure keeps the batch `purge_eligible` with a structured\n * per-replica error; nothing unknown is counted as purged.\n */\n\nexport interface MemoryReplicaV1 {\n\treadonly replicaId: string;\n\treadonly kind: \"canonical\" | \"index\" | \"cache\" | \"wal\" | \"backup\";\n\treadonly ownerScope: string;\n\treadonly contractVersion: string;\n\treadonly healthy: boolean;\n}\n\nexport type MemoryPurgeBatchStateV1 = \"authorized\" | \"completed\" | \"purge_eligible\";\n\n/**\n * Structured purge authorization provenance (记忆系统设计 §10). A trusted\n * user configuration or Profile policy service must issue it:\n * - mode \"user-immediate\" requires a non-empty confirmationRef;\n * - mode \"profile-policy\" requires a non-empty policyServiceRef;\n * - mode \"retention-expiry\" requires no extra reference.\n * Plain conversation turns may only create PENDING requests, never an\n * authorization ref.\n */\nexport interface MemoryPurgeAuthorizationRefV1 {\n\treadonly mode: \"user-immediate\" | \"retention-expiry\" | \"profile-policy\";\n\treadonly issuedAt: number;\n\treadonly issuedBy: string;\n\treadonly confirmationRef?: string;\n\treadonly policyServiceRef?: string;\n}\n\nexport interface MemoryPurgeBatchV1 {\n\treadonly batchId: string;\n\treadonly memoryIds: readonly string[];\n\treadonly reason: string;\n\treadonly authorizedBy: string;\n\t/** Structured authorization provenance (记忆系统设计 §10). */\n\treadonly authorizationRef?: MemoryPurgeAuthorizationRefV1;\n\treadonly replicaSetRevision: number;\n\treadonly replicas: readonly MemoryReplicaV1[];\n\treadonly state: MemoryPurgeBatchStateV1;\n\treadonly confirmedReplicas?: readonly string[];\n\treadonly failedReplicas?: readonly { readonly replicaId: string; readonly error: string }[];\n}\n\nfunction assertNonEmptyString(value: unknown, label: string): asserts value is string {\n\tif (typeof value !== \"string\" || value.trim().length === 0) throw new Error(`${label} must be a non-empty string`);\n}\n\n/** D-035 explicit-unsupported marker for capabilities the V1 single replica cannot serve. */\nexport class MemoryPurgeUnsupportedError extends Error {\n\treadonly code = \"memory_purge_unsupported\";\n\tconstructor(reason: string) {\n\t\tsuper(`Memory purge unsupported: ${reason}`);\n\t\tthis.name = \"MemoryPurgeUnsupportedError\";\n\t}\n}\n\nexport interface MemoryReplicaRegistryV1 {\n\tregister(replica: MemoryReplicaV1): void;\n\tget(replicaId: string): MemoryReplicaV1 | undefined;\n\tfreeze(): { readonly replicaSetRevision: number; readonly replicas: readonly MemoryReplicaV1[] };\n\tunfreeze(): void;\n\treadonly frozen: boolean;\n}\n\nexport function createMemoryReplicaRegistry(): MemoryReplicaRegistryV1 {\n\tconst replicas = new Map<string, MemoryReplicaV1>();\n\tlet replicaSetRevision = 0;\n\tlet frozen = false;\n\treturn {\n\t\tregister(replica) {\n\t\t\tassertNonEmptyString(replica.replicaId, \"Replica.replicaId\");\n\t\t\tif (frozen) {\n\t\t\t\tthrow new MemoryPurgeUnsupportedError(\n\t\t\t\t\t\"a purge batch is frozen; new or rebuilt replicas must pass the barrier before serving recall\",\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (replicas.has(replica.replicaId)) throw new Error(`Replica already registered: ${replica.replicaId}`);\n\t\t\treplicas.set(replica.replicaId, Object.freeze({ ...replica }));\n\t\t},\n\t\tget(replicaId) {\n\t\t\treturn replicas.get(replicaId);\n\t\t},\n\t\tfreeze() {\n\t\t\tfrozen = true;\n\t\t\treplicaSetRevision += 1;\n\t\t\treturn {\n\t\t\t\treplicaSetRevision,\n\t\t\t\treplicas: Object.freeze([...replicas.values()].map((replica) => Object.freeze({ ...replica }))),\n\t\t\t};\n\t\t},\n\t\tunfreeze() {\n\t\t\tfrozen = false;\n\t\t},\n\t\tget frozen() {\n\t\t\treturn frozen;\n\t\t},\n\t};\n}\n\nexport interface MemoryPurgeGateV1 {\n\tauthorizeBatch(input: {\n\t\treadonly memoryIds: readonly string[];\n\t\treadonly reason: string;\n\t\treadonly authorizedBy: string;\n\t\treadonly authorizationRef?: MemoryPurgeAuthorizationRefV1;\n\t}): MemoryPurgeBatchV1;\n\t/**\n\t * Runs the frozen replica set through the injected purge function and the\n\t * barrier. `purgeReplica` throws to signal a replica-level failure.\n\t */\n\texecuteBatch(\n\t\tbatchId: string,\n\t\tpurgeReplica: (replica: MemoryReplicaV1, memoryIds: readonly string[]) => void,\n\t): MemoryPurgeBatchV1;\n\tbatchState(batchId: string): MemoryPurgeBatchV1 | undefined;\n}\n\nexport function createMemoryPurgeGate(options: {\n\treadonly registry: MemoryReplicaRegistryV1;\n\treadonly now?: () => number;\n\treadonly batchIdFactory?: () => string;\n}): MemoryPurgeGateV1 {\n\tconst now = options.now ?? (() => Date.now());\n\tconst batches = new Map<string, MemoryPurgeBatchV1>();\n\tlet batchSequence = 0;\n\treturn {\n\t\tauthorizeBatch({ memoryIds, reason, authorizedBy, authorizationRef }) {\n\t\t\tassertNonEmptyString(reason, \"Purge batch reason\");\n\t\t\tassertNonEmptyString(authorizedBy, \"Purge batch authorizedBy\");\n\t\t\tif (!Array.isArray(memoryIds) || memoryIds.length === 0) {\n\t\t\t\tthrow new Error(\"Purge batch requires at least one memoryId\");\n\t\t\t}\n\t\t\tif (authorizationRef !== undefined) {\n\t\t\t\tif (\n\t\t\t\t\tauthorizationRef.mode === \"user-immediate\" &&\n\t\t\t\t\t(typeof authorizationRef.confirmationRef !== \"string\" ||\n\t\t\t\t\t\tauthorizationRef.confirmationRef.trim().length === 0)\n\t\t\t\t) {\n\t\t\t\t\tthrow new Error(\"user-immediate purge authorization requires a non-empty confirmationRef\");\n\t\t\t\t}\n\t\t\t\tif (\n\t\t\t\t\tauthorizationRef.mode === \"profile-policy\" &&\n\t\t\t\t\t(typeof authorizationRef.policyServiceRef !== \"string\" ||\n\t\t\t\t\t\tauthorizationRef.policyServiceRef.trim().length === 0)\n\t\t\t\t) {\n\t\t\t\t\tthrow new Error(\"profile-policy purge authorization requires a non-empty policyServiceRef\");\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst frozenSet = options.registry.freeze();\n\t\t\tconst batch: MemoryPurgeBatchV1 = Object.freeze({\n\t\t\t\tbatchId: options.batchIdFactory?.() ?? `purge-${++batchSequence}-${now()}`,\n\t\t\t\tmemoryIds: Object.freeze([...memoryIds]),\n\t\t\t\treason,\n\t\t\t\tauthorizedBy,\n\t\t\t\t...(authorizationRef === undefined ? {} : { authorizationRef }),\n\t\t\t\treplicaSetRevision: frozenSet.replicaSetRevision,\n\t\t\t\treplicas: frozenSet.replicas,\n\t\t\t\tstate: \"authorized\",\n\t\t\t});\n\t\t\tbatches.set(batch.batchId, batch);\n\t\t\treturn batch;\n\t\t},\n\t\texecuteBatch(batchId, purgeReplica) {\n\t\t\tconst batch = batches.get(batchId);\n\t\t\tif (!batch) throw new Error(`Unknown purge batch: ${batchId}`);\n\t\t\t// Completed batches are idempotent no-ops; purge_eligible batches allow\n\t\t\t// an idempotent retry (replica purge is expected to be re-runnable).\n\t\t\tif (batch.state === \"completed\") return batch;\n\t\t\tconst confirmedReplicas: string[] = [];\n\t\t\tconst failedReplicas: { replicaId: string; error: string }[] = [];\n\t\t\tfor (const replica of batch.replicas) {\n\t\t\t\ttry {\n\t\t\t\t\tpurgeReplica(replica, batch.memoryIds);\n\t\t\t\t\tconfirmedReplicas.push(replica.replicaId);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tfailedReplicas.push({\n\t\t\t\t\t\treplicaId: replica.replicaId,\n\t\t\t\t\t\terror: error instanceof Error ? error.message : String(error),\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Barrier: every frozen replica must confirm; partial failure keeps\n\t\t\t// the batch purge_eligible for an idempotent retry — never reported\n\t\t\t// as success.\n\t\t\tconst state: MemoryPurgeBatchStateV1 = failedReplicas.length === 0 ? \"completed\" : \"purge_eligible\";\n\t\t\tconst next: MemoryPurgeBatchV1 = Object.freeze({\n\t\t\t\t...batch,\n\t\t\t\tstate,\n\t\t\t\tconfirmedReplicas: Object.freeze(confirmedReplicas),\n\t\t\t\t...(failedReplicas.length > 0 ? { failedReplicas: Object.freeze(failedReplicas) } : {}),\n\t\t\t});\n\t\t\tbatches.set(batchId, next);\n\t\t\toptions.registry.unfreeze();\n\t\t\treturn next;\n\t\t},\n\t\tbatchState(batchId) {\n\t\t\treturn batches.get(batchId);\n\t\t},\n\t};\n}\n"]}
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Memory Foundation — memory-recall-agent isolation and Scheduler operation
3
+ * (1C.5b).
4
+ *
5
+ * Recall never runs inside the main agent's business session: the Memory
6
+ * Scheduler executes it through a dedicated memory-recall-agent bound to the
7
+ * instance's scheduler lease. The calling agent cannot pass an owner — the
8
+ * owner comes from the lease — so the request can never widen its visibility.
9
+ * Every failure (no lease, scheduler unavailable, cancelled, invalid anchors,
10
+ * budget violations) is a STRUCTURED failed packet; nothing degrades into an
11
+ * unbounded query. The main agent only ever receives the bounded packet
12
+ * (1C.5a).
13
+ */
14
+ import type { MemoryAtomV1, MemorySuiteFilterStatsV1 } from "./foundation.ts";
15
+ import type { MemoryRecallQueryV1, MemoryScopeV1 } from "./recall-index.ts";
16
+ import type { MemoryRecallPacketV1 } from "./recall-packet.ts";
17
+ import { type MemoryRecallBudgetContributionV1, type MemoryRecallBudgetsV1 } from "./recall-packet.ts";
18
+ import type { MemorySchedulerV1 } from "./scheduler.ts";
19
+ export interface MemoryRecallAgentInputV1 {
20
+ /** The calling AgentInstance; its active scheduler lease authorizes the recall. */
21
+ readonly instanceId: string;
22
+ readonly query?: {
23
+ readonly scope?: MemoryScopeV1;
24
+ /**
25
+ * Suite-scoped read boundary (方案系统设计 §6.1/§11): only atoms visible
26
+ * in this suite are recalled; legacy (suiteId-less) atoms are skipped
27
+ * fail-safe and reported via the packet's `suiteFilter` counters.
28
+ */
29
+ readonly suiteId?: string;
30
+ readonly timeRange?: {
31
+ readonly from?: string;
32
+ readonly to?: string;
33
+ };
34
+ readonly scenes?: readonly string[];
35
+ readonly projects?: readonly string[];
36
+ readonly tasks?: readonly string[];
37
+ readonly facets?: readonly {
38
+ readonly namespace: string;
39
+ readonly key?: string;
40
+ readonly value?: string;
41
+ }[];
42
+ readonly limit?: number;
43
+ readonly offset?: number;
44
+ /** Exact-lookup fast path (§5.2): bypasses anchors and scoring. */
45
+ readonly memoryIdEquals?: string;
46
+ };
47
+ /** Caller budgets may only tighten the resolved limits. */
48
+ readonly requestedBudgets?: MemoryRecallBudgetContributionV1;
49
+ readonly signal?: AbortSignal;
50
+ }
51
+ export interface MemoryRecallAgentErrorV1 {
52
+ readonly code: string;
53
+ readonly message: string;
54
+ readonly stage: "authorize" | "anchor" | "query" | "model" | "validate" | "serialize" | "purge";
55
+ readonly retryable: boolean;
56
+ }
57
+ export interface MemoryRecallAgentOperationV1 {
58
+ readonly leaseId: string;
59
+ readonly packet: Promise<MemoryRecallPacketV1>;
60
+ /** Awaits the bounded packet; cancellation and failures resolve, never throw. */
61
+ wait(): Promise<MemoryRecallPacketV1>;
62
+ }
63
+ export interface MemoryRecallAgentV1 {
64
+ recall(input: MemoryRecallAgentInputV1): MemoryRecallAgentOperationV1;
65
+ }
66
+ export interface MemoryRecallAgentOptionsV1 {
67
+ readonly scheduler: MemorySchedulerV1;
68
+ readonly index: {
69
+ query(query: MemoryRecallQueryV1): {
70
+ items: readonly {
71
+ atom: MemoryAtomV1;
72
+ }[];
73
+ total: number;
74
+ /** Present when the query carried a suiteId (suite read boundary). */
75
+ readonly suiteFilter?: MemorySuiteFilterStatsV1;
76
+ };
77
+ };
78
+ readonly policyDefaults: MemoryRecallBudgetsV1;
79
+ readonly hostLimits: MemoryRecallBudgetsV1;
80
+ readonly now?: () => number;
81
+ }
82
+ /** Creates the memory-recall-agent executor bound to the scheduler and recall index. */
83
+ export declare function createMemoryRecallAgent(options: MemoryRecallAgentOptionsV1): MemoryRecallAgentV1;
84
+ //# sourceMappingURL=recall-agent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recall-agent.d.ts","sourceRoot":"","sources":["../../src/memory/recall-agent.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Memory Foundation — memory-recall-agent isolation and Scheduler operation\n * (1C.5b).\n *\n * Recall never runs inside the main agent's business session: the Memory\n * Scheduler executes it through a dedicated memory-recall-agent bound to the\n * instance's scheduler lease. The calling agent cannot pass an owner — the\n * owner comes from the lease — so the request can never widen its visibility.\n * Every failure (no lease, scheduler unavailable, cancelled, invalid anchors,\n * budget violations) is a STRUCTURED failed packet; nothing degrades into an\n * unbounded query. The main agent only ever receives the bounded packet\n * (1C.5a).\n */\n\nimport type { MemoryAtomV1, MemorySuiteFilterStatsV1 } from \"./foundation.ts\";\nimport type { MemoryRecallQueryV1, MemoryScopeV1 } from \"./recall-index.ts\";\nimport type { MemoryRecallPacketV1 } from \"./recall-packet.ts\";\nimport {\n\tbuildMemoryRecallPacketV1,\n\ttype MemoryRecallBudgetContributionV1,\n\ttype MemoryRecallBudgetsV1,\n\tresolveMemoryRecallBudgets,\n} from \"./recall-packet.ts\";\nimport type { MemorySchedulerV1 } from \"./scheduler.ts\";\n\nexport interface MemoryRecallAgentInputV1 {\n\t/** The calling AgentInstance; its active scheduler lease authorizes the recall. */\n\treadonly instanceId: string;\n\treadonly query?: {\n\t\treadonly scope?: MemoryScopeV1;\n\t\t/**\n\t\t * Suite-scoped read boundary (方案系统设计 §6.1/§11): only atoms visible\n\t\t * in this suite are recalled; legacy (suiteId-less) atoms are skipped\n\t\t * fail-safe and reported via the packet's `suiteFilter` counters.\n\t\t */\n\t\treadonly suiteId?: string;\n\t\treadonly timeRange?: { readonly from?: string; readonly to?: string };\n\t\treadonly scenes?: readonly string[];\n\t\treadonly projects?: readonly string[];\n\t\treadonly tasks?: readonly string[];\n\t\treadonly facets?: readonly { readonly namespace: string; readonly key?: string; readonly value?: string }[];\n\t\treadonly limit?: number;\n\t\treadonly offset?: number;\n\t\t/** Exact-lookup fast path (§5.2): bypasses anchors and scoring. */\n\t\treadonly memoryIdEquals?: string;\n\t};\n\t/** Caller budgets may only tighten the resolved limits. */\n\treadonly requestedBudgets?: MemoryRecallBudgetContributionV1;\n\treadonly signal?: AbortSignal;\n}\n\nexport interface MemoryRecallAgentErrorV1 {\n\treadonly code: string;\n\treadonly message: string;\n\treadonly stage: \"authorize\" | \"anchor\" | \"query\" | \"model\" | \"validate\" | \"serialize\" | \"purge\";\n\treadonly retryable: boolean;\n}\n\nexport interface MemoryRecallAgentOperationV1 {\n\treadonly leaseId: string;\n\treadonly packet: Promise<MemoryRecallPacketV1>;\n\t/** Awaits the bounded packet; cancellation and failures resolve, never throw. */\n\twait(): Promise<MemoryRecallPacketV1>;\n}\n\nexport interface MemoryRecallAgentV1 {\n\trecall(input: MemoryRecallAgentInputV1): MemoryRecallAgentOperationV1;\n}\n\nexport interface MemoryRecallAgentOptionsV1 {\n\treadonly scheduler: MemorySchedulerV1;\n\treadonly index: {\n\t\tquery(query: MemoryRecallQueryV1): {\n\t\t\titems: readonly { atom: MemoryAtomV1 }[];\n\t\t\ttotal: number;\n\t\t\t/** Present when the query carried a suiteId (suite read boundary). */\n\t\t\treadonly suiteFilter?: MemorySuiteFilterStatsV1;\n\t\t};\n\t};\n\treadonly policyDefaults: MemoryRecallBudgetsV1;\n\treadonly hostLimits: MemoryRecallBudgetsV1;\n\treadonly now?: () => number;\n}\n\nfunction failedPacket(\n\toperationId: string,\n\terror: MemoryRecallAgentErrorV1,\n\tbudgets: MemoryRecallBudgetsV1,\n): MemoryRecallPacketV1 {\n\treturn Object.freeze({\n\t\toperationId,\n\t\tstatus: \"failed\" as const,\n\t\tfacts: Object.freeze([]),\n\t\tnarrowingHints: Object.freeze([]),\n\t\tomittedCount: 0,\n\t\ttruncated: false,\n\t\ttruncationScope: \"none\" as const,\n\t\tqueryPlan: { lookupUsed: false, filtersApplied: [], scorerVersion: \"scorer@1\" },\n\t\tattempts: 0,\n\t\terror: Object.freeze(error),\n\t\tbudget: Object.freeze({\n\t\t\tcandidateBudget: budgets.candidateBudget,\n\t\t\tmodelInspectionLimit: budgets.modelInspectionLimit,\n\t\t\tfinalResultLimit: budgets.finalResultLimit,\n\t\t\tused: Object.freeze({ candidateCount: 0, modelInspectedCount: 0, finalResultCount: 0 }),\n\t\t}),\n\t});\n}\n\n/** Creates the memory-recall-agent executor bound to the scheduler and recall index. */\nexport function createMemoryRecallAgent(options: MemoryRecallAgentOptionsV1): MemoryRecallAgentV1 {\n\tconst now = options.now ?? (() => Date.now());\n\treturn {\n\t\trecall(input: MemoryRecallAgentInputV1): MemoryRecallAgentOperationV1 {\n\t\t\tconst operationId = `memory-recall-${now()}-${Math.random().toString(36).slice(2, 8)}`;\n\t\t\tlet settle!: (packet: MemoryRecallPacketV1) => void;\n\t\t\tconst packet = new Promise<MemoryRecallPacketV1>((resolve) => {\n\t\t\t\tsettle = resolve;\n\t\t\t});\n\n\t\t\tvoid (async () => {\n\t\t\t\t// Zero budgets only label packets that fail before resolution.\n\t\t\t\tconst unresolvedBudgets: MemoryRecallBudgetsV1 = {\n\t\t\t\t\tcandidateBudget: 0,\n\t\t\t\t\tmodelInspectionLimit: 0,\n\t\t\t\t\tfinalResultLimit: 0,\n\t\t\t\t\tmaxRelationHops: 0,\n\t\t\t\t\tmaxModelCalls: 0,\n\t\t\t\t\tmaxOutputTokens: 0,\n\t\t\t\t\tmaxOutputBytes: 0,\n\t\t\t\t\tmaxRounds: 0,\n\t\t\t\t};\n\t\t\t\t// Gate 1 — authorize via the instance's active scheduler lease.\n\t\t\t\tconst lease = options.scheduler.leaseSnapshotOf(input.instanceId);\n\n\t\t\t\tif (!lease || lease.state !== \"active\") {\n\t\t\t\t\tconst packet = failedPacket(\n\t\t\t\t\t\toperationId,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tcode: \"no_active_lease\",\n\t\t\t\t\t\t\tmessage: `Instance ${input.instanceId} does not hold an active memory scheduler lease`,\n\t\t\t\t\t\t\tstage: \"authorize\",\n\t\t\t\t\t\t\tretryable: false,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tunresolvedBudgets,\n\t\t\t\t\t);\n\t\t\t\t\tsettle(packet);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// Gate 2 — scheduler availability re-checked at execution time.\n\t\t\t\tif (!options.scheduler.isAvailable()) {\n\t\t\t\t\tconst packet = failedPacket(\n\t\t\t\t\t\toperationId,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tcode: \"memory_scheduler_unavailable\",\n\t\t\t\t\t\t\tmessage: \"The memory scheduler stopped accepting recall operations\",\n\t\t\t\t\t\t\tstage: \"authorize\",\n\t\t\t\t\t\t\tretryable: true,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tunresolvedBudgets,\n\t\t\t\t\t);\n\t\t\t\t\tsettle(packet);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst resolvedBudgetsAttempt = (() => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\treturn resolveMemoryRecallBudgets({\n\t\t\t\t\t\t\trequested: input.requestedBudgets,\n\t\t\t\t\t\t\tpolicyDefaults: options.policyDefaults,\n\t\t\t\t\t\t\thostLimits: options.hostLimits,\n\t\t\t\t\t\t});\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t// v8 ignore next -- resolveMemoryRecallBudgets 只抛 Error 对象\n\t\t\t\t\t\t\terror: error instanceof Error ? error : new Error(String(error)),\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t})();\n\t\t\t\tif (\"error\" in resolvedBudgetsAttempt) {\n\t\t\t\t\tconst zeroBudgets: MemoryRecallBudgetsV1 = {\n\t\t\t\t\t\tcandidateBudget: 0,\n\t\t\t\t\t\tmodelInspectionLimit: 0,\n\t\t\t\t\t\tfinalResultLimit: 0,\n\t\t\t\t\t\tmaxRelationHops: 0,\n\t\t\t\t\t\tmaxModelCalls: 0,\n\t\t\t\t\t\tmaxOutputTokens: 0,\n\t\t\t\t\t\tmaxOutputBytes: 0,\n\t\t\t\t\t\tmaxRounds: 0,\n\t\t\t\t\t};\n\t\t\t\t\tsettle(\n\t\t\t\t\t\tfailedPacket(\n\t\t\t\t\t\t\toperationId,\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tcode: \"invalid_budget\",\n\t\t\t\t\t\t\t\tmessage: resolvedBudgetsAttempt.error.message,\n\t\t\t\t\t\t\t\tstage: \"query\",\n\t\t\t\t\t\t\t\tretryable: true,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tzeroBudgets,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst budgets = resolvedBudgetsAttempt.budgets;\n\t\t\t\t// Cancellation checkpoint before any query work.\n\t\t\t\tif (input.signal?.aborted) {\n\t\t\t\t\tsettle(\n\t\t\t\t\t\tfailedPacket(\n\t\t\t\t\t\t\toperationId,\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tcode: \"cancelled\",\n\t\t\t\t\t\t\t\tmessage: \"Recall was cancelled before execution\",\n\t\t\t\t\t\t\t\tstage: \"query\",\n\t\t\t\t\t\t\t\tretryable: false,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tbudgets,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// The owner ALWAYS comes from the lease — the caller cannot widen\n\t\t\t\t// its visibility through request fields.\n\t\t\t\tconst query = { ...input.query, owner: lease.owner };\n\t\t\t\tlet page: {\n\t\t\t\t\titems: readonly { atom: MemoryAtomV1 }[];\n\t\t\t\t\ttotal: number;\n\t\t\t\t\treadonly suiteFilter?: MemorySuiteFilterStatsV1;\n\t\t\t\t};\n\t\t\t\ttry {\n\t\t\t\t\tpage = options.index.query({\n\t\t\t\t\t\tscope: query.scope,\n\t\t\t\t\t\t...(query.suiteId === undefined ? {} : { suiteId: query.suiteId }),\n\t\t\t\t\t\ttimeRange: query.timeRange,\n\t\t\t\t\t\tscenes: query.scenes,\n\t\t\t\t\t\tprojects: query.projects,\n\t\t\t\t\t\ttasks: query.tasks,\n\t\t\t\t\t\tfacets: query.facets,\n\t\t\t\t\t\tlimit: query.limit,\n\t\t\t\t\t\toffset: query.offset,\n\t\t\t\t\t\towner: query.owner,\n\t\t\t\t\t});\n\t\t\t\t} catch (error) {\n\t\t\t\t\t// Anchor (or structural) validation failures are structured failures.\n\t\t\t\t\tsettle(\n\t\t\t\t\t\tfailedPacket(\n\t\t\t\t\t\t\toperationId,\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tcode: \"invalid_anchor\",\n\t\t\t\t\t\t\t\t// v8 ignore next -- index.query 只抛 Error 对象\n\t\t\t\t\t\t\t\tmessage: error instanceof Error ? error.message : String(error),\n\t\t\t\t\t\t\t\tstage: \"anchor\",\n\t\t\t\t\t\t\t\tretryable: true,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tbudgets,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// Cancellation checkpoint after the deterministic query.\n\t\t\t\t// v8 ignore next -- 查询是同步的,信号不可能在两个检查点之间翻转\n\t\t\t\tif (input.signal?.aborted) {\n\t\t\t\t\tsettle(\n\t\t\t\t\t\tfailedPacket(\n\t\t\t\t\t\t\toperationId,\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tcode: \"cancelled\",\n\t\t\t\t\t\t\t\tmessage: \"Recall was cancelled after the query\",\n\t\t\t\t\t\t\t\tstage: \"query\",\n\t\t\t\t\t\t\t\tretryable: false,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tbudgets,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst packet = buildMemoryRecallPacketV1({\n\t\t\t\t\toperationId,\n\t\t\t\t\tcandidates: page.items.map((item) => item.atom),\n\t\t\t\t\tbudgets,\n\t\t\t\t\t...(page.suiteFilter === undefined ? {} : { suiteFilter: page.suiteFilter }),\n\t\t\t\t});\n\t\t\t\t// Pagination honesty (独立复审 H6): the index may have matched more\n\t\t\t\t// records than the current page holds. Those records exist but were\n\t\t\t\t// not recalled — they must be reported as omitted, never as\n\t\t\t\t// nonexistent.\n\t\t\t\tconst unreturned = page.total - page.items.length;\n\t\t\t\tif (unreturned <= 0) {\n\t\t\t\t\tsettle(packet);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tsettle({\n\t\t\t\t\t...packet,\n\t\t\t\t\tomittedCount: packet.omittedCount + unreturned,\n\t\t\t\t\ttruncated: true,\n\t\t\t\t\tnarrowingHints: Object.freeze([\n\t\t\t\t\t\t...packet.narrowingHints,\n\t\t\t\t\t\t`Page shows ${page.items.length} of ${page.total} matching memories; add anchors or raise the limit`,\n\t\t\t\t\t]),\n\t\t\t\t});\n\t\t\t\tsettle(packet);\n\t\t\t})();\n\n\t\t\treturn {\n\t\t\t\tleaseId: options.scheduler.leaseSnapshotOf(input.instanceId)?.leaseId ?? \"\",\n\t\t\t\tpacket,\n\t\t\t\twait: () => packet,\n\t\t\t};\n\t\t},\n\t};\n}\n"]}
@@ -0,0 +1,199 @@
1
+ /**
2
+ * Memory Foundation — memory-recall-agent isolation and Scheduler operation
3
+ * (1C.5b).
4
+ *
5
+ * Recall never runs inside the main agent's business session: the Memory
6
+ * Scheduler executes it through a dedicated memory-recall-agent bound to the
7
+ * instance's scheduler lease. The calling agent cannot pass an owner — the
8
+ * owner comes from the lease — so the request can never widen its visibility.
9
+ * Every failure (no lease, scheduler unavailable, cancelled, invalid anchors,
10
+ * budget violations) is a STRUCTURED failed packet; nothing degrades into an
11
+ * unbounded query. The main agent only ever receives the bounded packet
12
+ * (1C.5a).
13
+ */
14
+ import { buildMemoryRecallPacketV1, resolveMemoryRecallBudgets, } from "./recall-packet.js";
15
+ function failedPacket(operationId, error, budgets) {
16
+ return Object.freeze({
17
+ operationId,
18
+ status: "failed",
19
+ facts: Object.freeze([]),
20
+ narrowingHints: Object.freeze([]),
21
+ omittedCount: 0,
22
+ truncated: false,
23
+ truncationScope: "none",
24
+ queryPlan: { lookupUsed: false, filtersApplied: [], scorerVersion: "scorer@1" },
25
+ attempts: 0,
26
+ error: Object.freeze(error),
27
+ budget: Object.freeze({
28
+ candidateBudget: budgets.candidateBudget,
29
+ modelInspectionLimit: budgets.modelInspectionLimit,
30
+ finalResultLimit: budgets.finalResultLimit,
31
+ used: Object.freeze({ candidateCount: 0, modelInspectedCount: 0, finalResultCount: 0 }),
32
+ }),
33
+ });
34
+ }
35
+ /** Creates the memory-recall-agent executor bound to the scheduler and recall index. */
36
+ export function createMemoryRecallAgent(options) {
37
+ const now = options.now ?? (() => Date.now());
38
+ return {
39
+ recall(input) {
40
+ const operationId = `memory-recall-${now()}-${Math.random().toString(36).slice(2, 8)}`;
41
+ let settle;
42
+ const packet = new Promise((resolve) => {
43
+ settle = resolve;
44
+ });
45
+ void (async () => {
46
+ // Zero budgets only label packets that fail before resolution.
47
+ const unresolvedBudgets = {
48
+ candidateBudget: 0,
49
+ modelInspectionLimit: 0,
50
+ finalResultLimit: 0,
51
+ maxRelationHops: 0,
52
+ maxModelCalls: 0,
53
+ maxOutputTokens: 0,
54
+ maxOutputBytes: 0,
55
+ maxRounds: 0,
56
+ };
57
+ // Gate 1 — authorize via the instance's active scheduler lease.
58
+ const lease = options.scheduler.leaseSnapshotOf(input.instanceId);
59
+ if (!lease || lease.state !== "active") {
60
+ const packet = failedPacket(operationId, {
61
+ code: "no_active_lease",
62
+ message: `Instance ${input.instanceId} does not hold an active memory scheduler lease`,
63
+ stage: "authorize",
64
+ retryable: false,
65
+ }, unresolvedBudgets);
66
+ settle(packet);
67
+ return;
68
+ }
69
+ // Gate 2 — scheduler availability re-checked at execution time.
70
+ if (!options.scheduler.isAvailable()) {
71
+ const packet = failedPacket(operationId, {
72
+ code: "memory_scheduler_unavailable",
73
+ message: "The memory scheduler stopped accepting recall operations",
74
+ stage: "authorize",
75
+ retryable: true,
76
+ }, unresolvedBudgets);
77
+ settle(packet);
78
+ return;
79
+ }
80
+ const resolvedBudgetsAttempt = (() => {
81
+ try {
82
+ return resolveMemoryRecallBudgets({
83
+ requested: input.requestedBudgets,
84
+ policyDefaults: options.policyDefaults,
85
+ hostLimits: options.hostLimits,
86
+ });
87
+ }
88
+ catch (error) {
89
+ return {
90
+ // v8 ignore next -- resolveMemoryRecallBudgets 只抛 Error 对象
91
+ error: error instanceof Error ? error : new Error(String(error)),
92
+ };
93
+ }
94
+ })();
95
+ if ("error" in resolvedBudgetsAttempt) {
96
+ const zeroBudgets = {
97
+ candidateBudget: 0,
98
+ modelInspectionLimit: 0,
99
+ finalResultLimit: 0,
100
+ maxRelationHops: 0,
101
+ maxModelCalls: 0,
102
+ maxOutputTokens: 0,
103
+ maxOutputBytes: 0,
104
+ maxRounds: 0,
105
+ };
106
+ settle(failedPacket(operationId, {
107
+ code: "invalid_budget",
108
+ message: resolvedBudgetsAttempt.error.message,
109
+ stage: "query",
110
+ retryable: true,
111
+ }, zeroBudgets));
112
+ return;
113
+ }
114
+ const budgets = resolvedBudgetsAttempt.budgets;
115
+ // Cancellation checkpoint before any query work.
116
+ if (input.signal?.aborted) {
117
+ settle(failedPacket(operationId, {
118
+ code: "cancelled",
119
+ message: "Recall was cancelled before execution",
120
+ stage: "query",
121
+ retryable: false,
122
+ }, budgets));
123
+ return;
124
+ }
125
+ // The owner ALWAYS comes from the lease — the caller cannot widen
126
+ // its visibility through request fields.
127
+ const query = { ...input.query, owner: lease.owner };
128
+ let page;
129
+ try {
130
+ page = options.index.query({
131
+ scope: query.scope,
132
+ ...(query.suiteId === undefined ? {} : { suiteId: query.suiteId }),
133
+ timeRange: query.timeRange,
134
+ scenes: query.scenes,
135
+ projects: query.projects,
136
+ tasks: query.tasks,
137
+ facets: query.facets,
138
+ limit: query.limit,
139
+ offset: query.offset,
140
+ owner: query.owner,
141
+ });
142
+ }
143
+ catch (error) {
144
+ // Anchor (or structural) validation failures are structured failures.
145
+ settle(failedPacket(operationId, {
146
+ code: "invalid_anchor",
147
+ // v8 ignore next -- index.query 只抛 Error 对象
148
+ message: error instanceof Error ? error.message : String(error),
149
+ stage: "anchor",
150
+ retryable: true,
151
+ }, budgets));
152
+ return;
153
+ }
154
+ // Cancellation checkpoint after the deterministic query.
155
+ // v8 ignore next -- 查询是同步的,信号不可能在两个检查点之间翻转
156
+ if (input.signal?.aborted) {
157
+ settle(failedPacket(operationId, {
158
+ code: "cancelled",
159
+ message: "Recall was cancelled after the query",
160
+ stage: "query",
161
+ retryable: false,
162
+ }, budgets));
163
+ return;
164
+ }
165
+ const packet = buildMemoryRecallPacketV1({
166
+ operationId,
167
+ candidates: page.items.map((item) => item.atom),
168
+ budgets,
169
+ ...(page.suiteFilter === undefined ? {} : { suiteFilter: page.suiteFilter }),
170
+ });
171
+ // Pagination honesty (独立复审 H6): the index may have matched more
172
+ // records than the current page holds. Those records exist but were
173
+ // not recalled — they must be reported as omitted, never as
174
+ // nonexistent.
175
+ const unreturned = page.total - page.items.length;
176
+ if (unreturned <= 0) {
177
+ settle(packet);
178
+ return;
179
+ }
180
+ settle({
181
+ ...packet,
182
+ omittedCount: packet.omittedCount + unreturned,
183
+ truncated: true,
184
+ narrowingHints: Object.freeze([
185
+ ...packet.narrowingHints,
186
+ `Page shows ${page.items.length} of ${page.total} matching memories; add anchors or raise the limit`,
187
+ ]),
188
+ });
189
+ settle(packet);
190
+ })();
191
+ return {
192
+ leaseId: options.scheduler.leaseSnapshotOf(input.instanceId)?.leaseId ?? "",
193
+ packet,
194
+ wait: () => packet,
195
+ };
196
+ },
197
+ };
198
+ }
199
+ //# sourceMappingURL=recall-agent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recall-agent.js","sourceRoot":"","sources":["../../src/memory/recall-agent.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Memory Foundation — memory-recall-agent isolation and Scheduler operation\n * (1C.5b).\n *\n * Recall never runs inside the main agent's business session: the Memory\n * Scheduler executes it through a dedicated memory-recall-agent bound to the\n * instance's scheduler lease. The calling agent cannot pass an owner — the\n * owner comes from the lease — so the request can never widen its visibility.\n * Every failure (no lease, scheduler unavailable, cancelled, invalid anchors,\n * budget violations) is a STRUCTURED failed packet; nothing degrades into an\n * unbounded query. The main agent only ever receives the bounded packet\n * (1C.5a).\n */\n\nimport type { MemoryAtomV1, MemorySuiteFilterStatsV1 } from \"./foundation.ts\";\nimport type { MemoryRecallQueryV1, MemoryScopeV1 } from \"./recall-index.ts\";\nimport type { MemoryRecallPacketV1 } from \"./recall-packet.ts\";\nimport {\n\tbuildMemoryRecallPacketV1,\n\ttype MemoryRecallBudgetContributionV1,\n\ttype MemoryRecallBudgetsV1,\n\tresolveMemoryRecallBudgets,\n} from \"./recall-packet.ts\";\nimport type { MemorySchedulerV1 } from \"./scheduler.ts\";\n\nexport interface MemoryRecallAgentInputV1 {\n\t/** The calling AgentInstance; its active scheduler lease authorizes the recall. */\n\treadonly instanceId: string;\n\treadonly query?: {\n\t\treadonly scope?: MemoryScopeV1;\n\t\t/**\n\t\t * Suite-scoped read boundary (方案系统设计 §6.1/§11): only atoms visible\n\t\t * in this suite are recalled; legacy (suiteId-less) atoms are skipped\n\t\t * fail-safe and reported via the packet's `suiteFilter` counters.\n\t\t */\n\t\treadonly suiteId?: string;\n\t\treadonly timeRange?: { readonly from?: string; readonly to?: string };\n\t\treadonly scenes?: readonly string[];\n\t\treadonly projects?: readonly string[];\n\t\treadonly tasks?: readonly string[];\n\t\treadonly facets?: readonly { readonly namespace: string; readonly key?: string; readonly value?: string }[];\n\t\treadonly limit?: number;\n\t\treadonly offset?: number;\n\t\t/** Exact-lookup fast path (§5.2): bypasses anchors and scoring. */\n\t\treadonly memoryIdEquals?: string;\n\t};\n\t/** Caller budgets may only tighten the resolved limits. */\n\treadonly requestedBudgets?: MemoryRecallBudgetContributionV1;\n\treadonly signal?: AbortSignal;\n}\n\nexport interface MemoryRecallAgentErrorV1 {\n\treadonly code: string;\n\treadonly message: string;\n\treadonly stage: \"authorize\" | \"anchor\" | \"query\" | \"model\" | \"validate\" | \"serialize\" | \"purge\";\n\treadonly retryable: boolean;\n}\n\nexport interface MemoryRecallAgentOperationV1 {\n\treadonly leaseId: string;\n\treadonly packet: Promise<MemoryRecallPacketV1>;\n\t/** Awaits the bounded packet; cancellation and failures resolve, never throw. */\n\twait(): Promise<MemoryRecallPacketV1>;\n}\n\nexport interface MemoryRecallAgentV1 {\n\trecall(input: MemoryRecallAgentInputV1): MemoryRecallAgentOperationV1;\n}\n\nexport interface MemoryRecallAgentOptionsV1 {\n\treadonly scheduler: MemorySchedulerV1;\n\treadonly index: {\n\t\tquery(query: MemoryRecallQueryV1): {\n\t\t\titems: readonly { atom: MemoryAtomV1 }[];\n\t\t\ttotal: number;\n\t\t\t/** Present when the query carried a suiteId (suite read boundary). */\n\t\t\treadonly suiteFilter?: MemorySuiteFilterStatsV1;\n\t\t};\n\t};\n\treadonly policyDefaults: MemoryRecallBudgetsV1;\n\treadonly hostLimits: MemoryRecallBudgetsV1;\n\treadonly now?: () => number;\n}\n\nfunction failedPacket(\n\toperationId: string,\n\terror: MemoryRecallAgentErrorV1,\n\tbudgets: MemoryRecallBudgetsV1,\n): MemoryRecallPacketV1 {\n\treturn Object.freeze({\n\t\toperationId,\n\t\tstatus: \"failed\" as const,\n\t\tfacts: Object.freeze([]),\n\t\tnarrowingHints: Object.freeze([]),\n\t\tomittedCount: 0,\n\t\ttruncated: false,\n\t\ttruncationScope: \"none\" as const,\n\t\tqueryPlan: { lookupUsed: false, filtersApplied: [], scorerVersion: \"scorer@1\" },\n\t\tattempts: 0,\n\t\terror: Object.freeze(error),\n\t\tbudget: Object.freeze({\n\t\t\tcandidateBudget: budgets.candidateBudget,\n\t\t\tmodelInspectionLimit: budgets.modelInspectionLimit,\n\t\t\tfinalResultLimit: budgets.finalResultLimit,\n\t\t\tused: Object.freeze({ candidateCount: 0, modelInspectedCount: 0, finalResultCount: 0 }),\n\t\t}),\n\t});\n}\n\n/** Creates the memory-recall-agent executor bound to the scheduler and recall index. */\nexport function createMemoryRecallAgent(options: MemoryRecallAgentOptionsV1): MemoryRecallAgentV1 {\n\tconst now = options.now ?? (() => Date.now());\n\treturn {\n\t\trecall(input: MemoryRecallAgentInputV1): MemoryRecallAgentOperationV1 {\n\t\t\tconst operationId = `memory-recall-${now()}-${Math.random().toString(36).slice(2, 8)}`;\n\t\t\tlet settle!: (packet: MemoryRecallPacketV1) => void;\n\t\t\tconst packet = new Promise<MemoryRecallPacketV1>((resolve) => {\n\t\t\t\tsettle = resolve;\n\t\t\t});\n\n\t\t\tvoid (async () => {\n\t\t\t\t// Zero budgets only label packets that fail before resolution.\n\t\t\t\tconst unresolvedBudgets: MemoryRecallBudgetsV1 = {\n\t\t\t\t\tcandidateBudget: 0,\n\t\t\t\t\tmodelInspectionLimit: 0,\n\t\t\t\t\tfinalResultLimit: 0,\n\t\t\t\t\tmaxRelationHops: 0,\n\t\t\t\t\tmaxModelCalls: 0,\n\t\t\t\t\tmaxOutputTokens: 0,\n\t\t\t\t\tmaxOutputBytes: 0,\n\t\t\t\t\tmaxRounds: 0,\n\t\t\t\t};\n\t\t\t\t// Gate 1 — authorize via the instance's active scheduler lease.\n\t\t\t\tconst lease = options.scheduler.leaseSnapshotOf(input.instanceId);\n\n\t\t\t\tif (!lease || lease.state !== \"active\") {\n\t\t\t\t\tconst packet = failedPacket(\n\t\t\t\t\t\toperationId,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tcode: \"no_active_lease\",\n\t\t\t\t\t\t\tmessage: `Instance ${input.instanceId} does not hold an active memory scheduler lease`,\n\t\t\t\t\t\t\tstage: \"authorize\",\n\t\t\t\t\t\t\tretryable: false,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tunresolvedBudgets,\n\t\t\t\t\t);\n\t\t\t\t\tsettle(packet);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// Gate 2 — scheduler availability re-checked at execution time.\n\t\t\t\tif (!options.scheduler.isAvailable()) {\n\t\t\t\t\tconst packet = failedPacket(\n\t\t\t\t\t\toperationId,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tcode: \"memory_scheduler_unavailable\",\n\t\t\t\t\t\t\tmessage: \"The memory scheduler stopped accepting recall operations\",\n\t\t\t\t\t\t\tstage: \"authorize\",\n\t\t\t\t\t\t\tretryable: true,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tunresolvedBudgets,\n\t\t\t\t\t);\n\t\t\t\t\tsettle(packet);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst resolvedBudgetsAttempt = (() => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\treturn resolveMemoryRecallBudgets({\n\t\t\t\t\t\t\trequested: input.requestedBudgets,\n\t\t\t\t\t\t\tpolicyDefaults: options.policyDefaults,\n\t\t\t\t\t\t\thostLimits: options.hostLimits,\n\t\t\t\t\t\t});\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t// v8 ignore next -- resolveMemoryRecallBudgets 只抛 Error 对象\n\t\t\t\t\t\t\terror: error instanceof Error ? error : new Error(String(error)),\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t})();\n\t\t\t\tif (\"error\" in resolvedBudgetsAttempt) {\n\t\t\t\t\tconst zeroBudgets: MemoryRecallBudgetsV1 = {\n\t\t\t\t\t\tcandidateBudget: 0,\n\t\t\t\t\t\tmodelInspectionLimit: 0,\n\t\t\t\t\t\tfinalResultLimit: 0,\n\t\t\t\t\t\tmaxRelationHops: 0,\n\t\t\t\t\t\tmaxModelCalls: 0,\n\t\t\t\t\t\tmaxOutputTokens: 0,\n\t\t\t\t\t\tmaxOutputBytes: 0,\n\t\t\t\t\t\tmaxRounds: 0,\n\t\t\t\t\t};\n\t\t\t\t\tsettle(\n\t\t\t\t\t\tfailedPacket(\n\t\t\t\t\t\t\toperationId,\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tcode: \"invalid_budget\",\n\t\t\t\t\t\t\t\tmessage: resolvedBudgetsAttempt.error.message,\n\t\t\t\t\t\t\t\tstage: \"query\",\n\t\t\t\t\t\t\t\tretryable: true,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tzeroBudgets,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst budgets = resolvedBudgetsAttempt.budgets;\n\t\t\t\t// Cancellation checkpoint before any query work.\n\t\t\t\tif (input.signal?.aborted) {\n\t\t\t\t\tsettle(\n\t\t\t\t\t\tfailedPacket(\n\t\t\t\t\t\t\toperationId,\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tcode: \"cancelled\",\n\t\t\t\t\t\t\t\tmessage: \"Recall was cancelled before execution\",\n\t\t\t\t\t\t\t\tstage: \"query\",\n\t\t\t\t\t\t\t\tretryable: false,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tbudgets,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// The owner ALWAYS comes from the lease — the caller cannot widen\n\t\t\t\t// its visibility through request fields.\n\t\t\t\tconst query = { ...input.query, owner: lease.owner };\n\t\t\t\tlet page: {\n\t\t\t\t\titems: readonly { atom: MemoryAtomV1 }[];\n\t\t\t\t\ttotal: number;\n\t\t\t\t\treadonly suiteFilter?: MemorySuiteFilterStatsV1;\n\t\t\t\t};\n\t\t\t\ttry {\n\t\t\t\t\tpage = options.index.query({\n\t\t\t\t\t\tscope: query.scope,\n\t\t\t\t\t\t...(query.suiteId === undefined ? {} : { suiteId: query.suiteId }),\n\t\t\t\t\t\ttimeRange: query.timeRange,\n\t\t\t\t\t\tscenes: query.scenes,\n\t\t\t\t\t\tprojects: query.projects,\n\t\t\t\t\t\ttasks: query.tasks,\n\t\t\t\t\t\tfacets: query.facets,\n\t\t\t\t\t\tlimit: query.limit,\n\t\t\t\t\t\toffset: query.offset,\n\t\t\t\t\t\towner: query.owner,\n\t\t\t\t\t});\n\t\t\t\t} catch (error) {\n\t\t\t\t\t// Anchor (or structural) validation failures are structured failures.\n\t\t\t\t\tsettle(\n\t\t\t\t\t\tfailedPacket(\n\t\t\t\t\t\t\toperationId,\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tcode: \"invalid_anchor\",\n\t\t\t\t\t\t\t\t// v8 ignore next -- index.query 只抛 Error 对象\n\t\t\t\t\t\t\t\tmessage: error instanceof Error ? error.message : String(error),\n\t\t\t\t\t\t\t\tstage: \"anchor\",\n\t\t\t\t\t\t\t\tretryable: true,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tbudgets,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// Cancellation checkpoint after the deterministic query.\n\t\t\t\t// v8 ignore next -- 查询是同步的,信号不可能在两个检查点之间翻转\n\t\t\t\tif (input.signal?.aborted) {\n\t\t\t\t\tsettle(\n\t\t\t\t\t\tfailedPacket(\n\t\t\t\t\t\t\toperationId,\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tcode: \"cancelled\",\n\t\t\t\t\t\t\t\tmessage: \"Recall was cancelled after the query\",\n\t\t\t\t\t\t\t\tstage: \"query\",\n\t\t\t\t\t\t\t\tretryable: false,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tbudgets,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst packet = buildMemoryRecallPacketV1({\n\t\t\t\t\toperationId,\n\t\t\t\t\tcandidates: page.items.map((item) => item.atom),\n\t\t\t\t\tbudgets,\n\t\t\t\t\t...(page.suiteFilter === undefined ? {} : { suiteFilter: page.suiteFilter }),\n\t\t\t\t});\n\t\t\t\t// Pagination honesty (独立复审 H6): the index may have matched more\n\t\t\t\t// records than the current page holds. Those records exist but were\n\t\t\t\t// not recalled — they must be reported as omitted, never as\n\t\t\t\t// nonexistent.\n\t\t\t\tconst unreturned = page.total - page.items.length;\n\t\t\t\tif (unreturned <= 0) {\n\t\t\t\t\tsettle(packet);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tsettle({\n\t\t\t\t\t...packet,\n\t\t\t\t\tomittedCount: packet.omittedCount + unreturned,\n\t\t\t\t\ttruncated: true,\n\t\t\t\t\tnarrowingHints: Object.freeze([\n\t\t\t\t\t\t...packet.narrowingHints,\n\t\t\t\t\t\t`Page shows ${page.items.length} of ${page.total} matching memories; add anchors or raise the limit`,\n\t\t\t\t\t]),\n\t\t\t\t});\n\t\t\t\tsettle(packet);\n\t\t\t})();\n\n\t\t\treturn {\n\t\t\t\tleaseId: options.scheduler.leaseSnapshotOf(input.instanceId)?.leaseId ?? \"\",\n\t\t\t\tpacket,\n\t\t\t\twait: () => packet,\n\t\t\t};\n\t\t},\n\t};\n}\n"]}