@mastra/memory 1.31.0-alpha.1 → 1.31.0-alpha.2
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/dist/docs/SKILL.md +1 -1
- package/dist/docs/assets/SOURCE_MAP.json +1 -1
- package/dist/docs/references/docs-memory-observational-memory.md +33 -0
- package/dist/docs/references/reference-memory-observational-memory.md +34 -4
- package/dist/hooks.cjs +158 -0
- package/dist/hooks.cjs.map +1 -0
- package/dist/hooks.d.ts +25 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +156 -0
- package/dist/hooks.js.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/processors/index.cjs +1 -1
- package/dist/processors/index.js +1 -1
- package/dist/processors/observational-memory/hooks.d.ts +63 -0
- package/dist/processors/observational-memory/hooks.d.ts.map +1 -0
- package/dist/processors/observational-memory/observation-groups.d.ts.map +1 -1
- package/dist/processors/observational-memory/observational-memory.d.ts.map +1 -1
- package/dist/processors/observational-memory/processor.d.ts.map +1 -1
- package/dist/processors/observational-memory/types.d.ts +6 -6
- package/dist/{src-BCvV2aGL.cjs → src-C4niho1P.cjs} +172 -44
- package/dist/{src-BCvV2aGL.cjs.map → src-C4niho1P.cjs.map} +1 -1
- package/dist/{src-Ju0u9OWc.js → src-DquR-jTR.js} +173 -45
- package/dist/{src-Ju0u9OWc.js.map → src-DquR-jTR.js.map} +1 -1
- package/dist/tools/om-tools.d.ts +4 -2
- package/dist/tools/om-tools.d.ts.map +1 -1
- package/package.json +13 -3
package/dist/docs/SKILL.md
CHANGED
|
@@ -849,6 +849,39 @@ Transform hooks are always awaited, on every path (manual `observe()`/`reflect()
|
|
|
849
849
|
|
|
850
850
|
Because hooks receive `threadId` and `resourceId`, you can also use them to update [working memory](https://mastra.ai/docs/memory/working-memory) via `memory.updateWorkingMemory()` during a cycle. These external updates aren't atomic with the OM text commit.
|
|
851
851
|
|
|
852
|
+
### Redact skill results
|
|
853
|
+
|
|
854
|
+
Agent skills are injected into the agent as tools (`skill`, `skill_search`, `skill_read`). Their results contain the skill's full instructions or file contents, so without redaction the Observer re-observes that text every time a skill is used. `skillResultRedactor()` is a ready-made `beforeObservation` hook that replaces those results with a placeholder and leaves everything else in place. The tool call survives, so the Observer still records which skill was used and what it was called with.
|
|
855
|
+
|
|
856
|
+
```typescript
|
|
857
|
+
import { Memory } from '@mastra/memory'
|
|
858
|
+
import { skillResultRedactor } from '@mastra/memory/hooks'
|
|
859
|
+
|
|
860
|
+
const memory = new Memory({
|
|
861
|
+
options: {
|
|
862
|
+
observationalMemory: {
|
|
863
|
+
model: 'google/gemini-2.5-flash',
|
|
864
|
+
hooks: {
|
|
865
|
+
beforeObservation: skillResultRedactor(),
|
|
866
|
+
},
|
|
867
|
+
},
|
|
868
|
+
},
|
|
869
|
+
})
|
|
870
|
+
```
|
|
871
|
+
|
|
872
|
+
Pass `toolNames` to redact results from a different set of tools. Because a hook is a function over the messages, it composes with your own transforms by chaining the outputs. Await each chained hook so an async one isn't discarded:
|
|
873
|
+
|
|
874
|
+
```typescript
|
|
875
|
+
const dropSkillResults = skillResultRedactor()
|
|
876
|
+
|
|
877
|
+
hooks: {
|
|
878
|
+
beforeObservation: async input => {
|
|
879
|
+
const messages = (await dropSkillResults(input))?.messages ?? input.messages
|
|
880
|
+
return { messages: messages.filter(m => m.role !== 'signal') }
|
|
881
|
+
},
|
|
882
|
+
}
|
|
883
|
+
```
|
|
884
|
+
|
|
852
885
|
## Migrating existing threads
|
|
853
886
|
|
|
854
887
|
No manual migration needed. OM reads existing messages and observes them lazily when thresholds are exceeded.
|
|
@@ -73,9 +73,9 @@ OM performs thresholding with fast local token estimation. Text uses `tokenx`, a
|
|
|
73
73
|
|
|
74
74
|
**observation.maxTokensPerBatch** (`number`): Maximum tokens per batch when observing multiple threads in resource scope. Threads are chunked into batches of this size and processed in parallel. Lower values mean more parallelism but more API calls.
|
|
75
75
|
|
|
76
|
-
**observation.modelSettings** (`ObservationalMemoryModelSettings`): Model settings for the Observer agent. The maxOutputTokens: 100\_000 default is only applied with default model selection (no model set, "default", or a ModelByInputTokens selector). Custom models get no maxOutputTokens default.
|
|
76
|
+
**observation.modelSettings** (`ObservationalMemoryModelSettings`): Model settings for the Observer agent. The temperature: 0.3 default is only applied when the resolved model is known to support temperature. The maxOutputTokens: 100\_000 default is only applied with default model selection (no model set, "default", or a ModelByInputTokens selector). Custom models get no maxOutputTokens default.
|
|
77
77
|
|
|
78
|
-
**observation.modelSettings.temperature** (`number`): Temperature for generation. Lower values produce more consistent output.
|
|
78
|
+
**observation.modelSettings.temperature** (`number`): Temperature for generation. Lower values produce more consistent output. The 0.3 default is only applied when the resolved model is known to support temperature.
|
|
79
79
|
|
|
80
80
|
**observation.modelSettings.maxOutputTokens** (`number`): Maximum output tokens. Set high to prevent truncation of observations. The 100000 default is only applied with default model selection; custom models get no default.
|
|
81
81
|
|
|
@@ -107,9 +107,9 @@ OM performs thresholding with fast local token estimation. Text uses `tokenx`, a
|
|
|
107
107
|
|
|
108
108
|
**reflection.observationTokens** (`number`): Token count of observations that triggers reflection. When observation tokens exceed this threshold, the Reflector agent is called to condense them.
|
|
109
109
|
|
|
110
|
-
**reflection.modelSettings** (`ObservationalMemoryModelSettings`): Model settings for the Reflector agent. The maxOutputTokens: 100\_000 default is only applied with default model selection (no model set, "default", or a ModelByInputTokens selector). Custom models get no maxOutputTokens default.
|
|
110
|
+
**reflection.modelSettings** (`ObservationalMemoryModelSettings`): Model settings for the Reflector agent. The temperature: 0 default is only applied when the resolved model is known to support temperature. The maxOutputTokens: 100\_000 default is only applied with default model selection (no model set, "default", or a ModelByInputTokens selector). Custom models get no maxOutputTokens default.
|
|
111
111
|
|
|
112
|
-
**reflection.modelSettings.temperature** (`number`): Temperature for generation. Lower values produce more consistent output.
|
|
112
|
+
**reflection.modelSettings.temperature** (`number`): Temperature for generation. Lower values produce more consistent output. The 0 default is only applied when the resolved model is known to support temperature.
|
|
113
113
|
|
|
114
114
|
**reflection.modelSettings.maxOutputTokens** (`number`): Maximum output tokens. Set high to prevent truncation of observations. The 100000 default is only applied with default model selection; custom models get no default.
|
|
115
115
|
|
|
@@ -883,6 +883,36 @@ const selector = new ModelByInputTokens({
|
|
|
883
883
|
|
|
884
884
|
**getThresholds** (`() => number[]`): Returns the configured thresholds in ascending order. Useful for introspection.
|
|
885
885
|
|
|
886
|
+
### skillResultRedactor
|
|
887
|
+
|
|
888
|
+
`skillResultRedactor` builds a `beforeObservation` transform hook that redacts the results of the built-in Agent Skills tools (`skill`, `skill_search`, and `skill_read`) before the Observer model sees them. Each result is replaced with a placeholder while the tool call is kept, so the Observer still records which skill was used and what it was called with, without the skill text.
|
|
889
|
+
|
|
890
|
+
```typescript
|
|
891
|
+
import { Memory } from '@mastra/memory'
|
|
892
|
+
import { skillResultRedactor } from '@mastra/memory/hooks'
|
|
893
|
+
|
|
894
|
+
const memory = new Memory({
|
|
895
|
+
options: {
|
|
896
|
+
observationalMemory: {
|
|
897
|
+
model: 'google/gemini-2.5-flash',
|
|
898
|
+
hooks: {
|
|
899
|
+
beforeObservation: skillResultRedactor(),
|
|
900
|
+
},
|
|
901
|
+
},
|
|
902
|
+
},
|
|
903
|
+
})
|
|
904
|
+
```
|
|
905
|
+
|
|
906
|
+
#### Parameters
|
|
907
|
+
|
|
908
|
+
**toolNames** (`readonly string[]`): Tool names whose results are redacted. Defaults to the built-in skill tools: skill, skill\_search, and skill\_read. Use this to redact a different set of tool results.
|
|
909
|
+
|
|
910
|
+
#### Returns
|
|
911
|
+
|
|
912
|
+
`(input: { messages: MastraDBMessage[] } & ObserveHookContext) => { messages: MastraDBMessage[] } | undefined`
|
|
913
|
+
|
|
914
|
+
The hook returns messages with matching tool results replaced by a placeholder, or `undefined` when no message matched (which passes the payload through unchanged). Tool calls, arguments, and every other message are left as they are.
|
|
915
|
+
|
|
886
916
|
### Related
|
|
887
917
|
|
|
888
918
|
- [Observational Memory](https://mastra.ai/docs/memory/observational-memory)
|
package/dist/hooks.cjs
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region src/processors/observational-memory/hooks.ts
|
|
3
|
+
/**
|
|
4
|
+
* Tool ids of the built-in Agent Skills tools created by `createSkillTools()`
|
|
5
|
+
* in `@mastra/core` (`packages/core/src/workspace/skills/tools.ts`).
|
|
6
|
+
*/
|
|
7
|
+
const SKILL_TOOL_NAMES = [
|
|
8
|
+
"skill",
|
|
9
|
+
"skill_search",
|
|
10
|
+
"skill_read"
|
|
11
|
+
];
|
|
12
|
+
/**
|
|
13
|
+
* Written in place of a redacted tool result. The Observer still records the
|
|
14
|
+
* call and its outcome; only the payload is replaced.
|
|
15
|
+
*/
|
|
16
|
+
const REDACTED_TOOL_RESULT = "[tool result omitted]";
|
|
17
|
+
function isRedactableToolResult(part, toolNames) {
|
|
18
|
+
if (part?.type !== "tool-invocation") return false;
|
|
19
|
+
if (part.toolInvocation.state !== "result") return false;
|
|
20
|
+
const toolName = part.toolInvocation.toolName;
|
|
21
|
+
if (typeof toolName !== "string" || !toolNames.has(toolName)) return false;
|
|
22
|
+
return part.toolInvocation.result !== void 0 || hasStoredModelOutput(part);
|
|
23
|
+
}
|
|
24
|
+
function hasStoredModelOutput(part) {
|
|
25
|
+
if (part?.type !== "tool-invocation") return false;
|
|
26
|
+
const mastra = part.providerMetadata?.mastra;
|
|
27
|
+
return !!mastra && typeof mastra === "object" && "modelOutput" in mastra;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Legacy messages carry tool calls in a second `toolInvocations` array, which
|
|
31
|
+
* `AIV5Adapter` falls back to when `parts` holds no tool invocation
|
|
32
|
+
* (`AIV5Adapter.ts:264`). Leave it alone and a redacted result can be
|
|
33
|
+
* resurrected from that array downstream. Only rewritten when it actually
|
|
34
|
+
* holds a matching result, so non-legacy messages keep their array by
|
|
35
|
+
* reference.
|
|
36
|
+
*/
|
|
37
|
+
function redactLegacyToolInvocations(toolInvocations, toolNames) {
|
|
38
|
+
if (!Array.isArray(toolInvocations)) return {
|
|
39
|
+
toolInvocations,
|
|
40
|
+
changed: false
|
|
41
|
+
};
|
|
42
|
+
let changed = false;
|
|
43
|
+
const next = toolInvocations.map((invocation) => {
|
|
44
|
+
if (invocation?.state !== "result" || typeof invocation.toolName !== "string" || !toolNames.has(invocation.toolName) || invocation.result === void 0) return invocation;
|
|
45
|
+
changed = true;
|
|
46
|
+
return {
|
|
47
|
+
...invocation,
|
|
48
|
+
result: REDACTED_TOOL_RESULT
|
|
49
|
+
};
|
|
50
|
+
});
|
|
51
|
+
return changed ? {
|
|
52
|
+
toolInvocations: next,
|
|
53
|
+
changed: true
|
|
54
|
+
} : {
|
|
55
|
+
toolInvocations,
|
|
56
|
+
changed: false
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Replace the result payload of a tool invocation with a placeholder, keeping
|
|
61
|
+
* the call's identity (tool name, arguments, terminal state). The Observer
|
|
62
|
+
* still records that the tool ran and what it was called with.
|
|
63
|
+
*/
|
|
64
|
+
function redactToolResult(part) {
|
|
65
|
+
const redacted = {
|
|
66
|
+
...part,
|
|
67
|
+
toolInvocation: {
|
|
68
|
+
...part.toolInvocation,
|
|
69
|
+
result: REDACTED_TOOL_RESULT
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
if (hasStoredModelOutput(part)) {
|
|
73
|
+
const providerMetadata = part.providerMetadata ?? {};
|
|
74
|
+
const mastra = providerMetadata.mastra;
|
|
75
|
+
return {
|
|
76
|
+
...redacted,
|
|
77
|
+
providerMetadata: {
|
|
78
|
+
...providerMetadata,
|
|
79
|
+
mastra: {
|
|
80
|
+
...mastra,
|
|
81
|
+
modelOutput: REDACTED_TOOL_RESULT
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
return redacted;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Build a `beforeObservation` hook that keeps Agent Skills results out of the
|
|
90
|
+
* Observer payload.
|
|
91
|
+
*
|
|
92
|
+
* The `skill` tool returns a skill's instructions verbatim as its result, and
|
|
93
|
+
* `skill_read` / `skill_search` return skill file contents, so without redaction
|
|
94
|
+
* the Observer re-observes the full skill text every time a skill is used.
|
|
95
|
+
* `skillResultRedactor()` replaces the result payload with a placeholder and
|
|
96
|
+
* leaves the tool call in place, so the Observer still records which skill was
|
|
97
|
+
* used without the skill text.
|
|
98
|
+
*
|
|
99
|
+
* ```typescript
|
|
100
|
+
* const memory = new Memory({
|
|
101
|
+
* options: {
|
|
102
|
+
* observationalMemory: {
|
|
103
|
+
* model: 'google/gemini-2.5-flash',
|
|
104
|
+
* hooks: { beforeObservation: skillResultRedactor() },
|
|
105
|
+
* },
|
|
106
|
+
* },
|
|
107
|
+
* });
|
|
108
|
+
* ```
|
|
109
|
+
*
|
|
110
|
+
* Because a hook is a function over the messages, this composes with your own
|
|
111
|
+
* transforms by chaining the outputs. Await each chained hook so an async one
|
|
112
|
+
* doesn't resolve to a promise that gets discarded:
|
|
113
|
+
*
|
|
114
|
+
* ```typescript
|
|
115
|
+
* const dropSkillResults = skillResultRedactor();
|
|
116
|
+
*
|
|
117
|
+
* hooks: {
|
|
118
|
+
* beforeObservation: async input => {
|
|
119
|
+
* const messages = (await dropSkillResults(input))?.messages ?? input.messages;
|
|
120
|
+
* return { messages: messages.filter(m => m.role !== 'signal') };
|
|
121
|
+
* },
|
|
122
|
+
* }
|
|
123
|
+
* ```
|
|
124
|
+
*/
|
|
125
|
+
function skillResultRedactor(options) {
|
|
126
|
+
const toolNames = new Set(options?.toolNames ?? SKILL_TOOL_NAMES);
|
|
127
|
+
return ({ messages }) => {
|
|
128
|
+
let changed = false;
|
|
129
|
+
const transformed = messages.map((message) => {
|
|
130
|
+
const parts = message.content?.parts;
|
|
131
|
+
let messageChanged = false;
|
|
132
|
+
const legacy = redactLegacyToolInvocations(message.content?.toolInvocations, toolNames);
|
|
133
|
+
if (legacy.changed) messageChanged = true;
|
|
134
|
+
let nextParts = parts;
|
|
135
|
+
if (Array.isArray(parts)) nextParts = parts.map((part) => {
|
|
136
|
+
if (!isRedactableToolResult(part, toolNames)) return part;
|
|
137
|
+
messageChanged = true;
|
|
138
|
+
return redactToolResult(part);
|
|
139
|
+
});
|
|
140
|
+
if (!messageChanged) return message;
|
|
141
|
+
changed = true;
|
|
142
|
+
return {
|
|
143
|
+
...message,
|
|
144
|
+
content: {
|
|
145
|
+
...message.content,
|
|
146
|
+
parts: nextParts,
|
|
147
|
+
toolInvocations: legacy.toolInvocations
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
});
|
|
151
|
+
return changed ? { messages: transformed } : void 0;
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
//#endregion
|
|
155
|
+
exports.SKILL_TOOL_NAMES = SKILL_TOOL_NAMES;
|
|
156
|
+
exports.skillResultRedactor = skillResultRedactor;
|
|
157
|
+
|
|
158
|
+
//# sourceMappingURL=hooks.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks.cjs","names":[],"sources":["../src/processors/observational-memory/hooks.ts"],"sourcesContent":["import type { MastraDBMessage } from '@mastra/core/agent';\n\nimport type { ObserveTransformHooks } from './types';\n\ntype MessagePart = MastraDBMessage['content']['parts'][number];\n\ntype ToolInvocationPart = Extract<MessagePart, { type: 'tool-invocation' }>;\n\ntype BeforeObservationHook = NonNullable<ObserveTransformHooks['beforeObservation']>;\n\ntype ToolInvocations = NonNullable<MastraDBMessage['content']['toolInvocations']>;\ntype StoredToolInvocation = ToolInvocations[number];\n\n/**\n * Tool ids of the built-in Agent Skills tools created by `createSkillTools()`\n * in `@mastra/core` (`packages/core/src/workspace/skills/tools.ts`).\n */\nexport const SKILL_TOOL_NAMES = ['skill', 'skill_search', 'skill_read'] as const;\n\n/**\n * A `beforeObservation` transform hook: it only rewrites messages. It receives\n * the messages about to be sent to the Observer and returns `{ messages }` to\n * replace them, or `undefined` to pass the payload through unchanged.\n */\nexport type ObserverMessageTransform = (\n ...args: Parameters<BeforeObservationHook>\n) => { messages: MastraDBMessage[] } | undefined;\n\nexport interface SkillResultRedactorOptions {\n /**\n * Tool names whose results are redacted. Defaults to\n * {@link SKILL_TOOL_NAMES}.\n */\n toolNames?: readonly string[];\n}\n\n/**\n * Written in place of a redacted tool result. The Observer still records the\n * call and its outcome; only the payload is replaced.\n */\nconst REDACTED_TOOL_RESULT = '[tool result omitted]';\n\nfunction isRedactableToolResult(part: MessagePart, toolNames: Set<string>): part is ToolInvocationPart {\n if (part?.type !== 'tool-invocation') return false;\n // Only `state: 'result'` parts render a `Tool Result <name>` body, so only\n // those carry the tool's output.\n if (part.toolInvocation.state !== 'result') return false;\n const toolName = part.toolInvocation.toolName;\n if (typeof toolName !== 'string' || !toolNames.has(toolName)) return false;\n // Nothing to redact when the tool returned no payload.\n return part.toolInvocation.result !== undefined || hasStoredModelOutput(part);\n}\n\nfunction hasStoredModelOutput(part: MessagePart): boolean {\n if (part?.type !== 'tool-invocation') return false;\n const mastra = part.providerMetadata?.mastra;\n return !!mastra && typeof mastra === 'object' && 'modelOutput' in mastra;\n}\n\n/**\n * Legacy messages carry tool calls in a second `toolInvocations` array, which\n * `AIV5Adapter` falls back to when `parts` holds no tool invocation\n * (`AIV5Adapter.ts:264`). Leave it alone and a redacted result can be\n * resurrected from that array downstream. Only rewritten when it actually\n * holds a matching result, so non-legacy messages keep their array by\n * reference.\n */\nfunction redactLegacyToolInvocations(\n toolInvocations: ToolInvocations | undefined,\n toolNames: Set<string>,\n): { toolInvocations: ToolInvocations | undefined; changed: boolean } {\n if (!Array.isArray(toolInvocations)) return { toolInvocations, changed: false };\n\n let changed = false;\n const next: ToolInvocations = toolInvocations.map(invocation => {\n if (\n invocation?.state !== 'result' ||\n typeof invocation.toolName !== 'string' ||\n !toolNames.has(invocation.toolName) ||\n invocation.result === undefined\n ) {\n return invocation;\n }\n changed = true;\n return { ...invocation, result: REDACTED_TOOL_RESULT } as StoredToolInvocation;\n });\n\n return changed ? { toolInvocations: next, changed: true } : { toolInvocations, changed: false };\n}\n\n/**\n * Replace the result payload of a tool invocation with a placeholder, keeping\n * the call's identity (tool name, arguments, terminal state). The Observer\n * still records that the tool ran and what it was called with.\n */\nfunction redactToolResult(part: ToolInvocationPart): ToolInvocationPart {\n const redacted: ToolInvocationPart = {\n ...part,\n toolInvocation: { ...part.toolInvocation, result: REDACTED_TOOL_RESULT },\n };\n\n // `resolveToolResultValue` prefers `providerMetadata.mastra.modelOutput` over\n // `toolInvocation.result`, so a stored model output has to be replaced too.\n // Copy rather than mutate: these message objects are shared with the stored\n // history, which keeps the full result.\n if (hasStoredModelOutput(part)) {\n const providerMetadata = part.providerMetadata ?? {};\n const mastra = providerMetadata.mastra as Record<string, unknown>;\n return {\n ...redacted,\n providerMetadata: { ...providerMetadata, mastra: { ...mastra, modelOutput: REDACTED_TOOL_RESULT } },\n };\n }\n\n return redacted;\n}\n\n/**\n * Build a `beforeObservation` hook that keeps Agent Skills results out of the\n * Observer payload.\n *\n * The `skill` tool returns a skill's instructions verbatim as its result, and\n * `skill_read` / `skill_search` return skill file contents, so without redaction\n * the Observer re-observes the full skill text every time a skill is used.\n * `skillResultRedactor()` replaces the result payload with a placeholder and\n * leaves the tool call in place, so the Observer still records which skill was\n * used without the skill text.\n *\n * ```typescript\n * const memory = new Memory({\n * options: {\n * observationalMemory: {\n * model: 'google/gemini-2.5-flash',\n * hooks: { beforeObservation: skillResultRedactor() },\n * },\n * },\n * });\n * ```\n *\n * Because a hook is a function over the messages, this composes with your own\n * transforms by chaining the outputs. Await each chained hook so an async one\n * doesn't resolve to a promise that gets discarded:\n *\n * ```typescript\n * const dropSkillResults = skillResultRedactor();\n *\n * hooks: {\n * beforeObservation: async input => {\n * const messages = (await dropSkillResults(input))?.messages ?? input.messages;\n * return { messages: messages.filter(m => m.role !== 'signal') };\n * },\n * }\n * ```\n */\nexport function skillResultRedactor(options?: SkillResultRedactorOptions): ObserverMessageTransform {\n const toolNames = new Set<string>(options?.toolNames ?? SKILL_TOOL_NAMES);\n\n return ({ messages }) => {\n let changed = false;\n\n const transformed = messages.map(message => {\n const parts = message.content?.parts;\n\n let messageChanged = false;\n\n // Legacy-only messages carry no `parts` at all, so this runs before the\n // parts check rather than being skipped by it.\n const legacy = redactLegacyToolInvocations(message.content?.toolInvocations, toolNames);\n if (legacy.changed) messageChanged = true;\n\n let nextParts = parts;\n if (Array.isArray(parts)) {\n nextParts = parts.map(part => {\n if (!isRedactableToolResult(part, toolNames)) return part;\n messageChanged = true;\n return redactToolResult(part);\n });\n }\n\n if (!messageChanged) return message;\n\n changed = true;\n return {\n ...message,\n content: { ...message.content, parts: nextParts, toolInvocations: legacy.toolInvocations },\n };\n });\n\n // `undefined` means \"pass through unchanged\", so leave untouched payloads alone.\n return changed ? { messages: transformed } : undefined;\n };\n}\n"],"mappings":";;;;;;AAiBA,MAAa,mBAAmB;CAAC;CAAS;CAAgB;AAAY;;;;;AAuBtE,MAAM,uBAAuB;AAE7B,SAAS,uBAAuB,MAAmB,WAAoD;CACrG,IAAI,MAAM,SAAS,mBAAmB,OAAO;CAG7C,IAAI,KAAK,eAAe,UAAU,UAAU,OAAO;CACnD,MAAM,WAAW,KAAK,eAAe;CACrC,IAAI,OAAO,aAAa,YAAY,CAAC,UAAU,IAAI,QAAQ,GAAG,OAAO;CAErE,OAAO,KAAK,eAAe,WAAW,KAAA,KAAa,qBAAqB,IAAI;AAC9E;AAEA,SAAS,qBAAqB,MAA4B;CACxD,IAAI,MAAM,SAAS,mBAAmB,OAAO;CAC7C,MAAM,SAAS,KAAK,kBAAkB;CACtC,OAAO,CAAC,CAAC,UAAU,OAAO,WAAW,YAAY,iBAAiB;AACpE;;;;;;;;;AAUA,SAAS,4BACP,iBACA,WACoE;CACpE,IAAI,CAAC,MAAM,QAAQ,eAAe,GAAG,OAAO;EAAE;EAAiB,SAAS;CAAM;CAE9E,IAAI,UAAU;CACd,MAAM,OAAwB,gBAAgB,KAAI,eAAc;EAC9D,IACE,YAAY,UAAU,YACtB,OAAO,WAAW,aAAa,YAC/B,CAAC,UAAU,IAAI,WAAW,QAAQ,KAClC,WAAW,WAAW,KAAA,GAEtB,OAAO;EAET,UAAU;EACV,OAAO;GAAE,GAAG;GAAY,QAAQ;EAAqB;CACvD,CAAC;CAED,OAAO,UAAU;EAAE,iBAAiB;EAAM,SAAS;CAAK,IAAI;EAAE;EAAiB,SAAS;CAAM;AAChG;;;;;;AAOA,SAAS,iBAAiB,MAA8C;CACtE,MAAM,WAA+B;EACnC,GAAG;EACH,gBAAgB;GAAE,GAAG,KAAK;GAAgB,QAAQ;EAAqB;CACzE;CAMA,IAAI,qBAAqB,IAAI,GAAG;EAC9B,MAAM,mBAAmB,KAAK,oBAAoB,CAAC;EACnD,MAAM,SAAS,iBAAiB;EAChC,OAAO;GACL,GAAG;GACH,kBAAkB;IAAE,GAAG;IAAkB,QAAQ;KAAE,GAAG;KAAQ,aAAa;IAAqB;GAAE;EACpG;CACF;CAEA,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuCA,SAAgB,oBAAoB,SAAgE;CAClG,MAAM,YAAY,IAAI,IAAY,SAAS,aAAa,gBAAgB;CAExE,QAAQ,EAAE,eAAe;EACvB,IAAI,UAAU;EAEd,MAAM,cAAc,SAAS,KAAI,YAAW;GAC1C,MAAM,QAAQ,QAAQ,SAAS;GAE/B,IAAI,iBAAiB;GAIrB,MAAM,SAAS,4BAA4B,QAAQ,SAAS,iBAAiB,SAAS;GACtF,IAAI,OAAO,SAAS,iBAAiB;GAErC,IAAI,YAAY;GAChB,IAAI,MAAM,QAAQ,KAAK,GACrB,YAAY,MAAM,KAAI,SAAQ;IAC5B,IAAI,CAAC,uBAAuB,MAAM,SAAS,GAAG,OAAO;IACrD,iBAAiB;IACjB,OAAO,iBAAiB,IAAI;GAC9B,CAAC;GAGH,IAAI,CAAC,gBAAgB,OAAO;GAE5B,UAAU;GACV,OAAO;IACL,GAAG;IACH,SAAS;KAAE,GAAG,QAAQ;KAAS,OAAO;KAAW,iBAAiB,OAAO;IAAgB;GAC3F;EACF,CAAC;EAGD,OAAO,UAAU,EAAE,UAAU,YAAY,IAAI,KAAA;CAC/C;AACF"}
|
package/dist/hooks.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prebuilt Observational Memory transform hooks.
|
|
3
|
+
*
|
|
4
|
+
* These are ready-made `beforeObservation` hooks you can pass to
|
|
5
|
+
* `observationalMemory.hooks`, so common redaction needs don't require writing
|
|
6
|
+
* the hook yourself.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* import { Memory } from '@mastra/memory';
|
|
11
|
+
* import { skillResultRedactor } from '@mastra/memory/hooks';
|
|
12
|
+
*
|
|
13
|
+
* const memory = new Memory({
|
|
14
|
+
* options: {
|
|
15
|
+
* observationalMemory: {
|
|
16
|
+
* model: 'google/gemini-2.5-flash',
|
|
17
|
+
* hooks: { beforeObservation: skillResultRedactor() },
|
|
18
|
+
* },
|
|
19
|
+
* },
|
|
20
|
+
* });
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export { skillResultRedactor, SKILL_TOOL_NAMES } from './processors/observational-memory/hooks.js';
|
|
24
|
+
export type { ObserverMessageTransform, SkillResultRedactorOptions } from './processors/observational-memory/hooks.js';
|
|
25
|
+
//# sourceMappingURL=hooks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,yCAAyC,CAAC;AAChG,YAAY,EAAE,wBAAwB,EAAE,0BAA0B,EAAE,MAAM,yCAAyC,CAAC"}
|
package/dist/hooks.js
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
//#region src/processors/observational-memory/hooks.ts
|
|
2
|
+
/**
|
|
3
|
+
* Tool ids of the built-in Agent Skills tools created by `createSkillTools()`
|
|
4
|
+
* in `@mastra/core` (`packages/core/src/workspace/skills/tools.ts`).
|
|
5
|
+
*/
|
|
6
|
+
const SKILL_TOOL_NAMES = [
|
|
7
|
+
"skill",
|
|
8
|
+
"skill_search",
|
|
9
|
+
"skill_read"
|
|
10
|
+
];
|
|
11
|
+
/**
|
|
12
|
+
* Written in place of a redacted tool result. The Observer still records the
|
|
13
|
+
* call and its outcome; only the payload is replaced.
|
|
14
|
+
*/
|
|
15
|
+
const REDACTED_TOOL_RESULT = "[tool result omitted]";
|
|
16
|
+
function isRedactableToolResult(part, toolNames) {
|
|
17
|
+
if (part?.type !== "tool-invocation") return false;
|
|
18
|
+
if (part.toolInvocation.state !== "result") return false;
|
|
19
|
+
const toolName = part.toolInvocation.toolName;
|
|
20
|
+
if (typeof toolName !== "string" || !toolNames.has(toolName)) return false;
|
|
21
|
+
return part.toolInvocation.result !== void 0 || hasStoredModelOutput(part);
|
|
22
|
+
}
|
|
23
|
+
function hasStoredModelOutput(part) {
|
|
24
|
+
if (part?.type !== "tool-invocation") return false;
|
|
25
|
+
const mastra = part.providerMetadata?.mastra;
|
|
26
|
+
return !!mastra && typeof mastra === "object" && "modelOutput" in mastra;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Legacy messages carry tool calls in a second `toolInvocations` array, which
|
|
30
|
+
* `AIV5Adapter` falls back to when `parts` holds no tool invocation
|
|
31
|
+
* (`AIV5Adapter.ts:264`). Leave it alone and a redacted result can be
|
|
32
|
+
* resurrected from that array downstream. Only rewritten when it actually
|
|
33
|
+
* holds a matching result, so non-legacy messages keep their array by
|
|
34
|
+
* reference.
|
|
35
|
+
*/
|
|
36
|
+
function redactLegacyToolInvocations(toolInvocations, toolNames) {
|
|
37
|
+
if (!Array.isArray(toolInvocations)) return {
|
|
38
|
+
toolInvocations,
|
|
39
|
+
changed: false
|
|
40
|
+
};
|
|
41
|
+
let changed = false;
|
|
42
|
+
const next = toolInvocations.map((invocation) => {
|
|
43
|
+
if (invocation?.state !== "result" || typeof invocation.toolName !== "string" || !toolNames.has(invocation.toolName) || invocation.result === void 0) return invocation;
|
|
44
|
+
changed = true;
|
|
45
|
+
return {
|
|
46
|
+
...invocation,
|
|
47
|
+
result: REDACTED_TOOL_RESULT
|
|
48
|
+
};
|
|
49
|
+
});
|
|
50
|
+
return changed ? {
|
|
51
|
+
toolInvocations: next,
|
|
52
|
+
changed: true
|
|
53
|
+
} : {
|
|
54
|
+
toolInvocations,
|
|
55
|
+
changed: false
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Replace the result payload of a tool invocation with a placeholder, keeping
|
|
60
|
+
* the call's identity (tool name, arguments, terminal state). The Observer
|
|
61
|
+
* still records that the tool ran and what it was called with.
|
|
62
|
+
*/
|
|
63
|
+
function redactToolResult(part) {
|
|
64
|
+
const redacted = {
|
|
65
|
+
...part,
|
|
66
|
+
toolInvocation: {
|
|
67
|
+
...part.toolInvocation,
|
|
68
|
+
result: REDACTED_TOOL_RESULT
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
if (hasStoredModelOutput(part)) {
|
|
72
|
+
const providerMetadata = part.providerMetadata ?? {};
|
|
73
|
+
const mastra = providerMetadata.mastra;
|
|
74
|
+
return {
|
|
75
|
+
...redacted,
|
|
76
|
+
providerMetadata: {
|
|
77
|
+
...providerMetadata,
|
|
78
|
+
mastra: {
|
|
79
|
+
...mastra,
|
|
80
|
+
modelOutput: REDACTED_TOOL_RESULT
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
return redacted;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Build a `beforeObservation` hook that keeps Agent Skills results out of the
|
|
89
|
+
* Observer payload.
|
|
90
|
+
*
|
|
91
|
+
* The `skill` tool returns a skill's instructions verbatim as its result, and
|
|
92
|
+
* `skill_read` / `skill_search` return skill file contents, so without redaction
|
|
93
|
+
* the Observer re-observes the full skill text every time a skill is used.
|
|
94
|
+
* `skillResultRedactor()` replaces the result payload with a placeholder and
|
|
95
|
+
* leaves the tool call in place, so the Observer still records which skill was
|
|
96
|
+
* used without the skill text.
|
|
97
|
+
*
|
|
98
|
+
* ```typescript
|
|
99
|
+
* const memory = new Memory({
|
|
100
|
+
* options: {
|
|
101
|
+
* observationalMemory: {
|
|
102
|
+
* model: 'google/gemini-2.5-flash',
|
|
103
|
+
* hooks: { beforeObservation: skillResultRedactor() },
|
|
104
|
+
* },
|
|
105
|
+
* },
|
|
106
|
+
* });
|
|
107
|
+
* ```
|
|
108
|
+
*
|
|
109
|
+
* Because a hook is a function over the messages, this composes with your own
|
|
110
|
+
* transforms by chaining the outputs. Await each chained hook so an async one
|
|
111
|
+
* doesn't resolve to a promise that gets discarded:
|
|
112
|
+
*
|
|
113
|
+
* ```typescript
|
|
114
|
+
* const dropSkillResults = skillResultRedactor();
|
|
115
|
+
*
|
|
116
|
+
* hooks: {
|
|
117
|
+
* beforeObservation: async input => {
|
|
118
|
+
* const messages = (await dropSkillResults(input))?.messages ?? input.messages;
|
|
119
|
+
* return { messages: messages.filter(m => m.role !== 'signal') };
|
|
120
|
+
* },
|
|
121
|
+
* }
|
|
122
|
+
* ```
|
|
123
|
+
*/
|
|
124
|
+
function skillResultRedactor(options) {
|
|
125
|
+
const toolNames = new Set(options?.toolNames ?? SKILL_TOOL_NAMES);
|
|
126
|
+
return ({ messages }) => {
|
|
127
|
+
let changed = false;
|
|
128
|
+
const transformed = messages.map((message) => {
|
|
129
|
+
const parts = message.content?.parts;
|
|
130
|
+
let messageChanged = false;
|
|
131
|
+
const legacy = redactLegacyToolInvocations(message.content?.toolInvocations, toolNames);
|
|
132
|
+
if (legacy.changed) messageChanged = true;
|
|
133
|
+
let nextParts = parts;
|
|
134
|
+
if (Array.isArray(parts)) nextParts = parts.map((part) => {
|
|
135
|
+
if (!isRedactableToolResult(part, toolNames)) return part;
|
|
136
|
+
messageChanged = true;
|
|
137
|
+
return redactToolResult(part);
|
|
138
|
+
});
|
|
139
|
+
if (!messageChanged) return message;
|
|
140
|
+
changed = true;
|
|
141
|
+
return {
|
|
142
|
+
...message,
|
|
143
|
+
content: {
|
|
144
|
+
...message.content,
|
|
145
|
+
parts: nextParts,
|
|
146
|
+
toolInvocations: legacy.toolInvocations
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
});
|
|
150
|
+
return changed ? { messages: transformed } : void 0;
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
//#endregion
|
|
154
|
+
export { SKILL_TOOL_NAMES, skillResultRedactor };
|
|
155
|
+
|
|
156
|
+
//# sourceMappingURL=hooks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks.js","names":[],"sources":["../src/processors/observational-memory/hooks.ts"],"sourcesContent":["import type { MastraDBMessage } from '@mastra/core/agent';\n\nimport type { ObserveTransformHooks } from './types';\n\ntype MessagePart = MastraDBMessage['content']['parts'][number];\n\ntype ToolInvocationPart = Extract<MessagePart, { type: 'tool-invocation' }>;\n\ntype BeforeObservationHook = NonNullable<ObserveTransformHooks['beforeObservation']>;\n\ntype ToolInvocations = NonNullable<MastraDBMessage['content']['toolInvocations']>;\ntype StoredToolInvocation = ToolInvocations[number];\n\n/**\n * Tool ids of the built-in Agent Skills tools created by `createSkillTools()`\n * in `@mastra/core` (`packages/core/src/workspace/skills/tools.ts`).\n */\nexport const SKILL_TOOL_NAMES = ['skill', 'skill_search', 'skill_read'] as const;\n\n/**\n * A `beforeObservation` transform hook: it only rewrites messages. It receives\n * the messages about to be sent to the Observer and returns `{ messages }` to\n * replace them, or `undefined` to pass the payload through unchanged.\n */\nexport type ObserverMessageTransform = (\n ...args: Parameters<BeforeObservationHook>\n) => { messages: MastraDBMessage[] } | undefined;\n\nexport interface SkillResultRedactorOptions {\n /**\n * Tool names whose results are redacted. Defaults to\n * {@link SKILL_TOOL_NAMES}.\n */\n toolNames?: readonly string[];\n}\n\n/**\n * Written in place of a redacted tool result. The Observer still records the\n * call and its outcome; only the payload is replaced.\n */\nconst REDACTED_TOOL_RESULT = '[tool result omitted]';\n\nfunction isRedactableToolResult(part: MessagePart, toolNames: Set<string>): part is ToolInvocationPart {\n if (part?.type !== 'tool-invocation') return false;\n // Only `state: 'result'` parts render a `Tool Result <name>` body, so only\n // those carry the tool's output.\n if (part.toolInvocation.state !== 'result') return false;\n const toolName = part.toolInvocation.toolName;\n if (typeof toolName !== 'string' || !toolNames.has(toolName)) return false;\n // Nothing to redact when the tool returned no payload.\n return part.toolInvocation.result !== undefined || hasStoredModelOutput(part);\n}\n\nfunction hasStoredModelOutput(part: MessagePart): boolean {\n if (part?.type !== 'tool-invocation') return false;\n const mastra = part.providerMetadata?.mastra;\n return !!mastra && typeof mastra === 'object' && 'modelOutput' in mastra;\n}\n\n/**\n * Legacy messages carry tool calls in a second `toolInvocations` array, which\n * `AIV5Adapter` falls back to when `parts` holds no tool invocation\n * (`AIV5Adapter.ts:264`). Leave it alone and a redacted result can be\n * resurrected from that array downstream. Only rewritten when it actually\n * holds a matching result, so non-legacy messages keep their array by\n * reference.\n */\nfunction redactLegacyToolInvocations(\n toolInvocations: ToolInvocations | undefined,\n toolNames: Set<string>,\n): { toolInvocations: ToolInvocations | undefined; changed: boolean } {\n if (!Array.isArray(toolInvocations)) return { toolInvocations, changed: false };\n\n let changed = false;\n const next: ToolInvocations = toolInvocations.map(invocation => {\n if (\n invocation?.state !== 'result' ||\n typeof invocation.toolName !== 'string' ||\n !toolNames.has(invocation.toolName) ||\n invocation.result === undefined\n ) {\n return invocation;\n }\n changed = true;\n return { ...invocation, result: REDACTED_TOOL_RESULT } as StoredToolInvocation;\n });\n\n return changed ? { toolInvocations: next, changed: true } : { toolInvocations, changed: false };\n}\n\n/**\n * Replace the result payload of a tool invocation with a placeholder, keeping\n * the call's identity (tool name, arguments, terminal state). The Observer\n * still records that the tool ran and what it was called with.\n */\nfunction redactToolResult(part: ToolInvocationPart): ToolInvocationPart {\n const redacted: ToolInvocationPart = {\n ...part,\n toolInvocation: { ...part.toolInvocation, result: REDACTED_TOOL_RESULT },\n };\n\n // `resolveToolResultValue` prefers `providerMetadata.mastra.modelOutput` over\n // `toolInvocation.result`, so a stored model output has to be replaced too.\n // Copy rather than mutate: these message objects are shared with the stored\n // history, which keeps the full result.\n if (hasStoredModelOutput(part)) {\n const providerMetadata = part.providerMetadata ?? {};\n const mastra = providerMetadata.mastra as Record<string, unknown>;\n return {\n ...redacted,\n providerMetadata: { ...providerMetadata, mastra: { ...mastra, modelOutput: REDACTED_TOOL_RESULT } },\n };\n }\n\n return redacted;\n}\n\n/**\n * Build a `beforeObservation` hook that keeps Agent Skills results out of the\n * Observer payload.\n *\n * The `skill` tool returns a skill's instructions verbatim as its result, and\n * `skill_read` / `skill_search` return skill file contents, so without redaction\n * the Observer re-observes the full skill text every time a skill is used.\n * `skillResultRedactor()` replaces the result payload with a placeholder and\n * leaves the tool call in place, so the Observer still records which skill was\n * used without the skill text.\n *\n * ```typescript\n * const memory = new Memory({\n * options: {\n * observationalMemory: {\n * model: 'google/gemini-2.5-flash',\n * hooks: { beforeObservation: skillResultRedactor() },\n * },\n * },\n * });\n * ```\n *\n * Because a hook is a function over the messages, this composes with your own\n * transforms by chaining the outputs. Await each chained hook so an async one\n * doesn't resolve to a promise that gets discarded:\n *\n * ```typescript\n * const dropSkillResults = skillResultRedactor();\n *\n * hooks: {\n * beforeObservation: async input => {\n * const messages = (await dropSkillResults(input))?.messages ?? input.messages;\n * return { messages: messages.filter(m => m.role !== 'signal') };\n * },\n * }\n * ```\n */\nexport function skillResultRedactor(options?: SkillResultRedactorOptions): ObserverMessageTransform {\n const toolNames = new Set<string>(options?.toolNames ?? SKILL_TOOL_NAMES);\n\n return ({ messages }) => {\n let changed = false;\n\n const transformed = messages.map(message => {\n const parts = message.content?.parts;\n\n let messageChanged = false;\n\n // Legacy-only messages carry no `parts` at all, so this runs before the\n // parts check rather than being skipped by it.\n const legacy = redactLegacyToolInvocations(message.content?.toolInvocations, toolNames);\n if (legacy.changed) messageChanged = true;\n\n let nextParts = parts;\n if (Array.isArray(parts)) {\n nextParts = parts.map(part => {\n if (!isRedactableToolResult(part, toolNames)) return part;\n messageChanged = true;\n return redactToolResult(part);\n });\n }\n\n if (!messageChanged) return message;\n\n changed = true;\n return {\n ...message,\n content: { ...message.content, parts: nextParts, toolInvocations: legacy.toolInvocations },\n };\n });\n\n // `undefined` means \"pass through unchanged\", so leave untouched payloads alone.\n return changed ? { messages: transformed } : undefined;\n };\n}\n"],"mappings":";;;;;AAiBA,MAAa,mBAAmB;CAAC;CAAS;CAAgB;AAAY;;;;;AAuBtE,MAAM,uBAAuB;AAE7B,SAAS,uBAAuB,MAAmB,WAAoD;CACrG,IAAI,MAAM,SAAS,mBAAmB,OAAO;CAG7C,IAAI,KAAK,eAAe,UAAU,UAAU,OAAO;CACnD,MAAM,WAAW,KAAK,eAAe;CACrC,IAAI,OAAO,aAAa,YAAY,CAAC,UAAU,IAAI,QAAQ,GAAG,OAAO;CAErE,OAAO,KAAK,eAAe,WAAW,KAAA,KAAa,qBAAqB,IAAI;AAC9E;AAEA,SAAS,qBAAqB,MAA4B;CACxD,IAAI,MAAM,SAAS,mBAAmB,OAAO;CAC7C,MAAM,SAAS,KAAK,kBAAkB;CACtC,OAAO,CAAC,CAAC,UAAU,OAAO,WAAW,YAAY,iBAAiB;AACpE;;;;;;;;;AAUA,SAAS,4BACP,iBACA,WACoE;CACpE,IAAI,CAAC,MAAM,QAAQ,eAAe,GAAG,OAAO;EAAE;EAAiB,SAAS;CAAM;CAE9E,IAAI,UAAU;CACd,MAAM,OAAwB,gBAAgB,KAAI,eAAc;EAC9D,IACE,YAAY,UAAU,YACtB,OAAO,WAAW,aAAa,YAC/B,CAAC,UAAU,IAAI,WAAW,QAAQ,KAClC,WAAW,WAAW,KAAA,GAEtB,OAAO;EAET,UAAU;EACV,OAAO;GAAE,GAAG;GAAY,QAAQ;EAAqB;CACvD,CAAC;CAED,OAAO,UAAU;EAAE,iBAAiB;EAAM,SAAS;CAAK,IAAI;EAAE;EAAiB,SAAS;CAAM;AAChG;;;;;;AAOA,SAAS,iBAAiB,MAA8C;CACtE,MAAM,WAA+B;EACnC,GAAG;EACH,gBAAgB;GAAE,GAAG,KAAK;GAAgB,QAAQ;EAAqB;CACzE;CAMA,IAAI,qBAAqB,IAAI,GAAG;EAC9B,MAAM,mBAAmB,KAAK,oBAAoB,CAAC;EACnD,MAAM,SAAS,iBAAiB;EAChC,OAAO;GACL,GAAG;GACH,kBAAkB;IAAE,GAAG;IAAkB,QAAQ;KAAE,GAAG;KAAQ,aAAa;IAAqB;GAAE;EACpG;CACF;CAEA,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuCA,SAAgB,oBAAoB,SAAgE;CAClG,MAAM,YAAY,IAAI,IAAY,SAAS,aAAa,gBAAgB;CAExE,QAAQ,EAAE,eAAe;EACvB,IAAI,UAAU;EAEd,MAAM,cAAc,SAAS,KAAI,YAAW;GAC1C,MAAM,QAAQ,QAAQ,SAAS;GAE/B,IAAI,iBAAiB;GAIrB,MAAM,SAAS,4BAA4B,QAAQ,SAAS,iBAAiB,SAAS;GACtF,IAAI,OAAO,SAAS,iBAAiB;GAErC,IAAI,YAAY;GAChB,IAAI,MAAM,QAAQ,KAAK,GACrB,YAAY,MAAM,KAAI,SAAQ;IAC5B,IAAI,CAAC,uBAAuB,MAAM,SAAS,GAAG,OAAO;IACrD,iBAAiB;IACjB,OAAO,iBAAiB,IAAI;GAC9B,CAAC;GAGH,IAAI,CAAC,gBAAgB,OAAO;GAE5B,UAAU;GACV,OAAO;IACL,GAAG;IACH,SAAS;KAAE,GAAG,QAAQ;KAAS,OAAO;KAAW,iBAAiB,OAAO;IAAgB;GAC3F;EACF,CAAC;EAGD,OAAO,UAAU,EAAE,UAAU,YAAY,IAAI,KAAA;CAC/C;AACF"}
|
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_src = require("./src-
|
|
2
|
+
const require_src = require("./src-C4niho1P.cjs");
|
|
3
3
|
let _mastra_core_processors = require("@mastra/core/processors");
|
|
4
4
|
exports.Extractor = require_src.Extractor;
|
|
5
5
|
exports.KnowledgeSemanticIndexCoordinator = require_src.KnowledgeSemanticIndexCoordinator;
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { C as SUMMARIZE_THREAD_DEFAULTS, G as ModelByInputTokens, H as KnowledgeSemanticIndexCoordinator, S as WorkingMemoryExtractor, U as StaleKnowledgeSemanticIndexError, V as Subconscious, X as Extractor, a as extractWorkingMemoryContent, b as WorkingMemoryStateProcessor, c as getObservationsAsOf, i as WorkingMemory, n as MessageHistory, o as extractWorkingMemoryTags, r as SemanticRecall, s as removeWorkingMemoryTags, t as Memory, v as WORKING_MEMORY_STATE_ID, w as summarizeConversation, x as deepMergeWorkingMemory, y as WORKING_MEMORY_STATE_PROCESSOR_ID } from "./src-
|
|
1
|
+
import { C as SUMMARIZE_THREAD_DEFAULTS, G as ModelByInputTokens, H as KnowledgeSemanticIndexCoordinator, S as WorkingMemoryExtractor, U as StaleKnowledgeSemanticIndexError, V as Subconscious, X as Extractor, a as extractWorkingMemoryContent, b as WorkingMemoryStateProcessor, c as getObservationsAsOf, i as WorkingMemory, n as MessageHistory, o as extractWorkingMemoryTags, r as SemanticRecall, s as removeWorkingMemoryTags, t as Memory, v as WORKING_MEMORY_STATE_ID, w as summarizeConversation, x as deepMergeWorkingMemory, y as WORKING_MEMORY_STATE_PROCESSOR_ID } from "./src-DquR-jTR.js";
|
|
2
2
|
export { Extractor, KnowledgeSemanticIndexCoordinator, Memory, MessageHistory, ModelByInputTokens, SUMMARIZE_THREAD_DEFAULTS, SemanticRecall, StaleKnowledgeSemanticIndexError, Subconscious, WORKING_MEMORY_STATE_ID, WORKING_MEMORY_STATE_PROCESSOR_ID, WorkingMemory, WorkingMemoryExtractor, WorkingMemoryStateProcessor, deepMergeWorkingMemory, extractWorkingMemoryContent, extractWorkingMemoryTags, getObservationsAsOf, removeWorkingMemoryTags, summarizeConversation };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_src = require("../src-
|
|
2
|
+
const require_src = require("../src-C4niho1P.cjs");
|
|
3
3
|
exports.Extractor = require_src.Extractor;
|
|
4
4
|
exports.KnowledgeSemanticIndexCoordinator = require_src.KnowledgeSemanticIndexCoordinator;
|
|
5
5
|
exports.ModelByInputTokens = require_src.ModelByInputTokens;
|
package/dist/processors/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { A as formatMessagesForObserver, B as OBSERVATION_CONTINUATION_HINT, D as buildObserverPrompt, E as OBSERVER_SYSTEM_PROMPT, F as parseAnchorId, G as ModelByInputTokens, H as KnowledgeSemanticIndexCoordinator, I as stripEphemeralAnchorIds, J as publishSubconsciousActivity, K as SUBCONSCIOUS_ACTIVITY_STATE_ID, L as OBSERVATIONAL_MEMORY_DEFAULTS, M as optimizeObservationsForContext, N as parseObserverOutput, O as buildObserverSystemPrompt, P as injectAnchorIds, R as OBSERVATION_CONTEXT_INSTRUCTIONS, S as WorkingMemoryExtractor, T as TokenCounter, U as StaleKnowledgeSemanticIndexError, V as Subconscious, W as SubconsciousRemindExtractor, X as Extractor, Y as renderSubconsciousActivity, _ as wrapInObservationGroup, c as getObservationsAsOf, d as combineObservationGroupRanges, f as deriveObservationGroupProvenance, g as stripObservationGroups, h as renderObservationGroupsForReflection, j as hasCurrentTaskSection, k as extractCurrentTask, l as ObservationalMemoryProcessor, m as reconcileObservationGroupsFromReflection, p as parseObservationGroups, q as buildSubconsciousActivitySnapshot, u as ObservationalMemory, w as summarizeConversation, z as OBSERVATION_CONTEXT_PROMPT } from "../src-
|
|
1
|
+
import { A as formatMessagesForObserver, B as OBSERVATION_CONTINUATION_HINT, D as buildObserverPrompt, E as OBSERVER_SYSTEM_PROMPT, F as parseAnchorId, G as ModelByInputTokens, H as KnowledgeSemanticIndexCoordinator, I as stripEphemeralAnchorIds, J as publishSubconsciousActivity, K as SUBCONSCIOUS_ACTIVITY_STATE_ID, L as OBSERVATIONAL_MEMORY_DEFAULTS, M as optimizeObservationsForContext, N as parseObserverOutput, O as buildObserverSystemPrompt, P as injectAnchorIds, R as OBSERVATION_CONTEXT_INSTRUCTIONS, S as WorkingMemoryExtractor, T as TokenCounter, U as StaleKnowledgeSemanticIndexError, V as Subconscious, W as SubconsciousRemindExtractor, X as Extractor, Y as renderSubconsciousActivity, _ as wrapInObservationGroup, c as getObservationsAsOf, d as combineObservationGroupRanges, f as deriveObservationGroupProvenance, g as stripObservationGroups, h as renderObservationGroupsForReflection, j as hasCurrentTaskSection, k as extractCurrentTask, l as ObservationalMemoryProcessor, m as reconcileObservationGroupsFromReflection, p as parseObservationGroups, q as buildSubconsciousActivitySnapshot, u as ObservationalMemory, w as summarizeConversation, z as OBSERVATION_CONTEXT_PROMPT } from "../src-DquR-jTR.js";
|
|
2
2
|
export { Extractor, KnowledgeSemanticIndexCoordinator, ModelByInputTokens, OBSERVATIONAL_MEMORY_DEFAULTS, OBSERVATION_CONTEXT_INSTRUCTIONS, OBSERVATION_CONTEXT_PROMPT, OBSERVATION_CONTINUATION_HINT, OBSERVER_SYSTEM_PROMPT, ObservationalMemory, ObservationalMemoryProcessor, SUBCONSCIOUS_ACTIVITY_STATE_ID, StaleKnowledgeSemanticIndexError, Subconscious, SubconsciousRemindExtractor, TokenCounter, WorkingMemoryExtractor, buildObserverPrompt, buildObserverSystemPrompt, buildSubconsciousActivitySnapshot, combineObservationGroupRanges, deriveObservationGroupProvenance, extractCurrentTask, formatMessagesForObserver, getObservationsAsOf, hasCurrentTaskSection, injectAnchorIds, optimizeObservationsForContext, parseAnchorId, parseObservationGroups, parseObserverOutput, publishSubconsciousActivity, reconcileObservationGroupsFromReflection, renderObservationGroupsForReflection, renderSubconsciousActivity, stripEphemeralAnchorIds, stripObservationGroups, summarizeConversation, wrapInObservationGroup };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { MastraDBMessage } from '@mastra/core/agent';
|
|
2
|
+
import type { ObserveTransformHooks } from './types.js';
|
|
3
|
+
type BeforeObservationHook = NonNullable<ObserveTransformHooks['beforeObservation']>;
|
|
4
|
+
/**
|
|
5
|
+
* Tool ids of the built-in Agent Skills tools created by `createSkillTools()`
|
|
6
|
+
* in `@mastra/core` (`packages/core/src/workspace/skills/tools.ts`).
|
|
7
|
+
*/
|
|
8
|
+
export declare const SKILL_TOOL_NAMES: readonly ["skill", "skill_search", "skill_read"];
|
|
9
|
+
/**
|
|
10
|
+
* A `beforeObservation` transform hook: it only rewrites messages. It receives
|
|
11
|
+
* the messages about to be sent to the Observer and returns `{ messages }` to
|
|
12
|
+
* replace them, or `undefined` to pass the payload through unchanged.
|
|
13
|
+
*/
|
|
14
|
+
export type ObserverMessageTransform = (...args: Parameters<BeforeObservationHook>) => {
|
|
15
|
+
messages: MastraDBMessage[];
|
|
16
|
+
} | undefined;
|
|
17
|
+
export interface SkillResultRedactorOptions {
|
|
18
|
+
/**
|
|
19
|
+
* Tool names whose results are redacted. Defaults to
|
|
20
|
+
* {@link SKILL_TOOL_NAMES}.
|
|
21
|
+
*/
|
|
22
|
+
toolNames?: readonly string[];
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Build a `beforeObservation` hook that keeps Agent Skills results out of the
|
|
26
|
+
* Observer payload.
|
|
27
|
+
*
|
|
28
|
+
* The `skill` tool returns a skill's instructions verbatim as its result, and
|
|
29
|
+
* `skill_read` / `skill_search` return skill file contents, so without redaction
|
|
30
|
+
* the Observer re-observes the full skill text every time a skill is used.
|
|
31
|
+
* `skillResultRedactor()` replaces the result payload with a placeholder and
|
|
32
|
+
* leaves the tool call in place, so the Observer still records which skill was
|
|
33
|
+
* used without the skill text.
|
|
34
|
+
*
|
|
35
|
+
* ```typescript
|
|
36
|
+
* const memory = new Memory({
|
|
37
|
+
* options: {
|
|
38
|
+
* observationalMemory: {
|
|
39
|
+
* model: 'google/gemini-2.5-flash',
|
|
40
|
+
* hooks: { beforeObservation: skillResultRedactor() },
|
|
41
|
+
* },
|
|
42
|
+
* },
|
|
43
|
+
* });
|
|
44
|
+
* ```
|
|
45
|
+
*
|
|
46
|
+
* Because a hook is a function over the messages, this composes with your own
|
|
47
|
+
* transforms by chaining the outputs. Await each chained hook so an async one
|
|
48
|
+
* doesn't resolve to a promise that gets discarded:
|
|
49
|
+
*
|
|
50
|
+
* ```typescript
|
|
51
|
+
* const dropSkillResults = skillResultRedactor();
|
|
52
|
+
*
|
|
53
|
+
* hooks: {
|
|
54
|
+
* beforeObservation: async input => {
|
|
55
|
+
* const messages = (await dropSkillResults(input))?.messages ?? input.messages;
|
|
56
|
+
* return { messages: messages.filter(m => m.role !== 'signal') };
|
|
57
|
+
* },
|
|
58
|
+
* }
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
export declare function skillResultRedactor(options?: SkillResultRedactorOptions): ObserverMessageTransform;
|
|
62
|
+
export {};
|
|
63
|
+
//# sourceMappingURL=hooks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../../src/processors/observational-memory/hooks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAE1D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAMrD,KAAK,qBAAqB,GAAG,WAAW,CAAC,qBAAqB,CAAC,mBAAmB,CAAC,CAAC,CAAC;AAKrF;;;GAGG;AACH,eAAO,MAAM,gBAAgB,kDAAmD,CAAC;AAEjF;;;;GAIG;AACH,MAAM,MAAM,wBAAwB,GAAG,CACrC,GAAG,IAAI,EAAE,UAAU,CAAC,qBAAqB,CAAC,KACvC;IAAE,QAAQ,EAAE,eAAe,EAAE,CAAA;CAAE,GAAG,SAAS,CAAC;AAEjD,MAAM,WAAW,0BAA0B;IACzC;;;OAGG;IACH,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC/B;AAmFD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,CAAC,EAAE,0BAA0B,GAAG,wBAAwB,CAqClG"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"observation-groups.d.ts","sourceRoot":"","sources":["../../../src/processors/observational-memory/observation-groups.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;
|
|
1
|
+
{"version":3,"file":"observation-groups.d.ts","sourceRoot":"","sources":["../../../src/processors/observational-memory/observation-groups.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAgKD,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAED,wBAAgB,sBAAsB,CACpC,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,MAAM,EACb,EAAE,SAAqB,EACvB,eAAe,CAAC,EAAE,MAAM,EAAE,EAC1B,IAAI,CAAC,EAAE,MAAM,GACZ,MAAM,CAIR;AAED,wBAAgB,sBAAsB,CAAC,YAAY,EAAE,MAAM,GAAG,gBAAgB,EAAE,CAyB/E;AAED,wBAAgB,sBAAsB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAQnE;AAmCD,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,gBAAgB,EAAE,GAAG,MAAM,CAiChF;AAED,wBAAgB,oCAAoC,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAaxF;AAcD,wBAAgB,gCAAgC,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,gBAAgB,EAAE,GAAG,gBAAgB,EAAE,CA6ChH;AAED,wBAAgB,wCAAwC,CAAC,OAAO,EAAE,MAAM,EAAE,kBAAkB,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAyBnH"}
|