@graphorin/memory 0.6.0 → 0.7.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/CHANGELOG.md +83 -0
- package/README.md +24 -7
- package/dist/conflict/index.js +0 -1
- package/dist/conflict/pipeline.js +0 -1
- package/dist/conflict/pipeline.js.map +1 -1
- package/dist/conflict/stages/stage3-heuristic-regex.js +0 -1
- package/dist/conflict/stages/stage3-heuristic-regex.js.map +1 -1
- package/dist/conflict/types.d.ts +1 -1
- package/dist/conflict/types.js.map +1 -1
- package/dist/consolidator/index.js +2 -0
- package/dist/consolidator/index.js.map +1 -1
- package/dist/consolidator/lock.js +7 -1
- package/dist/consolidator/lock.js.map +1 -1
- package/dist/consolidator/phases/deep.js +19 -12
- package/dist/consolidator/phases/deep.js.map +1 -1
- package/dist/consolidator/phases/induce.d.ts.map +1 -1
- package/dist/consolidator/phases/induce.js +2 -3
- package/dist/consolidator/phases/induce.js.map +1 -1
- package/dist/consolidator/phases/reflect.js +35 -8
- package/dist/consolidator/phases/reflect.js.map +1 -1
- package/dist/consolidator/phases/standard.js +199 -28
- package/dist/consolidator/phases/standard.js.map +1 -1
- package/dist/consolidator/reconcile.js +16 -15
- package/dist/consolidator/reconcile.js.map +1 -1
- package/dist/consolidator/runtime.d.ts.map +1 -1
- package/dist/consolidator/runtime.js +129 -47
- package/dist/consolidator/runtime.js.map +1 -1
- package/dist/consolidator/scheduler.d.ts +17 -3
- package/dist/consolidator/scheduler.d.ts.map +1 -1
- package/dist/consolidator/scheduler.js.map +1 -1
- package/dist/consolidator/types.d.ts +30 -1
- package/dist/consolidator/types.d.ts.map +1 -1
- package/dist/consolidator/types.js +10 -0
- package/dist/consolidator/types.js.map +1 -1
- package/dist/context-engine/compaction/clear-tool-results.d.ts +1 -1
- package/dist/context-engine/compaction/clear-tool-results.js +1 -1
- package/dist/context-engine/compaction/clear-tool-results.js.map +1 -1
- package/dist/context-engine/compaction/compactor.d.ts +1 -0
- package/dist/context-engine/compaction/compactor.d.ts.map +1 -1
- package/dist/context-engine/compaction/compactor.js +32 -8
- package/dist/context-engine/compaction/compactor.js.map +1 -1
- package/dist/context-engine/compaction/hooks/types.d.ts +1 -1
- package/dist/context-engine/compaction/index.d.ts +1 -0
- package/dist/context-engine/compaction/templates/marker.d.ts +33 -0
- package/dist/context-engine/compaction/templates/marker.d.ts.map +1 -0
- package/dist/context-engine/compaction/templates/marker.js +34 -0
- package/dist/context-engine/compaction/templates/marker.js.map +1 -0
- package/dist/context-engine/compaction/templates/summary-9-section.d.ts +1 -1
- package/dist/context-engine/compaction/templates/summary-9-section.d.ts.map +1 -1
- package/dist/context-engine/compaction/templates/summary-9-section.js +4 -3
- package/dist/context-engine/compaction/templates/summary-9-section.js.map +1 -1
- package/dist/context-engine/engine.d.ts +7 -176
- package/dist/context-engine/engine.d.ts.map +1 -1
- package/dist/context-engine/engine.js +0 -2
- package/dist/context-engine/engine.js.map +1 -1
- package/dist/context-engine/index.d.ts +8 -49
- package/dist/context-engine/io-types.d.ts +144 -0
- package/dist/context-engine/io-types.d.ts.map +1 -0
- package/dist/context-engine/locale-packs/types.d.ts +1 -1
- package/dist/context-engine/locale-packs/types.js.map +1 -1
- package/dist/context-engine/privacy-filter.d.ts +1 -2
- package/dist/context-engine/privacy-filter.d.ts.map +1 -1
- package/dist/context-engine/privacy-filter.js +1 -2
- package/dist/context-engine/privacy-filter.js.map +1 -1
- package/dist/errors/index.d.ts +2 -2
- package/dist/errors/index.js +2 -2
- package/dist/errors/index.js.map +1 -1
- package/dist/facade.d.ts +43 -52
- package/dist/facade.d.ts.map +1 -1
- package/dist/facade.js +5 -3
- package/dist/facade.js.map +1 -1
- package/dist/graph/entity-resolver.js +1 -1
- package/dist/graph/entity-resolver.js.map +1 -1
- package/dist/index.d.ts +17 -15
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -7
- package/dist/index.js.map +1 -1
- package/dist/internal/envelope.js +57 -0
- package/dist/internal/envelope.js.map +1 -0
- package/dist/internal/injection-heuristics.js +0 -0
- package/dist/internal/injection-heuristics.js.map +1 -1
- package/dist/internal/llm-json.js +40 -0
- package/dist/internal/llm-json.js.map +1 -0
- package/dist/internal/storage-adapter.d.ts +23 -10
- package/dist/internal/storage-adapter.d.ts.map +1 -1
- package/dist/memory-interface.d.ts +131 -0
- package/dist/memory-interface.d.ts.map +1 -0
- package/dist/package.js +6 -0
- package/dist/package.js.map +1 -0
- package/dist/search/iterative.d.ts.map +1 -1
- package/dist/search/iterative.js +2 -3
- package/dist/search/iterative.js.map +1 -1
- package/dist/search/query-transform.d.ts.map +1 -1
- package/dist/search/query-transform.js +2 -3
- package/dist/search/query-transform.js.map +1 -1
- package/dist/tiers/episodic-memory.js +3 -3
- package/dist/tiers/episodic-memory.js.map +1 -1
- package/dist/tiers/index.d.ts +2 -2
- package/dist/tiers/insight-memory.js +1 -1
- package/dist/tiers/insight-memory.js.map +1 -1
- package/dist/tiers/procedural-memory.d.ts +1 -1
- package/dist/tiers/procedural-memory.js +2 -2
- package/dist/tiers/procedural-memory.js.map +1 -1
- package/dist/tiers/semantic-memory.d.ts +58 -4
- package/dist/tiers/semantic-memory.d.ts.map +1 -1
- package/dist/tiers/semantic-memory.js +103 -20
- package/dist/tiers/semantic-memory.js.map +1 -1
- package/dist/tools/block-tools.d.ts +19 -57
- package/dist/tools/block-tools.d.ts.map +1 -1
- package/dist/tools/block-tools.js.map +1 -1
- package/dist/tools/fact-tools.d.ts +53 -214
- package/dist/tools/fact-tools.d.ts.map +1 -1
- package/dist/tools/fact-tools.js +3 -2
- package/dist/tools/fact-tools.js.map +1 -1
- package/dist/tools/recall-tools.d.ts +34 -175
- package/dist/tools/recall-tools.d.ts.map +1 -1
- package/dist/tools/recall-tools.js +5 -4
- package/dist/tools/recall-tools.js.map +1 -1
- package/dist/tools/runbook-tools.d.ts +15 -58
- package/dist/tools/runbook-tools.d.ts.map +1 -1
- package/dist/tools/runbook-tools.js.map +1 -1
- package/dist/tools/types.d.ts +1 -1
- package/package.json +18 -17
- package/src/conflict/index.ts +29 -0
- package/src/conflict/locale-packs/en.ts +142 -0
- package/src/conflict/locale-packs/index.ts +17 -0
- package/src/conflict/locale-packs/types.ts +138 -0
- package/src/conflict/pipeline.ts +371 -0
- package/src/conflict/stages/helpers.ts +65 -0
- package/src/conflict/stages/stage1-exact-dedup.ts +40 -0
- package/src/conflict/stages/stage2-embedding-three-zone.ts +54 -0
- package/src/conflict/stages/stage3-heuristic-regex.ts +51 -0
- package/src/conflict/stages/stage4-subject-predicate.ts +46 -0
- package/src/conflict/stages/stage5-defer-to-deep.ts +34 -0
- package/src/conflict/types.ts +263 -0
- package/src/consolidator/budget.ts +312 -0
- package/src/consolidator/decay.ts +246 -0
- package/src/consolidator/dlq.ts +61 -0
- package/src/consolidator/errors.ts +87 -0
- package/src/consolidator/idempotency.ts +27 -0
- package/src/consolidator/index.ts +269 -0
- package/src/consolidator/lock.ts +141 -0
- package/src/consolidator/noise-filter.ts +235 -0
- package/src/consolidator/phases/deep.ts +308 -0
- package/src/consolidator/phases/induce.ts +423 -0
- package/src/consolidator/phases/learned-context.ts +233 -0
- package/src/consolidator/phases/light.ts +166 -0
- package/src/consolidator/phases/reflect.ts +504 -0
- package/src/consolidator/phases/standard.ts +1002 -0
- package/src/consolidator/reconcile.ts +273 -0
- package/src/consolidator/runtime.ts +1049 -0
- package/src/consolidator/scheduler.ts +182 -0
- package/src/consolidator/triggers.ts +134 -0
- package/src/consolidator/types.ts +622 -0
- package/src/context-engine/annotations.ts +161 -0
- package/src/context-engine/auto-recall.ts +92 -0
- package/src/context-engine/compaction/clear-tool-results.ts +232 -0
- package/src/context-engine/compaction/compactor.ts +403 -0
- package/src/context-engine/compaction/hooks/reanchor-persona-block.ts +55 -0
- package/src/context-engine/compaction/hooks/reanchor-pinned-facts.ts +66 -0
- package/src/context-engine/compaction/hooks/reanchor-project-rules.ts +57 -0
- package/src/context-engine/compaction/hooks/reanchor-recent-results.ts +116 -0
- package/src/context-engine/compaction/hooks/types.ts +68 -0
- package/src/context-engine/compaction/index.ts +60 -0
- package/src/context-engine/compaction/templates/marker.ts +33 -0
- package/src/context-engine/compaction/templates/summary-9-section.ts +223 -0
- package/src/context-engine/compaction/thresholds.ts +107 -0
- package/src/context-engine/compaction/types.ts +255 -0
- package/src/context-engine/engine.ts +970 -0
- package/src/context-engine/index.ts +118 -0
- package/src/context-engine/io-types.ts +162 -0
- package/src/context-engine/locale-packs/en.ts +107 -0
- package/src/context-engine/locale-packs/index.ts +19 -0
- package/src/context-engine/locale-packs/resolver.ts +197 -0
- package/src/context-engine/locale-packs/types.ts +177 -0
- package/src/context-engine/metadata.ts +114 -0
- package/src/context-engine/preambles/inbound-en.ts +19 -0
- package/src/context-engine/privacy-filter.ts +190 -0
- package/src/context-engine/templates/base-en.ts +19 -0
- package/src/context-engine/templates/composer.ts +91 -0
- package/src/context-engine/token-budget.ts +319 -0
- package/src/context-engine/token-counter.ts +157 -0
- package/src/errors/index.ts +273 -0
- package/src/facade.ts +750 -0
- package/src/graph/entity-resolver.ts +364 -0
- package/src/graph/index.ts +24 -0
- package/src/index.ts +259 -0
- package/src/internal/contextualize.ts +173 -0
- package/src/internal/embedder-binding.ts +53 -0
- package/src/internal/envelope.ts +70 -0
- package/src/internal/id.ts +23 -0
- package/src/internal/injection-heuristics.ts +0 -0
- package/src/internal/llm-json.ts +38 -0
- package/src/internal/spans.ts +35 -0
- package/src/internal/storage-adapter.ts +963 -0
- package/src/memory-interface.ts +158 -0
- package/src/migration/embedder-migration.ts +237 -0
- package/src/migration/index.ts +13 -0
- package/src/search/explain.ts +123 -0
- package/src/search/fit-weights.ts +109 -0
- package/src/search/graph-ppr.ts +38 -0
- package/src/search/index.ts +55 -0
- package/src/search/iterative.ts +658 -0
- package/src/search/query-transform.ts +285 -0
- package/src/search/rrf.ts +225 -0
- package/src/search/trust.ts +55 -0
- package/src/search/types.ts +50 -0
- package/src/tiers/episodic-memory.ts +463 -0
- package/src/tiers/index.ts +50 -0
- package/src/tiers/insight-memory.ts +208 -0
- package/src/tiers/procedural-memory.ts +482 -0
- package/src/tiers/semantic-memory.ts +1729 -0
- package/src/tiers/session-memory.ts +271 -0
- package/src/tiers/shared-memory.ts +62 -0
- package/src/tiers/working-memory.ts +368 -0
- package/src/tools/block-tools.ts +172 -0
- package/src/tools/fact-tools.ts +473 -0
- package/src/tools/index.ts +109 -0
- package/src/tools/recall-tools.ts +303 -0
- package/src/tools/runbook-tools.ts +107 -0
- package/src/tools/taint.ts +39 -0
- package/src/tools/types.ts +37 -0
- package/dist/conflict/locale-packs/index.js +0 -4
- package/dist/context-engine/compaction/index.js +0 -10
- package/dist/context-engine/index.d.ts.map +0 -1
- package/dist/context-engine/index.js +0 -25
- package/dist/context-engine/locale-packs/index.js +0 -4
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import type { Tool } from '@graphorin/core';
|
|
2
|
+
import { tool } from '@graphorin/tools';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import type { MemoryToolDeps } from './types.js';
|
|
5
|
+
|
|
6
|
+
const blockAppendInputSchema = z.object({
|
|
7
|
+
label: z.string().min(1).max(128),
|
|
8
|
+
content: z.string().min(1),
|
|
9
|
+
});
|
|
10
|
+
const blockAppendOutputSchema = z.object({
|
|
11
|
+
label: z.string(),
|
|
12
|
+
length: z.number(),
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* W-013: explicit interfaces instead of `z.infer<typeof schema>` - the
|
|
17
|
+
* inferred aliases baked concrete v3 zod object generics into
|
|
18
|
+
* the published d.ts, which do not typecheck under a zod@4 consumer.
|
|
19
|
+
* Interface<->schema equality is pinned by type tests.
|
|
20
|
+
*/
|
|
21
|
+
export interface BlockAppendInput {
|
|
22
|
+
label: string;
|
|
23
|
+
content: string;
|
|
24
|
+
}
|
|
25
|
+
export interface BlockAppendOutput {
|
|
26
|
+
label: string;
|
|
27
|
+
length: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const blockReplaceInputSchema = z.object({
|
|
31
|
+
label: z.string().min(1).max(128),
|
|
32
|
+
oldUnique: z.string().min(1),
|
|
33
|
+
newText: z.string(),
|
|
34
|
+
});
|
|
35
|
+
const blockReplaceOutputSchema = z.object({
|
|
36
|
+
label: z.string(),
|
|
37
|
+
length: z.number(),
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
export interface BlockReplaceInput {
|
|
41
|
+
label: string;
|
|
42
|
+
oldUnique: string;
|
|
43
|
+
newText: string;
|
|
44
|
+
}
|
|
45
|
+
export interface BlockReplaceOutput {
|
|
46
|
+
label: string;
|
|
47
|
+
length: number;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const blockRethinkInputSchema = z.object({
|
|
51
|
+
label: z.string().min(1).max(128),
|
|
52
|
+
newValue: z.string(),
|
|
53
|
+
});
|
|
54
|
+
const blockRethinkOutputSchema = z.object({
|
|
55
|
+
label: z.string(),
|
|
56
|
+
length: z.number(),
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
export interface BlockRethinkInput {
|
|
60
|
+
label: string;
|
|
61
|
+
newValue: string;
|
|
62
|
+
}
|
|
63
|
+
export interface BlockRethinkOutput {
|
|
64
|
+
label: string;
|
|
65
|
+
length: number;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// W-013 parity gate (compile-time only, erased from the build and the
|
|
69
|
+
// d.ts): each explicit interface must stay MUTUALLY assignable with its
|
|
70
|
+
// schema's inference - a drifted transcription fails `tsc` right here.
|
|
71
|
+
type W013Equals<A, B> = A extends B ? (B extends A ? true : false) : false;
|
|
72
|
+
type W013Assert<T extends true> = T;
|
|
73
|
+
type _W013Check1 = W013Assert<W013Equals<BlockAppendInput, z.infer<typeof blockAppendInputSchema>>>;
|
|
74
|
+
type _W013Check2 = W013Assert<
|
|
75
|
+
W013Equals<BlockAppendOutput, z.infer<typeof blockAppendOutputSchema>>
|
|
76
|
+
>;
|
|
77
|
+
type _W013Check3 = W013Assert<
|
|
78
|
+
W013Equals<BlockReplaceInput, z.infer<typeof blockReplaceInputSchema>>
|
|
79
|
+
>;
|
|
80
|
+
type _W013Check4 = W013Assert<
|
|
81
|
+
W013Equals<BlockReplaceOutput, z.infer<typeof blockReplaceOutputSchema>>
|
|
82
|
+
>;
|
|
83
|
+
type _W013Check5 = W013Assert<
|
|
84
|
+
W013Equals<BlockRethinkInput, z.infer<typeof blockRethinkInputSchema>>
|
|
85
|
+
>;
|
|
86
|
+
type _W013Check6 = W013Assert<
|
|
87
|
+
W013Equals<BlockRethinkOutput, z.infer<typeof blockRethinkOutputSchema>>
|
|
88
|
+
>;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* `block_append` - append text (with a newline separator) to a working
|
|
92
|
+
* memory block.
|
|
93
|
+
*
|
|
94
|
+
* @stable
|
|
95
|
+
*/
|
|
96
|
+
export function createBlockAppendTool(
|
|
97
|
+
deps: MemoryToolDeps,
|
|
98
|
+
): Tool<BlockAppendInput, BlockAppendOutput> {
|
|
99
|
+
return tool<BlockAppendInput, BlockAppendOutput>({
|
|
100
|
+
name: 'block_append',
|
|
101
|
+
description:
|
|
102
|
+
'Append text to a working memory block. The block is identified by its label (registered via memory.working.define(...)). Use this when accumulating notes, observations, or reasoning steps that should persist across turns.',
|
|
103
|
+
inputSchema: blockAppendInputSchema,
|
|
104
|
+
outputSchema: blockAppendOutputSchema,
|
|
105
|
+
sideEffectClass: 'side-effecting',
|
|
106
|
+
sandboxPolicy: 'none',
|
|
107
|
+
sensitivity: 'internal',
|
|
108
|
+
memoryGuardTier: 'memory-aware',
|
|
109
|
+
tags: ['memory', 'working'],
|
|
110
|
+
async execute(input, ctx) {
|
|
111
|
+
const scope = await deps.resolveScope(ctx);
|
|
112
|
+
const block = await deps.working.append(scope, input.label, input.content);
|
|
113
|
+
return { label: block.label, length: block.value.length };
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* `block_replace` - replace a unique substring inside a working
|
|
120
|
+
* memory block. Throws when the substring is missing or appears more
|
|
121
|
+
* than once.
|
|
122
|
+
*
|
|
123
|
+
* @stable
|
|
124
|
+
*/
|
|
125
|
+
export function createBlockReplaceTool(
|
|
126
|
+
deps: MemoryToolDeps,
|
|
127
|
+
): Tool<BlockReplaceInput, BlockReplaceOutput> {
|
|
128
|
+
return tool<BlockReplaceInput, BlockReplaceOutput>({
|
|
129
|
+
name: 'block_replace',
|
|
130
|
+
description:
|
|
131
|
+
'Replace a UNIQUE substring inside a working memory block. The substring must appear exactly once; the call fails fast if missing or non-unique. Use block_rethink for full rewrites.',
|
|
132
|
+
inputSchema: blockReplaceInputSchema,
|
|
133
|
+
outputSchema: blockReplaceOutputSchema,
|
|
134
|
+
sideEffectClass: 'side-effecting',
|
|
135
|
+
sandboxPolicy: 'none',
|
|
136
|
+
sensitivity: 'internal',
|
|
137
|
+
memoryGuardTier: 'memory-aware',
|
|
138
|
+
tags: ['memory', 'working'],
|
|
139
|
+
async execute(input, ctx) {
|
|
140
|
+
const scope = await deps.resolveScope(ctx);
|
|
141
|
+
const block = await deps.working.replace(scope, input.label, input.oldUnique, input.newText);
|
|
142
|
+
return { label: block.label, length: block.value.length };
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* `block_rethink` - rewrite a working memory block from scratch.
|
|
149
|
+
*
|
|
150
|
+
* @stable
|
|
151
|
+
*/
|
|
152
|
+
export function createBlockRethinkTool(
|
|
153
|
+
deps: MemoryToolDeps,
|
|
154
|
+
): Tool<BlockRethinkInput, BlockRethinkOutput> {
|
|
155
|
+
return tool<BlockRethinkInput, BlockRethinkOutput>({
|
|
156
|
+
name: 'block_rethink',
|
|
157
|
+
description:
|
|
158
|
+
"Replace a working memory block's value entirely. Use this when restructuring or summarising the block's contents. For surgical edits, prefer block_replace.",
|
|
159
|
+
inputSchema: blockRethinkInputSchema,
|
|
160
|
+
outputSchema: blockRethinkOutputSchema,
|
|
161
|
+
sideEffectClass: 'side-effecting',
|
|
162
|
+
sandboxPolicy: 'none',
|
|
163
|
+
sensitivity: 'internal',
|
|
164
|
+
memoryGuardTier: 'memory-aware',
|
|
165
|
+
tags: ['memory', 'working'],
|
|
166
|
+
async execute(input, ctx) {
|
|
167
|
+
const scope = await deps.resolveScope(ctx);
|
|
168
|
+
const block = await deps.working.rethink(scope, input.label, () => input.newValue);
|
|
169
|
+
return { label: block.label, length: block.value.length };
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
}
|
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
import type { Tool } from '@graphorin/core';
|
|
2
|
+
import { toolReturn } from '@graphorin/core';
|
|
3
|
+
import { tool } from '@graphorin/tools';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { recallTaint } from './taint.js';
|
|
6
|
+
import type { MemoryToolDeps } from './types.js';
|
|
7
|
+
|
|
8
|
+
const sensitivityEnum = z.enum(['public', 'internal', 'secret']);
|
|
9
|
+
const provenanceEnum = z.enum([
|
|
10
|
+
'user',
|
|
11
|
+
'tool',
|
|
12
|
+
'extraction',
|
|
13
|
+
'reflection',
|
|
14
|
+
'induction',
|
|
15
|
+
'imported',
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
const factRememberInputSchema = z.object({
|
|
19
|
+
text: z.string().min(1).max(8192),
|
|
20
|
+
tags: z.array(z.string().min(1).max(64)).max(32).optional(),
|
|
21
|
+
confidence: z.number().min(0).max(1).optional(),
|
|
22
|
+
sensitivity: sensitivityEnum.optional(),
|
|
23
|
+
validFrom: z.string().datetime().optional(),
|
|
24
|
+
validTo: z.string().datetime().optional(),
|
|
25
|
+
});
|
|
26
|
+
const factRememberOutputSchema = z.object({
|
|
27
|
+
factId: z.string(),
|
|
28
|
+
/**
|
|
29
|
+
* MRET-3: `true` when the write landed in quarantine (hidden from
|
|
30
|
+
* default recall until validated). A synthesized consolidator write or
|
|
31
|
+
* a candidate that tripped the injection heuristics quarantines.
|
|
32
|
+
*/
|
|
33
|
+
quarantined: z.boolean(),
|
|
34
|
+
/** Why it was quarantined, when it was. */
|
|
35
|
+
quarantineReason: z.enum(['injection', 'synthesized']).optional(),
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* W-013: explicit interfaces instead of `z.infer<typeof schema>` - the
|
|
40
|
+
* inferred aliases baked concrete v3 zod object generics into
|
|
41
|
+
* the published d.ts, which do not typecheck under a zod@4 consumer.
|
|
42
|
+
* Interface<->schema equality is pinned by type tests. Optionals carry
|
|
43
|
+
* `| undefined` to match zod's `.optional()` inference exactly.
|
|
44
|
+
*/
|
|
45
|
+
export interface FactRememberInput {
|
|
46
|
+
text: string;
|
|
47
|
+
tags?: string[] | undefined;
|
|
48
|
+
confidence?: number | undefined;
|
|
49
|
+
sensitivity?: 'public' | 'internal' | 'secret' | undefined;
|
|
50
|
+
validFrom?: string | undefined;
|
|
51
|
+
validTo?: string | undefined;
|
|
52
|
+
}
|
|
53
|
+
export interface FactRememberOutput {
|
|
54
|
+
factId: string;
|
|
55
|
+
quarantined: boolean;
|
|
56
|
+
quarantineReason?: 'injection' | 'synthesized' | undefined;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const factSearchInputSchema = z.object({
|
|
60
|
+
query: z.string().min(1).max(1024),
|
|
61
|
+
topK: z.number().int().positive().max(50).optional(),
|
|
62
|
+
tags: z.array(z.string().min(1)).max(16).optional(),
|
|
63
|
+
asOf: z.string().datetime().optional(),
|
|
64
|
+
});
|
|
65
|
+
const factSearchOutputSchema = z.object({
|
|
66
|
+
hits: z.array(
|
|
67
|
+
z.object({
|
|
68
|
+
factId: z.string(),
|
|
69
|
+
text: z.string(),
|
|
70
|
+
score: z.number(),
|
|
71
|
+
sensitivity: sensitivityEnum,
|
|
72
|
+
provenance: provenanceEnum.optional(),
|
|
73
|
+
/**
|
|
74
|
+
* Set when the fact's validity interval was closed (superseded /
|
|
75
|
+
* expired). Only present on `asOf` reads - default reads exclude
|
|
76
|
+
* such facts entirely (memory-retrieval-01).
|
|
77
|
+
*/
|
|
78
|
+
validTo: z.string().optional(),
|
|
79
|
+
/** Id of the fact that superseded this one, when any. */
|
|
80
|
+
supersededBy: z.string().optional(),
|
|
81
|
+
}),
|
|
82
|
+
),
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
export interface FactSearchInput {
|
|
86
|
+
query: string;
|
|
87
|
+
topK?: number | undefined;
|
|
88
|
+
tags?: string[] | undefined;
|
|
89
|
+
asOf?: string | undefined;
|
|
90
|
+
}
|
|
91
|
+
export interface FactSearchHit {
|
|
92
|
+
factId: string;
|
|
93
|
+
text: string;
|
|
94
|
+
score: number;
|
|
95
|
+
sensitivity: 'public' | 'internal' | 'secret';
|
|
96
|
+
provenance?: 'user' | 'tool' | 'extraction' | 'reflection' | 'induction' | 'imported' | undefined;
|
|
97
|
+
validTo?: string | undefined;
|
|
98
|
+
supersededBy?: string | undefined;
|
|
99
|
+
}
|
|
100
|
+
export interface FactSearchOutput {
|
|
101
|
+
hits: FactSearchHit[];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const factSupersedeInputSchema = z.object({
|
|
105
|
+
oldId: z.string().min(1),
|
|
106
|
+
newText: z.string().min(1).max(8192),
|
|
107
|
+
reason: z.string().max(512).optional(),
|
|
108
|
+
});
|
|
109
|
+
const factSupersedeOutputSchema = z.object({
|
|
110
|
+
oldId: z.string(),
|
|
111
|
+
newId: z.string(),
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
export interface FactSupersedeInput {
|
|
115
|
+
oldId: string;
|
|
116
|
+
newText: string;
|
|
117
|
+
reason?: string | undefined;
|
|
118
|
+
}
|
|
119
|
+
export interface FactSupersedeOutput {
|
|
120
|
+
oldId: string;
|
|
121
|
+
newId: string;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const factForgetInputSchema = z.object({
|
|
125
|
+
factId: z.string().min(1),
|
|
126
|
+
reason: z.string().max(512).optional(),
|
|
127
|
+
});
|
|
128
|
+
const factForgetOutputSchema = z.object({
|
|
129
|
+
factId: z.string(),
|
|
130
|
+
forgotten: z.boolean(),
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
export interface FactForgetInput {
|
|
134
|
+
factId: string;
|
|
135
|
+
reason?: string | undefined;
|
|
136
|
+
}
|
|
137
|
+
export interface FactForgetOutput {
|
|
138
|
+
factId: string;
|
|
139
|
+
forgotten: boolean;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const factHistoryInputSchema = z.object({
|
|
143
|
+
factId: z.string().min(1),
|
|
144
|
+
});
|
|
145
|
+
const factHistoryOutputSchema = z.object({
|
|
146
|
+
chain: z.array(
|
|
147
|
+
z.object({
|
|
148
|
+
factId: z.string(),
|
|
149
|
+
text: z.string(),
|
|
150
|
+
validFrom: z.string().optional(),
|
|
151
|
+
validTo: z.string().optional(),
|
|
152
|
+
supersedes: z.string().optional(),
|
|
153
|
+
supersededBy: z.string().optional(),
|
|
154
|
+
sensitivity: sensitivityEnum,
|
|
155
|
+
}),
|
|
156
|
+
),
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
export interface FactHistoryInput {
|
|
160
|
+
factId: string;
|
|
161
|
+
}
|
|
162
|
+
export interface FactHistoryEntry {
|
|
163
|
+
factId: string;
|
|
164
|
+
text: string;
|
|
165
|
+
validFrom?: string | undefined;
|
|
166
|
+
validTo?: string | undefined;
|
|
167
|
+
supersedes?: string | undefined;
|
|
168
|
+
supersededBy?: string | undefined;
|
|
169
|
+
sensitivity: 'public' | 'internal' | 'secret';
|
|
170
|
+
}
|
|
171
|
+
export interface FactHistoryOutput {
|
|
172
|
+
chain: FactHistoryEntry[];
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const factValidateInputSchema = z.object({
|
|
176
|
+
factId: z.string().min(1),
|
|
177
|
+
reason: z.string().max(512).optional(),
|
|
178
|
+
});
|
|
179
|
+
const factValidateOutputSchema = z.object({
|
|
180
|
+
factId: z.string(),
|
|
181
|
+
validated: z.boolean(),
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
export interface FactValidateInput {
|
|
185
|
+
factId: string;
|
|
186
|
+
reason?: string | undefined;
|
|
187
|
+
}
|
|
188
|
+
export interface FactValidateOutput {
|
|
189
|
+
factId: string;
|
|
190
|
+
validated: boolean;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// W-013 parity gate (compile-time only, erased from the build and the
|
|
194
|
+
// d.ts): each explicit interface must stay MUTUALLY assignable with its
|
|
195
|
+
// schema's inference - a drifted transcription fails `tsc` right here.
|
|
196
|
+
type W013Equals<A, B> = A extends B ? (B extends A ? true : false) : false;
|
|
197
|
+
type W013Assert<T extends true> = T;
|
|
198
|
+
type _W013Check1 = W013Assert<
|
|
199
|
+
W013Equals<FactRememberInput, z.infer<typeof factRememberInputSchema>>
|
|
200
|
+
>;
|
|
201
|
+
type _W013Check2 = W013Assert<
|
|
202
|
+
W013Equals<FactRememberOutput, z.infer<typeof factRememberOutputSchema>>
|
|
203
|
+
>;
|
|
204
|
+
type _W013Check3 = W013Assert<W013Equals<FactSearchInput, z.infer<typeof factSearchInputSchema>>>;
|
|
205
|
+
type _W013Check4 = W013Assert<W013Equals<FactSearchOutput, z.infer<typeof factSearchOutputSchema>>>;
|
|
206
|
+
type _W013Check5 = W013Assert<
|
|
207
|
+
W013Equals<FactSupersedeInput, z.infer<typeof factSupersedeInputSchema>>
|
|
208
|
+
>;
|
|
209
|
+
type _W013Check6 = W013Assert<
|
|
210
|
+
W013Equals<FactSupersedeOutput, z.infer<typeof factSupersedeOutputSchema>>
|
|
211
|
+
>;
|
|
212
|
+
type _W013Check7 = W013Assert<W013Equals<FactForgetInput, z.infer<typeof factForgetInputSchema>>>;
|
|
213
|
+
type _W013Check8 = W013Assert<W013Equals<FactForgetOutput, z.infer<typeof factForgetOutputSchema>>>;
|
|
214
|
+
type _W013Check9 = W013Assert<W013Equals<FactHistoryInput, z.infer<typeof factHistoryInputSchema>>>;
|
|
215
|
+
type _W013Check10 = W013Assert<
|
|
216
|
+
W013Equals<FactHistoryOutput, z.infer<typeof factHistoryOutputSchema>>
|
|
217
|
+
>;
|
|
218
|
+
type _W013Check11 = W013Assert<
|
|
219
|
+
W013Equals<FactValidateInput, z.infer<typeof factValidateInputSchema>>
|
|
220
|
+
>;
|
|
221
|
+
type _W013Check12 = W013Assert<
|
|
222
|
+
W013Equals<FactValidateOutput, z.infer<typeof factValidateOutputSchema>>
|
|
223
|
+
>;
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* `fact_remember` - persist a single semantic fact. The minimum-viable
|
|
227
|
+
* pipeline writes the fact straight through with MD5 deduplication;
|
|
228
|
+
* Phase 10b extends the body with the multi-stage conflict resolution.
|
|
229
|
+
*
|
|
230
|
+
* @stable
|
|
231
|
+
*/
|
|
232
|
+
export function createFactRememberTool(
|
|
233
|
+
deps: MemoryToolDeps,
|
|
234
|
+
): Tool<FactRememberInput, FactRememberOutput> {
|
|
235
|
+
return tool<FactRememberInput, FactRememberOutput>({
|
|
236
|
+
name: 'fact_remember',
|
|
237
|
+
description:
|
|
238
|
+
'Persist an atomic factual statement about the user, the world, or the current session. Prefer one fact per call; the system applies dedup + conflict resolution downstream. Pass `validFrom` / `validTo` for bi-temporal facts and `sensitivity` to control redaction.',
|
|
239
|
+
inputSchema: factRememberInputSchema,
|
|
240
|
+
outputSchema: factRememberOutputSchema,
|
|
241
|
+
sideEffectClass: 'side-effecting',
|
|
242
|
+
sandboxPolicy: 'none',
|
|
243
|
+
sensitivity: 'internal',
|
|
244
|
+
memoryGuardTier: 'memory-aware',
|
|
245
|
+
tags: ['memory', 'semantic'],
|
|
246
|
+
async execute(input, ctx) {
|
|
247
|
+
const scope = await deps.resolveScope(ctx);
|
|
248
|
+
const outcome = await deps.semantic.rememberWithDecision(scope, {
|
|
249
|
+
text: input.text,
|
|
250
|
+
...(input.tags !== undefined ? { tags: input.tags } : {}),
|
|
251
|
+
...(input.confidence !== undefined ? { confidence: input.confidence } : {}),
|
|
252
|
+
...(input.sensitivity !== undefined ? { sensitivity: input.sensitivity } : {}),
|
|
253
|
+
...(input.validFrom !== undefined ? { validFrom: input.validFrom } : {}),
|
|
254
|
+
...(input.validTo !== undefined ? { validTo: input.validTo } : {}),
|
|
255
|
+
});
|
|
256
|
+
// MRET-3: tell the caller when the write was quarantined so a
|
|
257
|
+
// poisoned fact cannot masquerade as a normally-stored one.
|
|
258
|
+
return {
|
|
259
|
+
factId: outcome.fact.id,
|
|
260
|
+
quarantined: outcome.fact.status === 'quarantined',
|
|
261
|
+
...(outcome.quarantineReason !== undefined
|
|
262
|
+
? { quarantineReason: outcome.quarantineReason }
|
|
263
|
+
: {}),
|
|
264
|
+
};
|
|
265
|
+
},
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* `fact_search` - hybrid (vector + FTS5) search over the user's
|
|
271
|
+
* semantic memory. Results merged through the configured reranker.
|
|
272
|
+
*
|
|
273
|
+
* @stable
|
|
274
|
+
*/
|
|
275
|
+
export function createFactSearchTool(
|
|
276
|
+
deps: MemoryToolDeps,
|
|
277
|
+
): Tool<FactSearchInput, FactSearchOutput> {
|
|
278
|
+
return tool<FactSearchInput, FactSearchOutput>({
|
|
279
|
+
name: 'fact_search',
|
|
280
|
+
description:
|
|
281
|
+
"Search the user's long-term factual memory by natural-language query. Returns up to `topK` matched facts with their score and sensitivity. Use this BEFORE asking the user a question they may have answered earlier. Pass `asOf` (ISO-8601) to read memory as it was at a past instant - point-in-time / 'what was true on date X' - instead of the current state.",
|
|
282
|
+
inputSchema: factSearchInputSchema,
|
|
283
|
+
outputSchema: factSearchOutputSchema,
|
|
284
|
+
sideEffectClass: 'read-only',
|
|
285
|
+
sandboxPolicy: 'none',
|
|
286
|
+
sensitivity: 'internal',
|
|
287
|
+
memoryGuardTier: 'pure',
|
|
288
|
+
tags: ['memory', 'semantic'],
|
|
289
|
+
async execute(input, ctx) {
|
|
290
|
+
const scope = await deps.resolveScope(ctx);
|
|
291
|
+
const hits = await deps.semantic.search(scope, input.query, {
|
|
292
|
+
...(input.topK !== undefined ? { topK: input.topK } : {}),
|
|
293
|
+
// MRET-4: forward the tags filter - it was accepted by the
|
|
294
|
+
// schema and silently dropped here.
|
|
295
|
+
...(input.tags !== undefined && input.tags.length > 0 ? { tags: input.tags } : {}),
|
|
296
|
+
...(input.asOf !== undefined ? { asOf: input.asOf } : {}),
|
|
297
|
+
signal: ctx.signal,
|
|
298
|
+
});
|
|
299
|
+
const output = {
|
|
300
|
+
hits: hits.map((hit) => ({
|
|
301
|
+
factId: hit.record.id,
|
|
302
|
+
text: hit.record.text,
|
|
303
|
+
score: hit.score,
|
|
304
|
+
sensitivity: hit.record.sensitivity,
|
|
305
|
+
...(hit.record.provenance !== undefined ? { provenance: hit.record.provenance } : {}),
|
|
306
|
+
// memory-retrieval-01: surface validity metadata so the model
|
|
307
|
+
// can tell a historical (asOf) hit from a current one.
|
|
308
|
+
...(hit.record.validTo !== undefined ? { validTo: hit.record.validTo } : {}),
|
|
309
|
+
...(hit.record.supersededBy !== undefined
|
|
310
|
+
? { supersededBy: hit.record.supersededBy }
|
|
311
|
+
: {}),
|
|
312
|
+
})),
|
|
313
|
+
};
|
|
314
|
+
// C6: recalled quarantined / foreign-provenance content re-arms the
|
|
315
|
+
// taint ledger - the cross-session poisoning leg.
|
|
316
|
+
const taint = recallTaint(hits.map((h) => h.record));
|
|
317
|
+
return taint === undefined ? output : toolReturn({ output, taint });
|
|
318
|
+
},
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* `fact_supersede` - soft-supersede an old fact by storing a new one
|
|
324
|
+
* that replaces it. The old fact is kept for replay but no longer
|
|
325
|
+
* surfaced by default reads (they evaluate validity at NOW); it stays
|
|
326
|
+
* reachable via `asOf` / inspector paths.
|
|
327
|
+
*
|
|
328
|
+
* @stable
|
|
329
|
+
*/
|
|
330
|
+
export function createFactSupersedeTool(
|
|
331
|
+
deps: MemoryToolDeps,
|
|
332
|
+
): Tool<FactSupersedeInput, FactSupersedeOutput> {
|
|
333
|
+
return tool<FactSupersedeInput, FactSupersedeOutput>({
|
|
334
|
+
name: 'fact_supersede',
|
|
335
|
+
description:
|
|
336
|
+
"Mark an old fact superseded by a new one. Use this when the user's situation changed (moved cities, updated a goal, switched a preference) - the old fact is preserved for replay but no longer surfaced as current.",
|
|
337
|
+
inputSchema: factSupersedeInputSchema,
|
|
338
|
+
outputSchema: factSupersedeOutputSchema,
|
|
339
|
+
sideEffectClass: 'side-effecting',
|
|
340
|
+
sandboxPolicy: 'none',
|
|
341
|
+
sensitivity: 'internal',
|
|
342
|
+
memoryGuardTier: 'memory-aware',
|
|
343
|
+
tags: ['memory', 'semantic'],
|
|
344
|
+
async execute(input, ctx) {
|
|
345
|
+
const scope = await deps.resolveScope(ctx);
|
|
346
|
+
const result = await deps.semantic.supersede(
|
|
347
|
+
scope,
|
|
348
|
+
input.oldId,
|
|
349
|
+
{ text: input.newText },
|
|
350
|
+
input.reason,
|
|
351
|
+
);
|
|
352
|
+
return { oldId: result.old, newId: result.new.id };
|
|
353
|
+
},
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* `fact_forget` - soft-delete a fact (kept for replay; never hard-
|
|
359
|
+
* deleted at this layer).
|
|
360
|
+
*
|
|
361
|
+
* @stable
|
|
362
|
+
*/
|
|
363
|
+
export function createFactForgetTool(
|
|
364
|
+
deps: MemoryToolDeps,
|
|
365
|
+
): Tool<FactForgetInput, FactForgetOutput> {
|
|
366
|
+
return tool<FactForgetInput, FactForgetOutput>({
|
|
367
|
+
name: 'fact_forget',
|
|
368
|
+
description:
|
|
369
|
+
'Soft-delete a fact. The record is preserved for replay but no longer surfaced by fact_search. Use only for explicit user-driven removal (privacy / user request).',
|
|
370
|
+
inputSchema: factForgetInputSchema,
|
|
371
|
+
outputSchema: factForgetOutputSchema,
|
|
372
|
+
sideEffectClass: 'side-effecting',
|
|
373
|
+
sandboxPolicy: 'none',
|
|
374
|
+
sensitivity: 'internal',
|
|
375
|
+
memoryGuardTier: 'memory-aware',
|
|
376
|
+
tags: ['memory', 'semantic'],
|
|
377
|
+
async execute(input, ctx) {
|
|
378
|
+
const scope = await deps.resolveScope(ctx);
|
|
379
|
+
await deps.semantic.forget(scope, input.factId, input.reason);
|
|
380
|
+
return { factId: input.factId, forgotten: true };
|
|
381
|
+
},
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* `fact_history` - trace how a fact changed over time. Returns the
|
|
387
|
+
* full bi-temporal supersede chain the given fact belongs to, oldest →
|
|
388
|
+
* newest, including superseded entries, so the agent can answer "what
|
|
389
|
+
* did the user say before" / "how did this change". Read-only. P0-2.
|
|
390
|
+
*
|
|
391
|
+
* @stable
|
|
392
|
+
*/
|
|
393
|
+
export function createFactHistoryTool(
|
|
394
|
+
deps: MemoryToolDeps,
|
|
395
|
+
): Tool<FactHistoryInput, FactHistoryOutput> {
|
|
396
|
+
return tool<FactHistoryInput, FactHistoryOutput>({
|
|
397
|
+
name: 'fact_history',
|
|
398
|
+
description:
|
|
399
|
+
"Trace how a fact changed over time. Given a factId, returns its full bi-temporal supersede chain (oldest → newest), including superseded entries, with each entry's validFrom/validTo. Use this to answer 'how did this change' or 'what was the previous value'.",
|
|
400
|
+
inputSchema: factHistoryInputSchema,
|
|
401
|
+
outputSchema: factHistoryOutputSchema,
|
|
402
|
+
sideEffectClass: 'read-only',
|
|
403
|
+
sandboxPolicy: 'none',
|
|
404
|
+
sensitivity: 'internal',
|
|
405
|
+
memoryGuardTier: 'pure',
|
|
406
|
+
tags: ['memory', 'semantic', 'temporal'],
|
|
407
|
+
async execute(input, ctx) {
|
|
408
|
+
const scope = await deps.resolveScope(ctx);
|
|
409
|
+
const chain = await deps.semantic.history(scope, input.factId);
|
|
410
|
+
return {
|
|
411
|
+
chain: chain.map((f) => ({
|
|
412
|
+
factId: f.id,
|
|
413
|
+
text: f.text,
|
|
414
|
+
...(f.validFrom !== undefined ? { validFrom: f.validFrom } : {}),
|
|
415
|
+
...(f.validTo !== undefined ? { validTo: f.validTo } : {}),
|
|
416
|
+
...(f.supersedes !== undefined ? { supersedes: f.supersedes } : {}),
|
|
417
|
+
...(f.supersededBy !== undefined ? { supersededBy: f.supersededBy } : {}),
|
|
418
|
+
sensitivity: f.sensitivity,
|
|
419
|
+
})),
|
|
420
|
+
};
|
|
421
|
+
},
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* `fact_validate` - promote a quarantined fact to active (P1-4). The
|
|
427
|
+
* validation path that admits a synthesized (consolidator / reflection)
|
|
428
|
+
* memory into action-driving recall once it has been reviewed; the
|
|
429
|
+
* promotion is audited in `memory_history`.
|
|
430
|
+
*
|
|
431
|
+
* MRET-3 / MST-1 - two gates close the one-turn memory-poisoning chain
|
|
432
|
+
* (`fact_remember(poison)` → `fact_validate(id)` → active recall):
|
|
433
|
+
*
|
|
434
|
+
* 1. `needsApproval: true` - the run suspends for a human decision
|
|
435
|
+
* before this tool ever executes, so the agent cannot silently
|
|
436
|
+
* promote any quarantined fact.
|
|
437
|
+
* 2. The underlying `SemanticMemory.validate(...)` re-checks the fact's
|
|
438
|
+
* text against the injection heuristics and **refuses** (no `force`
|
|
439
|
+
* is passed here) - an injection-flagged memory cannot be promoted by
|
|
440
|
+
* the agent at all. Only an operator, via the programmatic API with
|
|
441
|
+
* `{ force: true }`, can override after review.
|
|
442
|
+
*
|
|
443
|
+
* Synthesized-but-clean consolidator writes promote normally once
|
|
444
|
+
* approved.
|
|
445
|
+
*
|
|
446
|
+
* @stable
|
|
447
|
+
*/
|
|
448
|
+
export function createFactValidateTool(
|
|
449
|
+
deps: MemoryToolDeps,
|
|
450
|
+
): Tool<FactValidateInput, FactValidateOutput> {
|
|
451
|
+
return tool<FactValidateInput, FactValidateOutput>({
|
|
452
|
+
name: 'fact_validate',
|
|
453
|
+
description:
|
|
454
|
+
'Promote a quarantined fact to active so it becomes eligible for normal recall. Quarantined facts are memories the system synthesized or flagged as risky; validating one is a deliberate, human-approved admission and never a routine step. This action requires approval, and facts flagged as prompt-injection cannot be promoted here - they are an operator-only decision.',
|
|
455
|
+
inputSchema: factValidateInputSchema,
|
|
456
|
+
outputSchema: factValidateOutputSchema,
|
|
457
|
+
// MRET-3: the only real gate on a model-callable tool - suspend the
|
|
458
|
+
// run for a human decision before promoting anything out of quarantine.
|
|
459
|
+
needsApproval: true,
|
|
460
|
+
sideEffectClass: 'side-effecting',
|
|
461
|
+
sandboxPolicy: 'none',
|
|
462
|
+
sensitivity: 'internal',
|
|
463
|
+
memoryGuardTier: 'memory-aware',
|
|
464
|
+
tags: ['memory', 'semantic', 'safety'],
|
|
465
|
+
async execute(input, ctx) {
|
|
466
|
+
const scope = await deps.resolveScope(ctx);
|
|
467
|
+
// No `force`: the agent path can never promote an injection-flagged
|
|
468
|
+
// fact (validate throws QuarantinePromotionRefusedError for those).
|
|
469
|
+
await deps.semantic.validate(scope, input.factId, input.reason);
|
|
470
|
+
return { factId: input.factId, validated: true };
|
|
471
|
+
},
|
|
472
|
+
});
|
|
473
|
+
}
|