@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.
- package/README.md +65 -0
- package/agent-forge.json +11 -0
- package/dist/capability.d.ts +182 -0
- package/dist/capability.d.ts.map +1 -0
- package/dist/capability.js +2565 -0
- package/dist/capability.js.map +1 -0
- package/dist/entry.d.ts +36 -0
- package/dist/entry.d.ts.map +1 -0
- package/dist/entry.js +154 -0
- package/dist/entry.js.map +1 -0
- package/dist/index.d.ts +49 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +49 -0
- package/dist/index.js.map +1 -0
- package/dist/memory/assistant-card.d.ts +31 -0
- package/dist/memory/assistant-card.d.ts.map +1 -0
- package/dist/memory/assistant-card.js +108 -0
- package/dist/memory/assistant-card.js.map +1 -0
- package/dist/memory/candidates.d.ts +65 -0
- package/dist/memory/candidates.d.ts.map +1 -0
- package/dist/memory/candidates.js +100 -0
- package/dist/memory/candidates.js.map +1 -0
- package/dist/memory/code-memory.d.ts +89 -0
- package/dist/memory/code-memory.d.ts.map +1 -0
- package/dist/memory/code-memory.js +104 -0
- package/dist/memory/code-memory.js.map +1 -0
- package/dist/memory/compaction-sequencer.d.ts +63 -0
- package/dist/memory/compaction-sequencer.d.ts.map +1 -0
- package/dist/memory/compaction-sequencer.js +129 -0
- package/dist/memory/compaction-sequencer.js.map +1 -0
- package/dist/memory/continuation.d.ts +44 -0
- package/dist/memory/continuation.d.ts.map +1 -0
- package/dist/memory/continuation.js +49 -0
- package/dist/memory/continuation.js.map +1 -0
- package/dist/memory/curation.d.ts +58 -0
- package/dist/memory/curation.d.ts.map +1 -0
- package/dist/memory/curation.js +68 -0
- package/dist/memory/curation.js.map +1 -0
- package/dist/memory/egress-policy.d.ts +50 -0
- package/dist/memory/egress-policy.d.ts.map +1 -0
- package/dist/memory/egress-policy.js +71 -0
- package/dist/memory/egress-policy.js.map +1 -0
- package/dist/memory/embedding-provider.d.ts +70 -0
- package/dist/memory/embedding-provider.d.ts.map +1 -0
- package/dist/memory/embedding-provider.js +164 -0
- package/dist/memory/embedding-provider.js.map +1 -0
- package/dist/memory/embedding-reranker.d.ts +56 -0
- package/dist/memory/embedding-reranker.d.ts.map +1 -0
- package/dist/memory/embedding-reranker.js +109 -0
- package/dist/memory/embedding-reranker.js.map +1 -0
- package/dist/memory/foundation.d.ts +168 -0
- package/dist/memory/foundation.d.ts.map +1 -0
- package/dist/memory/foundation.js +487 -0
- package/dist/memory/foundation.js.map +1 -0
- package/dist/memory/host-module-import.d.ts +25 -0
- package/dist/memory/host-module-import.d.ts.map +1 -0
- package/dist/memory/host-module-import.js +41 -0
- package/dist/memory/host-module-import.js.map +1 -0
- package/dist/memory/ledger.d.ts +58 -0
- package/dist/memory/ledger.d.ts.map +1 -0
- package/dist/memory/ledger.js +315 -0
- package/dist/memory/ledger.js.map +1 -0
- package/dist/memory/lifecycle.d.ts +124 -0
- package/dist/memory/lifecycle.d.ts.map +1 -0
- package/dist/memory/lifecycle.js +201 -0
- package/dist/memory/lifecycle.js.map +1 -0
- package/dist/memory/memory-network.d.ts +55 -0
- package/dist/memory/memory-network.d.ts.map +1 -0
- package/dist/memory/memory-network.js +70 -0
- package/dist/memory/memory-network.js.map +1 -0
- package/dist/memory/model-cache-hygiene.d.ts +18 -0
- package/dist/memory/model-cache-hygiene.d.ts.map +1 -0
- package/dist/memory/model-cache-hygiene.js +38 -0
- package/dist/memory/model-cache-hygiene.js.map +1 -0
- package/dist/memory/preference-disambiguator.d.ts +43 -0
- package/dist/memory/preference-disambiguator.d.ts.map +1 -0
- package/dist/memory/preference-disambiguator.js +81 -0
- package/dist/memory/preference-disambiguator.js.map +1 -0
- package/dist/memory/preference-lifecycle.d.ts +66 -0
- package/dist/memory/preference-lifecycle.d.ts.map +1 -0
- package/dist/memory/preference-lifecycle.js +129 -0
- package/dist/memory/preference-lifecycle.js.map +1 -0
- package/dist/memory/preference-promotion.d.ts +87 -0
- package/dist/memory/preference-promotion.d.ts.map +1 -0
- package/dist/memory/preference-promotion.js +102 -0
- package/dist/memory/preference-promotion.js.map +1 -0
- package/dist/memory/preference-resolver.d.ts +44 -0
- package/dist/memory/preference-resolver.d.ts.map +1 -0
- package/dist/memory/preference-resolver.js +107 -0
- package/dist/memory/preference-resolver.js.map +1 -0
- package/dist/memory/purge-journal.d.ts +76 -0
- package/dist/memory/purge-journal.d.ts.map +1 -0
- package/dist/memory/purge-journal.js +130 -0
- package/dist/memory/purge-journal.js.map +1 -0
- package/dist/memory/purge.d.ts +90 -0
- package/dist/memory/purge.d.ts.map +1 -0
- package/dist/memory/purge.js +138 -0
- package/dist/memory/purge.js.map +1 -0
- package/dist/memory/recall-agent.d.ts +84 -0
- package/dist/memory/recall-agent.d.ts.map +1 -0
- package/dist/memory/recall-agent.js +199 -0
- package/dist/memory/recall-agent.js.map +1 -0
- package/dist/memory/recall-index.d.ts +87 -0
- package/dist/memory/recall-index.d.ts.map +1 -0
- package/dist/memory/recall-index.js +222 -0
- package/dist/memory/recall-index.js.map +1 -0
- package/dist/memory/recall-packet.d.ts +121 -0
- package/dist/memory/recall-packet.d.ts.map +1 -0
- package/dist/memory/recall-packet.js +156 -0
- package/dist/memory/recall-packet.js.map +1 -0
- package/dist/memory/scheduler-api.d.ts +99 -0
- package/dist/memory/scheduler-api.d.ts.map +1 -0
- package/dist/memory/scheduler-api.js +93 -0
- package/dist/memory/scheduler-api.js.map +1 -0
- package/dist/memory/scheduler.d.ts +55 -0
- package/dist/memory/scheduler.d.ts.map +1 -0
- package/dist/memory/scheduler.js +91 -0
- package/dist/memory/scheduler.js.map +1 -0
- package/dist/memory/store.d.ts +107 -0
- package/dist/memory/store.d.ts.map +1 -0
- package/dist/memory/store.js +208 -0
- package/dist/memory/store.js.map +1 -0
- package/dist/memory/suite-memory.d.ts +208 -0
- package/dist/memory/suite-memory.d.ts.map +1 -0
- package/dist/memory/suite-memory.js +288 -0
- package/dist/memory/suite-memory.js.map +1 -0
- package/dist/memory/transfer.d.ts +142 -0
- package/dist/memory/transfer.d.ts.map +1 -0
- package/dist/memory/transfer.js +210 -0
- package/dist/memory/transfer.js.map +1 -0
- package/dist/memory/vector-index.d.ts +39 -0
- package/dist/memory/vector-index.d.ts.map +1 -0
- package/dist/memory/vector-index.js +136 -0
- package/dist/memory/vector-index.js.map +1 -0
- package/dist/memory/write-budget.d.ts +33 -0
- package/dist/memory/write-budget.d.ts.map +1 -0
- package/dist/memory/write-budget.js +45 -0
- package/dist/memory/write-budget.js.map +1 -0
- package/dist/testing/memory-testkit.d.ts +149 -0
- package/dist/testing/memory-testkit.d.ts.map +1 -0
- package/dist/testing/memory-testkit.js +438 -0
- package/dist/testing/memory-testkit.js.map +1 -0
- package/dist/utils/sync-sleep.d.ts +2 -0
- package/dist/utils/sync-sleep.d.ts.map +1 -0
- package/dist/utils/sync-sleep.js +11 -0
- package/dist/utils/sync-sleep.js.map +1 -0
- package/package.json +56 -0
- package/plugin.json +10 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
export const MEMORY_RECALL_BUDGET_FIELDS = [
|
|
2
|
+
"candidateBudget",
|
|
3
|
+
"modelInspectionLimit",
|
|
4
|
+
"finalResultLimit",
|
|
5
|
+
"maxRelationHops",
|
|
6
|
+
"maxModelCalls",
|
|
7
|
+
"maxOutputTokens",
|
|
8
|
+
"maxOutputBytes",
|
|
9
|
+
"maxRounds",
|
|
10
|
+
];
|
|
11
|
+
function assertBudgetValue(value, field, label) {
|
|
12
|
+
if (typeof value !== "number" || !Number.isFinite(value) || !Number.isSafeInteger(value) || value < 1) {
|
|
13
|
+
throw new Error(`${label}.${field} must be a positive safe integer`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Resolves the effective budgets. Resolution priority: caller request (tighten
|
|
18
|
+
* only) → Policy defaults (clamped to host limits, adjustments recorded) →
|
|
19
|
+
* host hard limits. Caller values over the host limit are explicitly rejected.
|
|
20
|
+
*/
|
|
21
|
+
export function resolveMemoryRecallBudgets(input) {
|
|
22
|
+
const adjustments = [];
|
|
23
|
+
const callerTightened = [];
|
|
24
|
+
const budgets = {};
|
|
25
|
+
for (const field of MEMORY_RECALL_BUDGET_FIELDS) {
|
|
26
|
+
const requested = input.requested?.[field];
|
|
27
|
+
if (requested !== undefined) {
|
|
28
|
+
assertBudgetValue(requested, field, "Requested recall budgets");
|
|
29
|
+
const hardLimit = input.hostLimits[field];
|
|
30
|
+
if (requested > hardLimit) {
|
|
31
|
+
throw new Error(`Requested recall budgets.${field} (${requested}) exceeds the host hard limit (${hardLimit}); callers may only tighten limits`);
|
|
32
|
+
}
|
|
33
|
+
budgets[field] = requested;
|
|
34
|
+
callerTightened.push(field);
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
const hardLimit = input.hostLimits[field];
|
|
38
|
+
const policyDefault = input.policyDefaults[field];
|
|
39
|
+
assertBudgetValue(policyDefault, field, "Policy default recall budgets");
|
|
40
|
+
assertBudgetValue(hardLimit, field, "Host limit recall budgets");
|
|
41
|
+
if (policyDefault > hardLimit) {
|
|
42
|
+
adjustments.push(`Policy default ${field}=${policyDefault} clamped to host limit ${hardLimit}`);
|
|
43
|
+
budgets[field] = hardLimit;
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
budgets[field] = policyDefault;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
budgets: Object.freeze(budgets),
|
|
51
|
+
adjustments: Object.freeze(adjustments),
|
|
52
|
+
callerTightened: Object.freeze(callerTightened),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function defaultStatement(atom) {
|
|
56
|
+
return JSON.stringify(atom.payload);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Builds the bounded recall packet from deterministic candidates:
|
|
60
|
+
* 1. candidates over `candidateBudget` → `needs_narrowing` with hints,
|
|
61
|
+
* omittedCount = full candidate count (nothing is passed on).
|
|
62
|
+
* 2. `modelInspectionLimit` bounds the inspected stage (V1 inspects
|
|
63
|
+
* deterministically); overflow is omitted with `round` scope.
|
|
64
|
+
* 3. `finalResultLimit` bounds the packet facts; overflow is omitted with
|
|
65
|
+
* `final_packet` scope.
|
|
66
|
+
*/
|
|
67
|
+
export function buildMemoryRecallPacketV1(input) {
|
|
68
|
+
const budgets = input.budgets;
|
|
69
|
+
const buildStatement = input.buildStatement ?? defaultStatement;
|
|
70
|
+
if (input.candidates.length > budgets.candidateBudget) {
|
|
71
|
+
return Object.freeze({
|
|
72
|
+
operationId: input.operationId,
|
|
73
|
+
status: "needs_narrowing",
|
|
74
|
+
facts: Object.freeze([]),
|
|
75
|
+
narrowingHints: Object.freeze([
|
|
76
|
+
`Candidate count ${input.candidates.length} exceeds candidateBudget ${budgets.candidateBudget}`,
|
|
77
|
+
"Add time, scene, project, task, or facet anchors to narrow the query",
|
|
78
|
+
]),
|
|
79
|
+
omittedCount: input.candidates.length,
|
|
80
|
+
truncated: true,
|
|
81
|
+
truncationScope: "round",
|
|
82
|
+
queryPlan: { lookupUsed: false, filtersApplied: [], scorerVersion: "scorer@1" },
|
|
83
|
+
attempts: 0,
|
|
84
|
+
...(input.suiteFilter === undefined ? {} : { suiteFilter: input.suiteFilter }),
|
|
85
|
+
budget: Object.freeze({
|
|
86
|
+
candidateBudget: budgets.candidateBudget,
|
|
87
|
+
modelInspectionLimit: budgets.modelInspectionLimit,
|
|
88
|
+
finalResultLimit: budgets.finalResultLimit,
|
|
89
|
+
used: Object.freeze({
|
|
90
|
+
candidateCount: input.candidates.length,
|
|
91
|
+
modelInspectedCount: 0,
|
|
92
|
+
finalResultCount: 0,
|
|
93
|
+
}),
|
|
94
|
+
}),
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
const inspected = input.candidates.slice(0, budgets.modelInspectionLimit);
|
|
98
|
+
const factsSource = inspected.slice(0, budgets.finalResultLimit);
|
|
99
|
+
const facts = factsSource.map((atom) => ({
|
|
100
|
+
memoryId: atom.memoryId,
|
|
101
|
+
contentRevision: atom.contentRevision,
|
|
102
|
+
statement: buildStatement(atom),
|
|
103
|
+
confidence: atom.confidence,
|
|
104
|
+
}));
|
|
105
|
+
const omittedFromFinal = inspected.length - facts.length;
|
|
106
|
+
const omittedFromInspection = input.candidates.length - inspected.length;
|
|
107
|
+
const omittedCount = omittedFromFinal + omittedFromInspection;
|
|
108
|
+
if (input.candidates.length === 0) {
|
|
109
|
+
return Object.freeze({
|
|
110
|
+
operationId: input.operationId,
|
|
111
|
+
status: "empty",
|
|
112
|
+
facts: Object.freeze([]),
|
|
113
|
+
narrowingHints: Object.freeze([]),
|
|
114
|
+
omittedCount: 0,
|
|
115
|
+
truncated: false,
|
|
116
|
+
truncationScope: "none",
|
|
117
|
+
queryPlan: { lookupUsed: false, filtersApplied: [], scorerVersion: "scorer@1" },
|
|
118
|
+
attempts: 0,
|
|
119
|
+
...(input.suiteFilter === undefined ? {} : { suiteFilter: input.suiteFilter }),
|
|
120
|
+
budget: Object.freeze({
|
|
121
|
+
candidateBudget: budgets.candidateBudget,
|
|
122
|
+
modelInspectionLimit: budgets.modelInspectionLimit,
|
|
123
|
+
finalResultLimit: budgets.finalResultLimit,
|
|
124
|
+
used: Object.freeze({ candidateCount: 0, modelInspectedCount: 0, finalResultCount: 0 }),
|
|
125
|
+
}),
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
const status = facts.length > 0 ? "completed" : "empty";
|
|
129
|
+
return Object.freeze({
|
|
130
|
+
operationId: input.operationId,
|
|
131
|
+
status,
|
|
132
|
+
facts: Object.freeze(facts),
|
|
133
|
+
narrowingHints: Object.freeze([]),
|
|
134
|
+
queryPlan: { lookupUsed: false, filtersApplied: [], scorerVersion: "scorer@1" },
|
|
135
|
+
attempts: 0,
|
|
136
|
+
omittedCount,
|
|
137
|
+
truncated: omittedCount > 0,
|
|
138
|
+
truncationScope: omittedCount === 0
|
|
139
|
+
? "none"
|
|
140
|
+
: omittedFromInspection > 0
|
|
141
|
+
? "round"
|
|
142
|
+
: "final_packet",
|
|
143
|
+
...(input.suiteFilter === undefined ? {} : { suiteFilter: input.suiteFilter }),
|
|
144
|
+
budget: Object.freeze({
|
|
145
|
+
candidateBudget: budgets.candidateBudget,
|
|
146
|
+
modelInspectionLimit: budgets.modelInspectionLimit,
|
|
147
|
+
finalResultLimit: budgets.finalResultLimit,
|
|
148
|
+
used: Object.freeze({
|
|
149
|
+
candidateCount: input.candidates.length,
|
|
150
|
+
modelInspectedCount: inspected.length,
|
|
151
|
+
finalResultCount: facts.length,
|
|
152
|
+
}),
|
|
153
|
+
}),
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=recall-packet.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recall-packet.js","sourceRoot":"","sources":["../../src/memory/recall-packet.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Memory Foundation — recall budgets and bounded recall packet (1C.5a).\n *\n * Three-layer budgets (candidateBudget / modelInspectionLimit /\n * finalResultLimit) plus host hard limits govern every recall. Resolution\n * rules are frozen (记忆系统设计.md §6.4):\n * - Caller-provided values may only TIGHTEN limits. Negative, non-finite,\n * non-integer, or over-hard-limit caller values are explicitly rejected —\n * never silently clamped.\n * - Only Profile/Policy default values may be clamped down to host limits,\n * and every such adjustment is recorded with its reason.\n * - Hitting any limit yields `omittedCount`, `truncated: true` with a\n * `truncationScope` and narrowing hints — candidates are never silently\n * passed to the main agent.\n * V1 recall is deterministic: no model inspection happens in this slice, the\n * inspection limit still bounds the deterministic stage.\n */\nimport type { MemoryAtomV1, MemorySuiteFilterStatsV1 } from \"./foundation.ts\";\n\nexport interface MemoryRecallBudgetsV1 {\n\treadonly candidateBudget: number;\n\treadonly modelInspectionLimit: number;\n\treadonly finalResultLimit: number;\n\treadonly maxRelationHops: number;\n\treadonly maxModelCalls: number;\n\treadonly maxOutputTokens: number;\n\treadonly maxOutputBytes: number;\n\treadonly maxRounds: number;\n}\n\nexport type MemoryRecallBudgetContributionV1 = Partial<MemoryRecallBudgetsV1>;\n\nexport const MEMORY_RECALL_BUDGET_FIELDS = [\n\t\"candidateBudget\",\n\t\"modelInspectionLimit\",\n\t\"finalResultLimit\",\n\t\"maxRelationHops\",\n\t\"maxModelCalls\",\n\t\"maxOutputTokens\",\n\t\"maxOutputBytes\",\n\t\"maxRounds\",\n] as const;\n\nexport type MemoryRecallBudgetField = (typeof MEMORY_RECALL_BUDGET_FIELDS)[number];\n\nfunction assertBudgetValue(value: unknown, field: MemoryRecallBudgetField, label: string): asserts value is number {\n\tif (typeof value !== \"number\" || !Number.isFinite(value) || !Number.isSafeInteger(value) || value < 1) {\n\t\tthrow new Error(`${label}.${field} must be a positive safe integer`);\n\t}\n}\n\nexport interface MemoryRecallBudgetsResolution {\n\treadonly budgets: MemoryRecallBudgetsV1;\n\t/** Recorded default-clamp decisions (Policy defaults narrowed by host limits). */\n\treadonly adjustments: readonly string[];\n\t/** Caller contributions that were accepted. */\n\treadonly callerTightened: readonly MemoryRecallBudgetField[];\n}\n\n/**\n * Resolves the effective budgets. Resolution priority: caller request (tighten\n * only) → Policy defaults (clamped to host limits, adjustments recorded) →\n * host hard limits. Caller values over the host limit are explicitly rejected.\n */\nexport function resolveMemoryRecallBudgets(input: {\n\treadonly requested?: MemoryRecallBudgetContributionV1;\n\treadonly policyDefaults: MemoryRecallBudgetsV1;\n\treadonly hostLimits: MemoryRecallBudgetsV1;\n}): MemoryRecallBudgetsResolution {\n\tconst adjustments: string[] = [];\n\tconst callerTightened: MemoryRecallBudgetField[] = [];\n\tconst budgets = {} as Record<MemoryRecallBudgetField, number>;\n\tfor (const field of MEMORY_RECALL_BUDGET_FIELDS) {\n\t\tconst requested = input.requested?.[field];\n\t\tif (requested !== undefined) {\n\t\t\tassertBudgetValue(requested, field, \"Requested recall budgets\");\n\t\t\tconst hardLimit = input.hostLimits[field];\n\t\t\tif (requested > hardLimit) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Requested recall budgets.${field} (${requested}) exceeds the host hard limit (${hardLimit}); callers may only tighten limits`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tbudgets[field] = requested;\n\t\t\tcallerTightened.push(field);\n\t\t\tcontinue;\n\t\t}\n\t\tconst hardLimit = input.hostLimits[field];\n\t\tconst policyDefault = input.policyDefaults[field];\n\t\tassertBudgetValue(policyDefault, field, \"Policy default recall budgets\");\n\t\tassertBudgetValue(hardLimit, field, \"Host limit recall budgets\");\n\t\tif (policyDefault > hardLimit) {\n\t\t\tadjustments.push(`Policy default ${field}=${policyDefault} clamped to host limit ${hardLimit}`);\n\t\t\tbudgets[field] = hardLimit;\n\t\t} else {\n\t\t\tbudgets[field] = policyDefault;\n\t\t}\n\t}\n\treturn {\n\t\tbudgets: Object.freeze(budgets),\n\t\tadjustments: Object.freeze(adjustments),\n\t\tcallerTightened: Object.freeze(callerTightened),\n\t};\n}\n\nexport interface MemoryRecallPacketBudgetUsageV1 {\n\treadonly candidateCount: number;\n\treadonly modelInspectedCount: number;\n\treadonly finalResultCount: number;\n}\n\nexport type MemoryRecallTruncationScopeV1 = \"none\" | \"operation\" | \"round\" | \"final_packet\";\n\nexport interface MemoryRecallPacketErrorV1 {\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 MemoryRecallPacketV1 {\n\treadonly operationId: string;\n\treadonly status: \"completed\" | \"empty\" | \"needs_narrowing\" | \"failed\";\n\treadonly facts: readonly {\n\t\treadonly memoryId: string;\n\t\treadonly contentRevision: string;\n\t\treadonly statement: string;\n\t\treadonly confidence: number;\n\t}[];\n\t/** Query plan facts (设计 §6.3): what the recall actually did. */\n\treadonly queryPlan: {\n\t\treadonly lookupUsed: boolean;\n\t\treadonly filtersApplied: readonly string[];\n\t\t/** Version of the deterministic scorer that ordered the facts. */\n\t\treadonly scorerVersion: string;\n\t};\n\t/** Model-call attempts spent (V1 deterministic: 0 or 1). */\n\treadonly attempts: number;\n\treadonly narrowingHints: readonly string[];\n\treadonly omittedCount: number;\n\treadonly truncated: boolean;\n\treadonly truncationScope: MemoryRecallTruncationScopeV1;\n\t/** Present only on failed packets — failures resolve as structured packets, never throw. */\n\treadonly error?: MemoryRecallPacketErrorV1;\n\t/**\n\t * Present only when the recall query carried a suiteId: how many legacy and\n\t * foreign-suite records the suite read boundary excluded (方案系统设计\n\t * §6.1/§11, 读取边界惰性可观测).\n\t */\n\treadonly suiteFilter?: MemorySuiteFilterStatsV1;\n\treadonly budget: {\n\t\treadonly candidateBudget: number;\n\t\treadonly modelInspectionLimit: number;\n\t\treadonly finalResultLimit: number;\n\t\treadonly used: {\n\t\t\treadonly candidateCount: number;\n\t\t\treadonly modelInspectedCount: number;\n\t\t\treadonly finalResultCount: number;\n\t\t};\n\t};\n}\n\nfunction defaultStatement(atom: MemoryAtomV1): string {\n\treturn JSON.stringify(atom.payload);\n}\n\nexport interface MemoryRecallPacketInputV1 {\n\treadonly operationId: string;\n\treadonly candidates: readonly MemoryAtomV1[];\n\treadonly budgets: MemoryRecallBudgetsV1;\n\t/** Deterministic statement projector; defaults to the payload JSON. */\n\treadonly buildStatement?: (atom: MemoryAtomV1) => string;\n\t/** Suite read-boundary skip counters; present only on suite-scoped recalls. */\n\treadonly suiteFilter?: MemorySuiteFilterStatsV1;\n}\n\n/**\n * Builds the bounded recall packet from deterministic candidates:\n * 1. candidates over `candidateBudget` → `needs_narrowing` with hints,\n * omittedCount = full candidate count (nothing is passed on).\n * 2. `modelInspectionLimit` bounds the inspected stage (V1 inspects\n * deterministically); overflow is omitted with `round` scope.\n * 3. `finalResultLimit` bounds the packet facts; overflow is omitted with\n * `final_packet` scope.\n */\nexport function buildMemoryRecallPacketV1(input: MemoryRecallPacketInputV1): MemoryRecallPacketV1 {\n\tconst budgets = input.budgets;\n\tconst buildStatement = input.buildStatement ?? defaultStatement;\n\n\tif (input.candidates.length > budgets.candidateBudget) {\n\t\treturn Object.freeze({\n\t\t\toperationId: input.operationId,\n\t\t\tstatus: \"needs_narrowing\" as const,\n\t\t\tfacts: Object.freeze([]),\n\t\t\tnarrowingHints: Object.freeze([\n\t\t\t\t`Candidate count ${input.candidates.length} exceeds candidateBudget ${budgets.candidateBudget}`,\n\t\t\t\t\"Add time, scene, project, task, or facet anchors to narrow the query\",\n\t\t\t]),\n\t\t\tomittedCount: input.candidates.length,\n\t\t\ttruncated: true,\n\t\t\ttruncationScope: \"round\" as const,\n\t\t\tqueryPlan: { lookupUsed: false, filtersApplied: [], scorerVersion: \"scorer@1\" },\n\t\t\tattempts: 0,\n\t\t\t...(input.suiteFilter === undefined ? {} : { suiteFilter: input.suiteFilter }),\n\t\t\tbudget: Object.freeze({\n\t\t\t\tcandidateBudget: budgets.candidateBudget,\n\t\t\t\tmodelInspectionLimit: budgets.modelInspectionLimit,\n\t\t\t\tfinalResultLimit: budgets.finalResultLimit,\n\t\t\t\tused: Object.freeze({\n\t\t\t\t\tcandidateCount: input.candidates.length,\n\t\t\t\t\tmodelInspectedCount: 0,\n\t\t\t\t\tfinalResultCount: 0,\n\t\t\t\t}),\n\t\t\t}),\n\t\t});\n\t}\n\n\tconst inspected = input.candidates.slice(0, budgets.modelInspectionLimit);\n\tconst factsSource = inspected.slice(0, budgets.finalResultLimit);\n\tconst facts = factsSource.map((atom) => ({\n\t\tmemoryId: atom.memoryId,\n\t\tcontentRevision: atom.contentRevision,\n\t\tstatement: buildStatement(atom),\n\t\tconfidence: atom.confidence,\n\t}));\n\tconst omittedFromFinal = inspected.length - facts.length;\n\tconst omittedFromInspection = input.candidates.length - inspected.length;\n\tconst omittedCount = omittedFromFinal + omittedFromInspection;\n\n\tif (input.candidates.length === 0) {\n\t\treturn Object.freeze({\n\t\t\toperationId: input.operationId,\n\t\t\tstatus: \"empty\" as const,\n\t\t\tfacts: Object.freeze([]),\n\t\t\tnarrowingHints: Object.freeze([]),\n\t\t\tomittedCount: 0,\n\t\t\ttruncated: false,\n\t\t\ttruncationScope: \"none\" as const,\n\t\t\tqueryPlan: { lookupUsed: false, filtersApplied: [], scorerVersion: \"scorer@1\" },\n\t\t\tattempts: 0,\n\t\t\t...(input.suiteFilter === undefined ? {} : { suiteFilter: input.suiteFilter }),\n\t\t\tbudget: Object.freeze({\n\t\t\t\tcandidateBudget: budgets.candidateBudget,\n\t\t\t\tmodelInspectionLimit: budgets.modelInspectionLimit,\n\t\t\t\tfinalResultLimit: budgets.finalResultLimit,\n\t\t\t\tused: Object.freeze({ candidateCount: 0, modelInspectedCount: 0, finalResultCount: 0 }),\n\t\t\t}),\n\t\t});\n\t}\n\n\tconst status: MemoryRecallPacketV1[\"status\"] = facts.length > 0 ? \"completed\" : \"empty\";\n\treturn Object.freeze({\n\t\toperationId: input.operationId,\n\t\tstatus,\n\t\tfacts: Object.freeze(facts),\n\t\tnarrowingHints: Object.freeze([]),\n\t\tqueryPlan: { lookupUsed: false, filtersApplied: [], scorerVersion: \"scorer@1\" },\n\t\tattempts: 0,\n\t\tomittedCount,\n\t\ttruncated: omittedCount > 0,\n\t\ttruncationScope:\n\t\t\tomittedCount === 0\n\t\t\t\t? (\"none\" as const)\n\t\t\t\t: omittedFromInspection > 0\n\t\t\t\t\t? (\"round\" as const)\n\t\t\t\t\t: (\"final_packet\" as const),\n\t\t...(input.suiteFilter === undefined ? {} : { suiteFilter: input.suiteFilter }),\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({\n\t\t\t\tcandidateCount: input.candidates.length,\n\t\t\t\tmodelInspectedCount: inspected.length,\n\t\t\t\tfinalResultCount: facts.length,\n\t\t\t}),\n\t\t}),\n\t});\n}\n"]}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MemorySchedulerAPI facade (P1-4, 记忆系统设计 §10) — the single entry
|
|
3
|
+
* point a main agent uses for every memory operation: recall, observation,
|
|
4
|
+
* candidate submission, usage reporting, lifecycle evaluation, and purge.
|
|
5
|
+
*
|
|
6
|
+
* The facade composes the existing verified modules (recall-agent, candidate
|
|
7
|
+
* machine, lifecycle manager, purge gate) and exposes ONLY these six
|
|
8
|
+
* operations, so a main agent cannot bypass the scheduler by reaching into
|
|
9
|
+
* individual modules. All checks are deterministic; no model is called.
|
|
10
|
+
*/
|
|
11
|
+
import type { MemoryCandidateMachineV1 } from "./candidates.ts";
|
|
12
|
+
import type { MemoryEgressPolicyGateV1 } from "./egress-policy.ts";
|
|
13
|
+
import type { MemoryAtomV1, MemorySourceRefV1 } from "./foundation.ts";
|
|
14
|
+
import type { MemoryLifecycleManagerV1 } from "./lifecycle.ts";
|
|
15
|
+
import type { MemoryPurgeGateV1 } from "./purge.ts";
|
|
16
|
+
import type { MemoryRecallAgentV1 } from "./recall-agent.ts";
|
|
17
|
+
import type { MemoryRecallPacketV1 } from "./recall-packet.ts";
|
|
18
|
+
export interface MemorySchedulerApiDepsV1 {
|
|
19
|
+
readonly recallAgent: MemoryRecallAgentV1;
|
|
20
|
+
readonly machine: MemoryCandidateMachineV1;
|
|
21
|
+
readonly lifecycle: MemoryLifecycleManagerV1;
|
|
22
|
+
readonly purgeGate: MemoryPurgeGateV1;
|
|
23
|
+
/** Egress gate applied to every recall result before return (设计 §11: MUST run). */
|
|
24
|
+
readonly egressPolicy: MemoryEgressPolicyGateV1;
|
|
25
|
+
/** Provenance + payload for each memoryId — the egress gate is fail-closed
|
|
26
|
+
* and blocks any fact without an entry. */
|
|
27
|
+
readonly factInfoLookup: (memoryId: string) => {
|
|
28
|
+
readonly sourceRefs: readonly MemorySourceRefV1[];
|
|
29
|
+
readonly payload: Record<string, unknown>;
|
|
30
|
+
} | undefined;
|
|
31
|
+
}
|
|
32
|
+
export interface MemorySchedulerApiV1 {
|
|
33
|
+
recall(input: {
|
|
34
|
+
readonly instanceId: string;
|
|
35
|
+
readonly query?: {
|
|
36
|
+
readonly scope?: "session" | "cycle" | "long-term";
|
|
37
|
+
/**
|
|
38
|
+
* Suite-scoped read boundary (方案系统设计 §6.1/§11): only atoms
|
|
39
|
+
* visible in this suite are recalled; legacy (suiteId-less) atoms
|
|
40
|
+
* are skipped fail-safe and reported via the packet's `suiteFilter`
|
|
41
|
+
* counters. Absent = suite-unscoped recall.
|
|
42
|
+
*/
|
|
43
|
+
readonly suiteId?: string;
|
|
44
|
+
readonly projects?: readonly string[];
|
|
45
|
+
readonly tasks?: readonly string[];
|
|
46
|
+
/** Facet anchors (e.g. tags) narrowed deterministically by the caller. */
|
|
47
|
+
readonly facets?: readonly {
|
|
48
|
+
readonly namespace: string;
|
|
49
|
+
readonly key?: string;
|
|
50
|
+
readonly value?: string;
|
|
51
|
+
}[];
|
|
52
|
+
readonly limit?: number;
|
|
53
|
+
};
|
|
54
|
+
readonly requestedBudgets?: Record<string, number>;
|
|
55
|
+
readonly signal?: AbortSignal;
|
|
56
|
+
}): Promise<MemoryRecallPacketV1>;
|
|
57
|
+
submitObservation(input: {
|
|
58
|
+
readonly owner: string;
|
|
59
|
+
readonly observationId: string;
|
|
60
|
+
readonly draft: MemoryAtomV1;
|
|
61
|
+
}): {
|
|
62
|
+
readonly state: string;
|
|
63
|
+
readonly reason?: string;
|
|
64
|
+
};
|
|
65
|
+
submitCandidate(input: {
|
|
66
|
+
readonly observationId: string;
|
|
67
|
+
readonly owner: string;
|
|
68
|
+
}): {
|
|
69
|
+
readonly status: "committed" | "deduped" | "rejected";
|
|
70
|
+
readonly memoryId?: string;
|
|
71
|
+
readonly reason?: string;
|
|
72
|
+
};
|
|
73
|
+
reportUsage(input: {
|
|
74
|
+
readonly memoryId: string;
|
|
75
|
+
readonly owner: string;
|
|
76
|
+
}): void;
|
|
77
|
+
evaluateLifecycle(input: {
|
|
78
|
+
readonly memoryId: string;
|
|
79
|
+
}): {
|
|
80
|
+
readonly status: string;
|
|
81
|
+
};
|
|
82
|
+
authorizePurge(input: {
|
|
83
|
+
readonly memoryIds: readonly string[];
|
|
84
|
+
readonly reason: string;
|
|
85
|
+
readonly authorizedBy: string;
|
|
86
|
+
readonly authorizationRef?: {
|
|
87
|
+
readonly mode: "user-immediate" | "retention-expiry" | "profile-policy";
|
|
88
|
+
readonly issuedAt: number;
|
|
89
|
+
readonly issuedBy: string;
|
|
90
|
+
readonly confirmationRef?: string;
|
|
91
|
+
readonly policyServiceRef?: string;
|
|
92
|
+
};
|
|
93
|
+
}): {
|
|
94
|
+
readonly batchId: string;
|
|
95
|
+
readonly state: string;
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
export declare function createMemorySchedulerApi(deps: MemorySchedulerApiDepsV1): MemorySchedulerApiV1;
|
|
99
|
+
//# sourceMappingURL=scheduler-api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scheduler-api.d.ts","sourceRoot":"","sources":["../../src/memory/scheduler-api.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * MemorySchedulerAPI facade (P1-4, 记忆系统设计 §10) — the single entry\n * point a main agent uses for every memory operation: recall, observation,\n * candidate submission, usage reporting, lifecycle evaluation, and purge.\n *\n * The facade composes the existing verified modules (recall-agent, candidate\n * machine, lifecycle manager, purge gate) and exposes ONLY these six\n * operations, so a main agent cannot bypass the scheduler by reaching into\n * individual modules. All checks are deterministic; no model is called.\n */\n\nimport type { MemoryCandidateMachineV1 } from \"./candidates.ts\";\nimport type { MemoryEgressPolicyGateV1 } from \"./egress-policy.ts\";\nimport type { MemoryAtomV1, MemorySourceRefV1 } from \"./foundation.ts\";\nimport type { MemoryLifecycleManagerV1 } from \"./lifecycle.ts\";\nimport type { MemoryPurgeGateV1 } from \"./purge.ts\";\nimport type { MemoryRecallAgentV1 } from \"./recall-agent.ts\";\nimport type { MemoryRecallPacketV1 } from \"./recall-packet.ts\";\n\nexport interface MemorySchedulerApiDepsV1 {\n\treadonly recallAgent: MemoryRecallAgentV1;\n\treadonly machine: MemoryCandidateMachineV1;\n\treadonly lifecycle: MemoryLifecycleManagerV1;\n\treadonly purgeGate: MemoryPurgeGateV1;\n\t/** Egress gate applied to every recall result before return (设计 §11: MUST run). */\n\treadonly egressPolicy: MemoryEgressPolicyGateV1;\n\t/** Provenance + payload for each memoryId — the egress gate is fail-closed\n\t * and blocks any fact without an entry. */\n\treadonly factInfoLookup: (\n\t\tmemoryId: string,\n\t) => { readonly sourceRefs: readonly MemorySourceRefV1[]; readonly payload: Record<string, unknown> } | undefined;\n}\n\nexport interface MemorySchedulerApiV1 {\n\trecall(input: {\n\t\treadonly instanceId: string;\n\t\treadonly query?: {\n\t\t\treadonly scope?: \"session\" | \"cycle\" | \"long-term\";\n\t\t\t/**\n\t\t\t * Suite-scoped read boundary (方案系统设计 §6.1/§11): only atoms\n\t\t\t * visible in this suite are recalled; legacy (suiteId-less) atoms\n\t\t\t * are skipped fail-safe and reported via the packet's `suiteFilter`\n\t\t\t * counters. Absent = suite-unscoped recall.\n\t\t\t */\n\t\t\treadonly suiteId?: string;\n\t\t\treadonly projects?: readonly string[];\n\t\t\treadonly tasks?: readonly string[];\n\t\t\t/** Facet anchors (e.g. tags) narrowed deterministically by the caller. */\n\t\t\treadonly facets?: readonly { readonly namespace: string; readonly key?: string; readonly value?: string }[];\n\t\t\treadonly limit?: number;\n\t\t};\n\t\treadonly requestedBudgets?: Record<string, number>;\n\t\treadonly signal?: AbortSignal;\n\t}): Promise<MemoryRecallPacketV1>;\n\tsubmitObservation(input: { readonly owner: string; readonly observationId: string; readonly draft: MemoryAtomV1 }): {\n\t\treadonly state: string;\n\t\treadonly reason?: string;\n\t};\n\tsubmitCandidate(input: { readonly observationId: string; readonly owner: string }): {\n\t\treadonly status: \"committed\" | \"deduped\" | \"rejected\";\n\t\treadonly memoryId?: string;\n\t\treadonly reason?: string;\n\t};\n\treportUsage(input: { readonly memoryId: string; readonly owner: string }): void;\n\tevaluateLifecycle(input: { readonly memoryId: string }): { readonly status: string };\n\tauthorizePurge(input: {\n\t\treadonly memoryIds: readonly string[];\n\t\treadonly reason: string;\n\t\treadonly authorizedBy: string;\n\t\treadonly authorizationRef?: {\n\t\t\treadonly mode: \"user-immediate\" | \"retention-expiry\" | \"profile-policy\";\n\t\t\treadonly issuedAt: number;\n\t\t\treadonly issuedBy: string;\n\t\t\treadonly confirmationRef?: string;\n\t\t\treadonly policyServiceRef?: string;\n\t\t};\n\t}): { readonly batchId: string; readonly state: string };\n}\n\nexport function createMemorySchedulerApi(deps: MemorySchedulerApiDepsV1): MemorySchedulerApiV1 {\n\treturn {\n\t\tasync recall(input) {\n\t\t\tlet packet = await deps.recallAgent\n\t\t\t\t.recall({\n\t\t\t\t\tinstanceId: input.instanceId,\n\t\t\t\t\tquery: input.query,\n\t\t\t\t\trequestedBudgets: input.requestedBudgets,\n\t\t\t\t\tsignal: input.signal,\n\t\t\t\t})\n\t\t\t\t.wait();\n\t\t\t{\n\t\t\t\tconst factInfo: Record<\n\t\t\t\t\tstring,\n\t\t\t\t\t| { readonly sourceRefs: readonly MemorySourceRefV1[]; readonly payload: Record<string, unknown> }\n\t\t\t\t\t| undefined\n\t\t\t\t> = {};\n\t\t\t\tfor (const fact of packet.facts) {\n\t\t\t\t\tfactInfo[fact.memoryId] = deps.factInfoLookup(fact.memoryId);\n\t\t\t\t}\n\t\t\t\tconst outcome = deps.egressPolicy.apply(packet, factInfo);\n\t\t\t\tif (outcome.status === \"blocked\") {\n\t\t\t\t\treturn {\n\t\t\t\t\t\toperationId: packet.operationId,\n\t\t\t\t\t\tstatus: \"failed\",\n\t\t\t\t\t\tfacts: [],\n\t\t\t\t\t\tnarrowingHints: [`egress blocked: ${outcome.reason}`],\n\t\t\t\t\t\tomittedCount: packet.omittedCount,\n\t\t\t\t\t\ttruncated: true,\n\t\t\t\t\t\ttruncationScope: \"operation\",\n\t\t\t\t\t\tqueryPlan: packet.queryPlan,\n\t\t\t\t\t\tattempts: packet.attempts,\n\t\t\t\t\t\terror: {\n\t\t\t\t\t\t\tcode: \"egress_blocked\",\n\t\t\t\t\t\t\tmessage: outcome.reason,\n\t\t\t\t\t\t\tstage: \"validate\",\n\t\t\t\t\t\t\tretryable: false,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tbudget: packet.budget,\n\t\t\t\t\t} satisfies MemoryRecallPacketV1;\n\t\t\t\t}\n\t\t\t\tpacket = outcome.packet;\n\t\t\t}\n\t\t\treturn packet;\n\t\t},\n\t\tsubmitObservation({ owner, observationId, draft }) {\n\t\t\tconst observation = deps.machine.observe({ owner, observationId, draft });\n\t\t\treturn {\n\t\t\t\tstate: observation.record.state,\n\t\t\t\t...(observation.record.rejectedReason === undefined ? {} : { reason: observation.record.rejectedReason }),\n\t\t\t};\n\t\t},\n\t\tsubmitCandidate({ observationId, owner }) {\n\t\t\tconst record = deps.machine.stateOf(observationId, owner);\n\t\t\tif (!record) return { status: \"rejected\", reason: \"unknown observation\" };\n\t\t\tif (record.state === \"rejected\") return { status: \"rejected\", reason: record.rejectedReason };\n\t\t\tif (record.state === \"committed\" && record.committed) {\n\t\t\t\treturn { status: \"deduped\", memoryId: record.committed.atom.memoryId };\n\t\t\t}\n\t\t\tconst committed = deps.machine.accept(observationId, owner);\n\t\t\treturn { status: \"committed\", memoryId: committed.atom.memoryId };\n\t\t},\n\t\treportUsage(input) {\n\t\t\tdeps.lifecycle.applyUsage({\n\t\t\t\tmemoryId: input.memoryId,\n\t\t\t\tkind: \"accessed\",\n\t\t\t\tidempotencyKey: `used:${input.memoryId}`,\n\t\t\t});\n\t\t},\n\t\tevaluateLifecycle({ memoryId }) {\n\t\t\tconst projection = deps.lifecycle.evaluate(memoryId);\n\t\t\treturn { status: projection.effectiveStatus };\n\t\t},\n\t\tauthorizePurge(input) {\n\t\t\tconst batch = deps.purgeGate.authorizeBatch({\n\t\t\t\tmemoryIds: input.memoryIds,\n\t\t\t\treason: input.reason,\n\t\t\t\tauthorizedBy: input.authorizedBy,\n\t\t\t\t...(input.authorizationRef === undefined ? {} : { authorizationRef: input.authorizationRef }),\n\t\t\t});\n\t\t\treturn { batchId: batch.batchId, state: batch.state };\n\t\t},\n\t};\n}\n"]}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MemorySchedulerAPI facade (P1-4, 记忆系统设计 §10) — the single entry
|
|
3
|
+
* point a main agent uses for every memory operation: recall, observation,
|
|
4
|
+
* candidate submission, usage reporting, lifecycle evaluation, and purge.
|
|
5
|
+
*
|
|
6
|
+
* The facade composes the existing verified modules (recall-agent, candidate
|
|
7
|
+
* machine, lifecycle manager, purge gate) and exposes ONLY these six
|
|
8
|
+
* operations, so a main agent cannot bypass the scheduler by reaching into
|
|
9
|
+
* individual modules. All checks are deterministic; no model is called.
|
|
10
|
+
*/
|
|
11
|
+
export function createMemorySchedulerApi(deps) {
|
|
12
|
+
return {
|
|
13
|
+
async recall(input) {
|
|
14
|
+
let packet = await deps.recallAgent
|
|
15
|
+
.recall({
|
|
16
|
+
instanceId: input.instanceId,
|
|
17
|
+
query: input.query,
|
|
18
|
+
requestedBudgets: input.requestedBudgets,
|
|
19
|
+
signal: input.signal,
|
|
20
|
+
})
|
|
21
|
+
.wait();
|
|
22
|
+
{
|
|
23
|
+
const factInfo = {};
|
|
24
|
+
for (const fact of packet.facts) {
|
|
25
|
+
factInfo[fact.memoryId] = deps.factInfoLookup(fact.memoryId);
|
|
26
|
+
}
|
|
27
|
+
const outcome = deps.egressPolicy.apply(packet, factInfo);
|
|
28
|
+
if (outcome.status === "blocked") {
|
|
29
|
+
return {
|
|
30
|
+
operationId: packet.operationId,
|
|
31
|
+
status: "failed",
|
|
32
|
+
facts: [],
|
|
33
|
+
narrowingHints: [`egress blocked: ${outcome.reason}`],
|
|
34
|
+
omittedCount: packet.omittedCount,
|
|
35
|
+
truncated: true,
|
|
36
|
+
truncationScope: "operation",
|
|
37
|
+
queryPlan: packet.queryPlan,
|
|
38
|
+
attempts: packet.attempts,
|
|
39
|
+
error: {
|
|
40
|
+
code: "egress_blocked",
|
|
41
|
+
message: outcome.reason,
|
|
42
|
+
stage: "validate",
|
|
43
|
+
retryable: false,
|
|
44
|
+
},
|
|
45
|
+
budget: packet.budget,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
packet = outcome.packet;
|
|
49
|
+
}
|
|
50
|
+
return packet;
|
|
51
|
+
},
|
|
52
|
+
submitObservation({ owner, observationId, draft }) {
|
|
53
|
+
const observation = deps.machine.observe({ owner, observationId, draft });
|
|
54
|
+
return {
|
|
55
|
+
state: observation.record.state,
|
|
56
|
+
...(observation.record.rejectedReason === undefined ? {} : { reason: observation.record.rejectedReason }),
|
|
57
|
+
};
|
|
58
|
+
},
|
|
59
|
+
submitCandidate({ observationId, owner }) {
|
|
60
|
+
const record = deps.machine.stateOf(observationId, owner);
|
|
61
|
+
if (!record)
|
|
62
|
+
return { status: "rejected", reason: "unknown observation" };
|
|
63
|
+
if (record.state === "rejected")
|
|
64
|
+
return { status: "rejected", reason: record.rejectedReason };
|
|
65
|
+
if (record.state === "committed" && record.committed) {
|
|
66
|
+
return { status: "deduped", memoryId: record.committed.atom.memoryId };
|
|
67
|
+
}
|
|
68
|
+
const committed = deps.machine.accept(observationId, owner);
|
|
69
|
+
return { status: "committed", memoryId: committed.atom.memoryId };
|
|
70
|
+
},
|
|
71
|
+
reportUsage(input) {
|
|
72
|
+
deps.lifecycle.applyUsage({
|
|
73
|
+
memoryId: input.memoryId,
|
|
74
|
+
kind: "accessed",
|
|
75
|
+
idempotencyKey: `used:${input.memoryId}`,
|
|
76
|
+
});
|
|
77
|
+
},
|
|
78
|
+
evaluateLifecycle({ memoryId }) {
|
|
79
|
+
const projection = deps.lifecycle.evaluate(memoryId);
|
|
80
|
+
return { status: projection.effectiveStatus };
|
|
81
|
+
},
|
|
82
|
+
authorizePurge(input) {
|
|
83
|
+
const batch = deps.purgeGate.authorizeBatch({
|
|
84
|
+
memoryIds: input.memoryIds,
|
|
85
|
+
reason: input.reason,
|
|
86
|
+
authorizedBy: input.authorizedBy,
|
|
87
|
+
...(input.authorizationRef === undefined ? {} : { authorizationRef: input.authorizationRef }),
|
|
88
|
+
});
|
|
89
|
+
return { batchId: batch.batchId, state: batch.state };
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=scheduler-api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scheduler-api.js","sourceRoot":"","sources":["../../src/memory/scheduler-api.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * MemorySchedulerAPI facade (P1-4, 记忆系统设计 §10) — the single entry\n * point a main agent uses for every memory operation: recall, observation,\n * candidate submission, usage reporting, lifecycle evaluation, and purge.\n *\n * The facade composes the existing verified modules (recall-agent, candidate\n * machine, lifecycle manager, purge gate) and exposes ONLY these six\n * operations, so a main agent cannot bypass the scheduler by reaching into\n * individual modules. All checks are deterministic; no model is called.\n */\n\nimport type { MemoryCandidateMachineV1 } from \"./candidates.ts\";\nimport type { MemoryEgressPolicyGateV1 } from \"./egress-policy.ts\";\nimport type { MemoryAtomV1, MemorySourceRefV1 } from \"./foundation.ts\";\nimport type { MemoryLifecycleManagerV1 } from \"./lifecycle.ts\";\nimport type { MemoryPurgeGateV1 } from \"./purge.ts\";\nimport type { MemoryRecallAgentV1 } from \"./recall-agent.ts\";\nimport type { MemoryRecallPacketV1 } from \"./recall-packet.ts\";\n\nexport interface MemorySchedulerApiDepsV1 {\n\treadonly recallAgent: MemoryRecallAgentV1;\n\treadonly machine: MemoryCandidateMachineV1;\n\treadonly lifecycle: MemoryLifecycleManagerV1;\n\treadonly purgeGate: MemoryPurgeGateV1;\n\t/** Egress gate applied to every recall result before return (设计 §11: MUST run). */\n\treadonly egressPolicy: MemoryEgressPolicyGateV1;\n\t/** Provenance + payload for each memoryId — the egress gate is fail-closed\n\t * and blocks any fact without an entry. */\n\treadonly factInfoLookup: (\n\t\tmemoryId: string,\n\t) => { readonly sourceRefs: readonly MemorySourceRefV1[]; readonly payload: Record<string, unknown> } | undefined;\n}\n\nexport interface MemorySchedulerApiV1 {\n\trecall(input: {\n\t\treadonly instanceId: string;\n\t\treadonly query?: {\n\t\t\treadonly scope?: \"session\" | \"cycle\" | \"long-term\";\n\t\t\t/**\n\t\t\t * Suite-scoped read boundary (方案系统设计 §6.1/§11): only atoms\n\t\t\t * visible in this suite are recalled; legacy (suiteId-less) atoms\n\t\t\t * are skipped fail-safe and reported via the packet's `suiteFilter`\n\t\t\t * counters. Absent = suite-unscoped recall.\n\t\t\t */\n\t\t\treadonly suiteId?: string;\n\t\t\treadonly projects?: readonly string[];\n\t\t\treadonly tasks?: readonly string[];\n\t\t\t/** Facet anchors (e.g. tags) narrowed deterministically by the caller. */\n\t\t\treadonly facets?: readonly { readonly namespace: string; readonly key?: string; readonly value?: string }[];\n\t\t\treadonly limit?: number;\n\t\t};\n\t\treadonly requestedBudgets?: Record<string, number>;\n\t\treadonly signal?: AbortSignal;\n\t}): Promise<MemoryRecallPacketV1>;\n\tsubmitObservation(input: { readonly owner: string; readonly observationId: string; readonly draft: MemoryAtomV1 }): {\n\t\treadonly state: string;\n\t\treadonly reason?: string;\n\t};\n\tsubmitCandidate(input: { readonly observationId: string; readonly owner: string }): {\n\t\treadonly status: \"committed\" | \"deduped\" | \"rejected\";\n\t\treadonly memoryId?: string;\n\t\treadonly reason?: string;\n\t};\n\treportUsage(input: { readonly memoryId: string; readonly owner: string }): void;\n\tevaluateLifecycle(input: { readonly memoryId: string }): { readonly status: string };\n\tauthorizePurge(input: {\n\t\treadonly memoryIds: readonly string[];\n\t\treadonly reason: string;\n\t\treadonly authorizedBy: string;\n\t\treadonly authorizationRef?: {\n\t\t\treadonly mode: \"user-immediate\" | \"retention-expiry\" | \"profile-policy\";\n\t\t\treadonly issuedAt: number;\n\t\t\treadonly issuedBy: string;\n\t\t\treadonly confirmationRef?: string;\n\t\t\treadonly policyServiceRef?: string;\n\t\t};\n\t}): { readonly batchId: string; readonly state: string };\n}\n\nexport function createMemorySchedulerApi(deps: MemorySchedulerApiDepsV1): MemorySchedulerApiV1 {\n\treturn {\n\t\tasync recall(input) {\n\t\t\tlet packet = await deps.recallAgent\n\t\t\t\t.recall({\n\t\t\t\t\tinstanceId: input.instanceId,\n\t\t\t\t\tquery: input.query,\n\t\t\t\t\trequestedBudgets: input.requestedBudgets,\n\t\t\t\t\tsignal: input.signal,\n\t\t\t\t})\n\t\t\t\t.wait();\n\t\t\t{\n\t\t\t\tconst factInfo: Record<\n\t\t\t\t\tstring,\n\t\t\t\t\t| { readonly sourceRefs: readonly MemorySourceRefV1[]; readonly payload: Record<string, unknown> }\n\t\t\t\t\t| undefined\n\t\t\t\t> = {};\n\t\t\t\tfor (const fact of packet.facts) {\n\t\t\t\t\tfactInfo[fact.memoryId] = deps.factInfoLookup(fact.memoryId);\n\t\t\t\t}\n\t\t\t\tconst outcome = deps.egressPolicy.apply(packet, factInfo);\n\t\t\t\tif (outcome.status === \"blocked\") {\n\t\t\t\t\treturn {\n\t\t\t\t\t\toperationId: packet.operationId,\n\t\t\t\t\t\tstatus: \"failed\",\n\t\t\t\t\t\tfacts: [],\n\t\t\t\t\t\tnarrowingHints: [`egress blocked: ${outcome.reason}`],\n\t\t\t\t\t\tomittedCount: packet.omittedCount,\n\t\t\t\t\t\ttruncated: true,\n\t\t\t\t\t\ttruncationScope: \"operation\",\n\t\t\t\t\t\tqueryPlan: packet.queryPlan,\n\t\t\t\t\t\tattempts: packet.attempts,\n\t\t\t\t\t\terror: {\n\t\t\t\t\t\t\tcode: \"egress_blocked\",\n\t\t\t\t\t\t\tmessage: outcome.reason,\n\t\t\t\t\t\t\tstage: \"validate\",\n\t\t\t\t\t\t\tretryable: false,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tbudget: packet.budget,\n\t\t\t\t\t} satisfies MemoryRecallPacketV1;\n\t\t\t\t}\n\t\t\t\tpacket = outcome.packet;\n\t\t\t}\n\t\t\treturn packet;\n\t\t},\n\t\tsubmitObservation({ owner, observationId, draft }) {\n\t\t\tconst observation = deps.machine.observe({ owner, observationId, draft });\n\t\t\treturn {\n\t\t\t\tstate: observation.record.state,\n\t\t\t\t...(observation.record.rejectedReason === undefined ? {} : { reason: observation.record.rejectedReason }),\n\t\t\t};\n\t\t},\n\t\tsubmitCandidate({ observationId, owner }) {\n\t\t\tconst record = deps.machine.stateOf(observationId, owner);\n\t\t\tif (!record) return { status: \"rejected\", reason: \"unknown observation\" };\n\t\t\tif (record.state === \"rejected\") return { status: \"rejected\", reason: record.rejectedReason };\n\t\t\tif (record.state === \"committed\" && record.committed) {\n\t\t\t\treturn { status: \"deduped\", memoryId: record.committed.atom.memoryId };\n\t\t\t}\n\t\t\tconst committed = deps.machine.accept(observationId, owner);\n\t\t\treturn { status: \"committed\", memoryId: committed.atom.memoryId };\n\t\t},\n\t\treportUsage(input) {\n\t\t\tdeps.lifecycle.applyUsage({\n\t\t\t\tmemoryId: input.memoryId,\n\t\t\t\tkind: \"accessed\",\n\t\t\t\tidempotencyKey: `used:${input.memoryId}`,\n\t\t\t});\n\t\t},\n\t\tevaluateLifecycle({ memoryId }) {\n\t\t\tconst projection = deps.lifecycle.evaluate(memoryId);\n\t\t\treturn { status: projection.effectiveStatus };\n\t\t},\n\t\tauthorizePurge(input) {\n\t\t\tconst batch = deps.purgeGate.authorizeBatch({\n\t\t\t\tmemoryIds: input.memoryIds,\n\t\t\t\treason: input.reason,\n\t\t\t\tauthorizedBy: input.authorizedBy,\n\t\t\t\t...(input.authorizationRef === undefined ? {} : { authorizationRef: input.authorizationRef }),\n\t\t\t});\n\t\t\treturn { batchId: batch.batchId, state: batch.state };\n\t\t},\n\t};\n}\n"]}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory Foundation — per-AgentInstance Memory Scheduler lease (1C.4).
|
|
3
|
+
*
|
|
4
|
+
* Every AgentInstance holds at most ONE active Memory Scheduler lease. The
|
|
5
|
+
* lease binds owner/profile for later recall authorization (1C.5a).
|
|
6
|
+
* When the scheduler is unavailable, acquisition fails with an explicit
|
|
7
|
+
* structured error — callers can never fall back to reading the Foundation
|
|
8
|
+
* ledger directly (记忆系统设计.md §6.4).
|
|
9
|
+
*/
|
|
10
|
+
export type MemorySchedulerLeaseStateV1 = "active" | "released";
|
|
11
|
+
export interface MemorySchedulerLeaseSnapshotV1 {
|
|
12
|
+
readonly leaseId: string;
|
|
13
|
+
readonly instanceId: string;
|
|
14
|
+
readonly owner: string;
|
|
15
|
+
readonly profileId?: string;
|
|
16
|
+
/** Per-instance generation: increments each time the instance re-acquires. */
|
|
17
|
+
readonly generation: number;
|
|
18
|
+
readonly state: MemorySchedulerLeaseStateV1;
|
|
19
|
+
readonly acquiredAt: number;
|
|
20
|
+
readonly releasedAt?: number;
|
|
21
|
+
}
|
|
22
|
+
/** Explicit structured failure — never a silent fallback to direct ledger reads. */
|
|
23
|
+
export declare class MemorySchedulerUnavailableError extends Error {
|
|
24
|
+
readonly code = "memory_scheduler_unavailable";
|
|
25
|
+
constructor(reason: string);
|
|
26
|
+
}
|
|
27
|
+
export interface MemorySchedulerLeaseV1 {
|
|
28
|
+
readonly leaseId: string;
|
|
29
|
+
readonly instanceId: string;
|
|
30
|
+
readonly owner: string;
|
|
31
|
+
snapshot(): MemorySchedulerLeaseSnapshotV1;
|
|
32
|
+
release(): void;
|
|
33
|
+
}
|
|
34
|
+
export interface MemorySchedulerInspectV1 {
|
|
35
|
+
readonly available: boolean;
|
|
36
|
+
readonly activeLeases: readonly MemorySchedulerLeaseSnapshotV1[];
|
|
37
|
+
readonly releasedLeaseCount: number;
|
|
38
|
+
}
|
|
39
|
+
export interface MemorySchedulerV1 {
|
|
40
|
+
acquireForInstance(input: {
|
|
41
|
+
readonly instanceId: string;
|
|
42
|
+
readonly owner: string;
|
|
43
|
+
readonly profileId?: string;
|
|
44
|
+
}): MemorySchedulerLeaseV1;
|
|
45
|
+
leaseSnapshotOf(instanceId: string): MemorySchedulerLeaseSnapshotV1 | undefined;
|
|
46
|
+
setAvailable(available: boolean): void;
|
|
47
|
+
isAvailable(): boolean;
|
|
48
|
+
inspect(): MemorySchedulerInspectV1;
|
|
49
|
+
}
|
|
50
|
+
export declare function createMemoryScheduler(options?: {
|
|
51
|
+
readonly now?: () => number;
|
|
52
|
+
readonly available?: boolean;
|
|
53
|
+
readonly leaseIdFactory?: (instanceId: string, generation: number) => string;
|
|
54
|
+
}): MemorySchedulerV1;
|
|
55
|
+
//# sourceMappingURL=scheduler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../../src/memory/scheduler.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,MAAM,2BAA2B,GAAG,QAAQ,GAAG,UAAU,CAAC;AAEhE,MAAM,WAAW,8BAA8B;IAC9C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,8EAA8E;IAC9E,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,2BAA2B,CAAC;IAC5C,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,sFAAoF;AACpF,qBAAa,+BAAgC,SAAQ,KAAK;IACzD,QAAQ,CAAC,IAAI,kCAAkC;IAC/C,YAAY,MAAM,EAAE,MAAM,EAGzB;CACD;AAED,MAAM,WAAW,sBAAsB;IACtC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,IAAI,8BAA8B,CAAC;IAC3C,OAAO,IAAI,IAAI,CAAC;CAChB;AAED,MAAM,WAAW,wBAAwB;IACxC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,YAAY,EAAE,SAAS,8BAA8B,EAAE,CAAC;IACjE,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;CACpC;AAMD,MAAM,WAAW,iBAAiB;IACjC,kBAAkB,CAAC,KAAK,EAAE;QACzB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;QAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;QACvB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;KAC5B,GAAG,sBAAsB,CAAC;IAC3B,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,8BAA8B,GAAG,SAAS,CAAC;IAChF,YAAY,CAAC,SAAS,EAAE,OAAO,GAAG,IAAI,CAAC;IACvC,WAAW,IAAI,OAAO,CAAC;IACvB,OAAO,IAAI,wBAAwB,CAAC;CACpC;AAED,wBAAgB,qBAAqB,CACpC,OAAO,GAAE;IACR,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IAC5B,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,KAAK,MAAM,CAAC;CACxE,GACJ,iBAAiB,CA0EnB","sourcesContent":["/**\n * Memory Foundation — per-AgentInstance Memory Scheduler lease (1C.4).\n *\n * Every AgentInstance holds at most ONE active Memory Scheduler lease. The\n * lease binds owner/profile for later recall authorization (1C.5a).\n * When the scheduler is unavailable, acquisition fails with an explicit\n * structured error — callers can never fall back to reading the Foundation\n * ledger directly (记忆系统设计.md §6.4).\n */\n\nexport type MemorySchedulerLeaseStateV1 = \"active\" | \"released\";\n\nexport interface MemorySchedulerLeaseSnapshotV1 {\n\treadonly leaseId: string;\n\treadonly instanceId: string;\n\treadonly owner: string;\n\treadonly profileId?: string;\n\t/** Per-instance generation: increments each time the instance re-acquires. */\n\treadonly generation: number;\n\treadonly state: MemorySchedulerLeaseStateV1;\n\treadonly acquiredAt: number;\n\treadonly releasedAt?: number;\n}\n\n/** Explicit structured failure — never a silent fallback to direct ledger reads. */\nexport class MemorySchedulerUnavailableError extends Error {\n\treadonly code = \"memory_scheduler_unavailable\";\n\tconstructor(reason: string) {\n\t\tsuper(`Memory scheduler is unavailable: ${reason}`);\n\t\tthis.name = \"MemorySchedulerUnavailableError\";\n\t}\n}\n\nexport interface MemorySchedulerLeaseV1 {\n\treadonly leaseId: string;\n\treadonly instanceId: string;\n\treadonly owner: string;\n\tsnapshot(): MemorySchedulerLeaseSnapshotV1;\n\trelease(): void;\n}\n\nexport interface MemorySchedulerInspectV1 {\n\treadonly available: boolean;\n\treadonly activeLeases: readonly MemorySchedulerLeaseSnapshotV1[];\n\treadonly releasedLeaseCount: number;\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\nexport interface MemorySchedulerV1 {\n\tacquireForInstance(input: {\n\t\treadonly instanceId: string;\n\t\treadonly owner: string;\n\t\treadonly profileId?: string;\n\t}): MemorySchedulerLeaseV1;\n\tleaseSnapshotOf(instanceId: string): MemorySchedulerLeaseSnapshotV1 | undefined;\n\tsetAvailable(available: boolean): void;\n\tisAvailable(): boolean;\n\tinspect(): MemorySchedulerInspectV1;\n}\n\nexport function createMemoryScheduler(\n\toptions: {\n\t\treadonly now?: () => number;\n\t\treadonly available?: boolean;\n\t\treadonly leaseIdFactory?: (instanceId: string, generation: number) => string;\n\t} = {},\n): MemorySchedulerV1 {\n\tconst now = options.now ?? (() => Date.now());\n\tlet available = options.available ?? true;\n\tconst byInstance = new Map<string, MemorySchedulerLeaseV1>();\n\tconst generations = new Map<string, number>();\n\tconst releasedHistory: MemorySchedulerLeaseSnapshotV1[] = [];\n\tlet leaseSequence = 0;\n\n\tconst defaultLeaseIdFactory = (instanceId: string, generation: number): string =>\n\t\t`memsched-${instanceId}-g${generation}-${++leaseSequence}`;\n\n\tconst leaseIdFactory =\n\t\toptions.leaseIdFactory ??\n\t\t((instanceId: string, generation: number) => defaultLeaseIdFactory(instanceId, generation));\n\n\treturn {\n\t\tacquireForInstance(input) {\n\t\t\tassertNonEmptyString(input.instanceId, \"instanceId\");\n\t\t\tassertNonEmptyString(input.owner, \"owner\");\n\t\t\tif (!available) {\n\t\t\t\tthrow new MemorySchedulerUnavailableError(\"the memory scheduler is not accepting leases\");\n\t\t\t}\n\t\t\tconst existing = byInstance.get(input.instanceId);\n\t\t\tif (existing && existing.snapshot().state === \"active\") {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Instance ${input.instanceId} already holds an active memory scheduler lease: ${existing.leaseId}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst generation = (generations.get(input.instanceId) ?? 0) + 1;\n\t\t\tgenerations.set(input.instanceId, generation);\n\t\t\tconst snapshot: MemorySchedulerLeaseSnapshotV1 = Object.freeze({\n\t\t\t\tleaseId: leaseIdFactory(input.instanceId, generation),\n\t\t\t\tinstanceId: input.instanceId,\n\t\t\t\towner: input.owner,\n\t\t\t\t...(input.profileId === undefined ? {} : { profileId: input.profileId }),\n\t\t\t\tgeneration,\n\t\t\t\tstate: \"active\" as const,\n\t\t\t\tacquiredAt: now(),\n\t\t\t});\n\t\t\tlet released = false;\n\t\t\tconst lease: MemorySchedulerLeaseV1 = {\n\t\t\t\tleaseId: snapshot.leaseId,\n\t\t\t\tinstanceId: input.instanceId,\n\t\t\t\towner: input.owner,\n\t\t\t\tsnapshot: () => snapshot,\n\t\t\t\trelease(): void {\n\t\t\t\t\tif (released) return;\n\t\t\t\t\treleased = true;\n\t\t\t\t\tbyInstance.delete(input.instanceId);\n\t\t\t\t\treleasedHistory.push(Object.freeze({ ...snapshot, state: \"released\" as const, releasedAt: now() }));\n\t\t\t\t},\n\t\t\t};\n\t\t\tbyInstance.set(input.instanceId, lease);\n\t\t\treturn lease;\n\t\t},\n\t\tleaseSnapshotOf(instanceId) {\n\t\t\tconst lease = byInstance.get(instanceId);\n\t\t\treturn lease?.snapshot();\n\t\t},\n\t\tsetAvailable(nextAvailable) {\n\t\t\tavailable = nextAvailable;\n\t\t},\n\t\tisAvailable() {\n\t\t\treturn available;\n\t\t},\n\t\tinspect() {\n\t\t\tconst activeLeases = [...byInstance.values()].map((lease) => lease.snapshot());\n\t\t\treturn {\n\t\t\t\tavailable,\n\t\t\t\tactiveLeases: Object.freeze(activeLeases),\n\t\t\t\treleasedLeaseCount: releasedHistory.length,\n\t\t\t};\n\t\t},\n\t};\n}\n"]}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory Foundation — per-AgentInstance Memory Scheduler lease (1C.4).
|
|
3
|
+
*
|
|
4
|
+
* Every AgentInstance holds at most ONE active Memory Scheduler lease. The
|
|
5
|
+
* lease binds owner/profile for later recall authorization (1C.5a).
|
|
6
|
+
* When the scheduler is unavailable, acquisition fails with an explicit
|
|
7
|
+
* structured error — callers can never fall back to reading the Foundation
|
|
8
|
+
* ledger directly (记忆系统设计.md §6.4).
|
|
9
|
+
*/
|
|
10
|
+
/** Explicit structured failure — never a silent fallback to direct ledger reads. */
|
|
11
|
+
export class MemorySchedulerUnavailableError extends Error {
|
|
12
|
+
code = "memory_scheduler_unavailable";
|
|
13
|
+
constructor(reason) {
|
|
14
|
+
super(`Memory scheduler is unavailable: ${reason}`);
|
|
15
|
+
this.name = "MemorySchedulerUnavailableError";
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function assertNonEmptyString(value, label) {
|
|
19
|
+
if (typeof value !== "string" || value.trim().length === 0)
|
|
20
|
+
throw new Error(`${label} must be a non-empty string`);
|
|
21
|
+
}
|
|
22
|
+
export function createMemoryScheduler(options = {}) {
|
|
23
|
+
const now = options.now ?? (() => Date.now());
|
|
24
|
+
let available = options.available ?? true;
|
|
25
|
+
const byInstance = new Map();
|
|
26
|
+
const generations = new Map();
|
|
27
|
+
const releasedHistory = [];
|
|
28
|
+
let leaseSequence = 0;
|
|
29
|
+
const defaultLeaseIdFactory = (instanceId, generation) => `memsched-${instanceId}-g${generation}-${++leaseSequence}`;
|
|
30
|
+
const leaseIdFactory = options.leaseIdFactory ??
|
|
31
|
+
((instanceId, generation) => defaultLeaseIdFactory(instanceId, generation));
|
|
32
|
+
return {
|
|
33
|
+
acquireForInstance(input) {
|
|
34
|
+
assertNonEmptyString(input.instanceId, "instanceId");
|
|
35
|
+
assertNonEmptyString(input.owner, "owner");
|
|
36
|
+
if (!available) {
|
|
37
|
+
throw new MemorySchedulerUnavailableError("the memory scheduler is not accepting leases");
|
|
38
|
+
}
|
|
39
|
+
const existing = byInstance.get(input.instanceId);
|
|
40
|
+
if (existing && existing.snapshot().state === "active") {
|
|
41
|
+
throw new Error(`Instance ${input.instanceId} already holds an active memory scheduler lease: ${existing.leaseId}`);
|
|
42
|
+
}
|
|
43
|
+
const generation = (generations.get(input.instanceId) ?? 0) + 1;
|
|
44
|
+
generations.set(input.instanceId, generation);
|
|
45
|
+
const snapshot = Object.freeze({
|
|
46
|
+
leaseId: leaseIdFactory(input.instanceId, generation),
|
|
47
|
+
instanceId: input.instanceId,
|
|
48
|
+
owner: input.owner,
|
|
49
|
+
...(input.profileId === undefined ? {} : { profileId: input.profileId }),
|
|
50
|
+
generation,
|
|
51
|
+
state: "active",
|
|
52
|
+
acquiredAt: now(),
|
|
53
|
+
});
|
|
54
|
+
let released = false;
|
|
55
|
+
const lease = {
|
|
56
|
+
leaseId: snapshot.leaseId,
|
|
57
|
+
instanceId: input.instanceId,
|
|
58
|
+
owner: input.owner,
|
|
59
|
+
snapshot: () => snapshot,
|
|
60
|
+
release() {
|
|
61
|
+
if (released)
|
|
62
|
+
return;
|
|
63
|
+
released = true;
|
|
64
|
+
byInstance.delete(input.instanceId);
|
|
65
|
+
releasedHistory.push(Object.freeze({ ...snapshot, state: "released", releasedAt: now() }));
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
byInstance.set(input.instanceId, lease);
|
|
69
|
+
return lease;
|
|
70
|
+
},
|
|
71
|
+
leaseSnapshotOf(instanceId) {
|
|
72
|
+
const lease = byInstance.get(instanceId);
|
|
73
|
+
return lease?.snapshot();
|
|
74
|
+
},
|
|
75
|
+
setAvailable(nextAvailable) {
|
|
76
|
+
available = nextAvailable;
|
|
77
|
+
},
|
|
78
|
+
isAvailable() {
|
|
79
|
+
return available;
|
|
80
|
+
},
|
|
81
|
+
inspect() {
|
|
82
|
+
const activeLeases = [...byInstance.values()].map((lease) => lease.snapshot());
|
|
83
|
+
return {
|
|
84
|
+
available,
|
|
85
|
+
activeLeases: Object.freeze(activeLeases),
|
|
86
|
+
releasedLeaseCount: releasedHistory.length,
|
|
87
|
+
};
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=scheduler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scheduler.js","sourceRoot":"","sources":["../../src/memory/scheduler.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Memory Foundation — per-AgentInstance Memory Scheduler lease (1C.4).\n *\n * Every AgentInstance holds at most ONE active Memory Scheduler lease. The\n * lease binds owner/profile for later recall authorization (1C.5a).\n * When the scheduler is unavailable, acquisition fails with an explicit\n * structured error — callers can never fall back to reading the Foundation\n * ledger directly (记忆系统设计.md §6.4).\n */\n\nexport type MemorySchedulerLeaseStateV1 = \"active\" | \"released\";\n\nexport interface MemorySchedulerLeaseSnapshotV1 {\n\treadonly leaseId: string;\n\treadonly instanceId: string;\n\treadonly owner: string;\n\treadonly profileId?: string;\n\t/** Per-instance generation: increments each time the instance re-acquires. */\n\treadonly generation: number;\n\treadonly state: MemorySchedulerLeaseStateV1;\n\treadonly acquiredAt: number;\n\treadonly releasedAt?: number;\n}\n\n/** Explicit structured failure — never a silent fallback to direct ledger reads. */\nexport class MemorySchedulerUnavailableError extends Error {\n\treadonly code = \"memory_scheduler_unavailable\";\n\tconstructor(reason: string) {\n\t\tsuper(`Memory scheduler is unavailable: ${reason}`);\n\t\tthis.name = \"MemorySchedulerUnavailableError\";\n\t}\n}\n\nexport interface MemorySchedulerLeaseV1 {\n\treadonly leaseId: string;\n\treadonly instanceId: string;\n\treadonly owner: string;\n\tsnapshot(): MemorySchedulerLeaseSnapshotV1;\n\trelease(): void;\n}\n\nexport interface MemorySchedulerInspectV1 {\n\treadonly available: boolean;\n\treadonly activeLeases: readonly MemorySchedulerLeaseSnapshotV1[];\n\treadonly releasedLeaseCount: number;\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\nexport interface MemorySchedulerV1 {\n\tacquireForInstance(input: {\n\t\treadonly instanceId: string;\n\t\treadonly owner: string;\n\t\treadonly profileId?: string;\n\t}): MemorySchedulerLeaseV1;\n\tleaseSnapshotOf(instanceId: string): MemorySchedulerLeaseSnapshotV1 | undefined;\n\tsetAvailable(available: boolean): void;\n\tisAvailable(): boolean;\n\tinspect(): MemorySchedulerInspectV1;\n}\n\nexport function createMemoryScheduler(\n\toptions: {\n\t\treadonly now?: () => number;\n\t\treadonly available?: boolean;\n\t\treadonly leaseIdFactory?: (instanceId: string, generation: number) => string;\n\t} = {},\n): MemorySchedulerV1 {\n\tconst now = options.now ?? (() => Date.now());\n\tlet available = options.available ?? true;\n\tconst byInstance = new Map<string, MemorySchedulerLeaseV1>();\n\tconst generations = new Map<string, number>();\n\tconst releasedHistory: MemorySchedulerLeaseSnapshotV1[] = [];\n\tlet leaseSequence = 0;\n\n\tconst defaultLeaseIdFactory = (instanceId: string, generation: number): string =>\n\t\t`memsched-${instanceId}-g${generation}-${++leaseSequence}`;\n\n\tconst leaseIdFactory =\n\t\toptions.leaseIdFactory ??\n\t\t((instanceId: string, generation: number) => defaultLeaseIdFactory(instanceId, generation));\n\n\treturn {\n\t\tacquireForInstance(input) {\n\t\t\tassertNonEmptyString(input.instanceId, \"instanceId\");\n\t\t\tassertNonEmptyString(input.owner, \"owner\");\n\t\t\tif (!available) {\n\t\t\t\tthrow new MemorySchedulerUnavailableError(\"the memory scheduler is not accepting leases\");\n\t\t\t}\n\t\t\tconst existing = byInstance.get(input.instanceId);\n\t\t\tif (existing && existing.snapshot().state === \"active\") {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Instance ${input.instanceId} already holds an active memory scheduler lease: ${existing.leaseId}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst generation = (generations.get(input.instanceId) ?? 0) + 1;\n\t\t\tgenerations.set(input.instanceId, generation);\n\t\t\tconst snapshot: MemorySchedulerLeaseSnapshotV1 = Object.freeze({\n\t\t\t\tleaseId: leaseIdFactory(input.instanceId, generation),\n\t\t\t\tinstanceId: input.instanceId,\n\t\t\t\towner: input.owner,\n\t\t\t\t...(input.profileId === undefined ? {} : { profileId: input.profileId }),\n\t\t\t\tgeneration,\n\t\t\t\tstate: \"active\" as const,\n\t\t\t\tacquiredAt: now(),\n\t\t\t});\n\t\t\tlet released = false;\n\t\t\tconst lease: MemorySchedulerLeaseV1 = {\n\t\t\t\tleaseId: snapshot.leaseId,\n\t\t\t\tinstanceId: input.instanceId,\n\t\t\t\towner: input.owner,\n\t\t\t\tsnapshot: () => snapshot,\n\t\t\t\trelease(): void {\n\t\t\t\t\tif (released) return;\n\t\t\t\t\treleased = true;\n\t\t\t\t\tbyInstance.delete(input.instanceId);\n\t\t\t\t\treleasedHistory.push(Object.freeze({ ...snapshot, state: \"released\" as const, releasedAt: now() }));\n\t\t\t\t},\n\t\t\t};\n\t\t\tbyInstance.set(input.instanceId, lease);\n\t\t\treturn lease;\n\t\t},\n\t\tleaseSnapshotOf(instanceId) {\n\t\t\tconst lease = byInstance.get(instanceId);\n\t\t\treturn lease?.snapshot();\n\t\t},\n\t\tsetAvailable(nextAvailable) {\n\t\t\tavailable = nextAvailable;\n\t\t},\n\t\tisAvailable() {\n\t\t\treturn available;\n\t\t},\n\t\tinspect() {\n\t\t\tconst activeLeases = [...byInstance.values()].map((lease) => lease.snapshot());\n\t\t\treturn {\n\t\t\t\tavailable,\n\t\t\t\tactiveLeases: Object.freeze(activeLeases),\n\t\t\t\treleasedLeaseCount: releasedHistory.length,\n\t\t\t};\n\t\t},\n\t};\n}\n"]}
|