@mastra/memory 1.27.0-alpha.1 → 1.27.0-alpha.3
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 +22 -0
- package/dist/docs/SKILL.md +2 -1
- package/dist/docs/assets/SOURCE_MAP.json +1 -1
- package/dist/docs/references/docs-storage.md +1 -0
- package/dist/docs/references/reference-memory-memory-class.md +1 -0
- package/dist/docs/references/reference-memory-settled.md +57 -0
- package/dist/docs/references/reference-processors-token-limiter-processor.md +4 -0
- package/dist/index.cjs +4 -1
- package/dist/index.d.ts +52 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/processors/index.cjs +11 -1
- package/dist/processors/index.js +2 -2
- package/dist/processors/observational-memory/extracted-values.d.ts +6 -1
- package/dist/processors/observational-memory/extracted-values.d.ts.map +1 -1
- package/dist/processors/observational-memory/extractor.d.ts +12 -4
- package/dist/processors/observational-memory/extractor.d.ts.map +1 -1
- package/dist/processors/observational-memory/index.d.ts +2 -0
- package/dist/processors/observational-memory/index.d.ts.map +1 -1
- package/dist/processors/observational-memory/observation-strategies/async-buffer.d.ts.map +1 -1
- package/dist/processors/observational-memory/observation-strategies/base.d.ts.map +1 -1
- package/dist/processors/observational-memory/observation-strategies/resource-scoped.d.ts.map +1 -1
- package/dist/processors/observational-memory/observation-strategies/sync.d.ts.map +1 -1
- package/dist/processors/observational-memory/observation-strategies/types.d.ts +1 -0
- package/dist/processors/observational-memory/observation-strategies/types.d.ts.map +1 -1
- package/dist/processors/observational-memory/observation-turn/step.d.ts.map +1 -1
- package/dist/processors/observational-memory/observation-turn/turn.d.ts +3 -0
- package/dist/processors/observational-memory/observation-turn/turn.d.ts.map +1 -1
- package/dist/processors/observational-memory/observational-memory.d.ts +34 -0
- 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/reflector-runner.d.ts +5 -1
- package/dist/processors/observational-memory/reflector-runner.d.ts.map +1 -1
- package/dist/processors/observational-memory/subconscious/activity.d.ts +39 -0
- package/dist/processors/observational-memory/subconscious/activity.d.ts.map +1 -0
- package/dist/processors/observational-memory/subconscious/capture.d.ts +44 -0
- package/dist/processors/observational-memory/subconscious/capture.d.ts.map +1 -0
- package/dist/processors/observational-memory/subconscious/curate.d.ts +8 -0
- package/dist/processors/observational-memory/subconscious/curate.d.ts.map +1 -0
- package/dist/processors/observational-memory/subconscious/index.d.ts +30 -0
- package/dist/processors/observational-memory/subconscious/index.d.ts.map +1 -0
- package/dist/processors/observational-memory/subconscious/knowledge-tools.d.ts +12 -0
- package/dist/processors/observational-memory/subconscious/knowledge-tools.d.ts.map +1 -0
- package/dist/processors/observational-memory/subconscious/knowledge-write-tools.d.ts +16 -0
- package/dist/processors/observational-memory/subconscious/knowledge-write-tools.d.ts.map +1 -0
- package/dist/processors/observational-memory/subconscious/learn.d.ts +23 -0
- package/dist/processors/observational-memory/subconscious/learn.d.ts.map +1 -0
- package/dist/processors/observational-memory/subconscious/model.d.ts +21 -0
- package/dist/processors/observational-memory/subconscious/model.d.ts.map +1 -0
- package/dist/processors/observational-memory/subconscious/origin.d.ts +4 -0
- package/dist/processors/observational-memory/subconscious/origin.d.ts.map +1 -0
- package/dist/processors/observational-memory/subconscious/pinned-state-processor.d.ts +47 -0
- package/dist/processors/observational-memory/subconscious/pinned-state-processor.d.ts.map +1 -0
- package/dist/processors/observational-memory/subconscious/pinned.d.ts +54 -0
- package/dist/processors/observational-memory/subconscious/pinned.d.ts.map +1 -0
- package/dist/processors/observational-memory/subconscious/remind.d.ts +7 -0
- package/dist/processors/observational-memory/subconscious/remind.d.ts.map +1 -0
- package/dist/processors/observational-memory/subconscious/scope.d.ts +14 -0
- package/dist/processors/observational-memory/subconscious/scope.d.ts.map +1 -0
- package/dist/processors/observational-memory/subconscious/semantic-index.d.ts +20 -0
- package/dist/processors/observational-memory/subconscious/semantic-index.d.ts.map +1 -0
- package/dist/processors/observational-memory/subconscious/types.d.ts +118 -0
- package/dist/processors/observational-memory/subconscious/types.d.ts.map +1 -0
- package/dist/processors/observational-memory/types.d.ts +21 -0
- package/dist/processors/observational-memory/types.d.ts.map +1 -1
- package/dist/{src-_7ADtEwc.cjs → src-Ik0ksTOt.cjs} +2846 -297
- package/dist/src-Ik0ksTOt.cjs.map +1 -0
- package/dist/{src-BKRU-0Fx.js → src-t_4yoTX9.js} +2789 -300
- package/dist/src-t_4yoTX9.js.map +1 -0
- package/package.json +5 -5
- package/dist/src-BKRU-0Fx.js.map +0 -1
- package/dist/src-_7ADtEwc.cjs.map +0 -1
|
@@ -43,17 +43,17 @@ let async_mutex = require("async-mutex");
|
|
|
43
43
|
let lru_cache = require("lru-cache");
|
|
44
44
|
let xxhash_wasm = require("xxhash-wasm");
|
|
45
45
|
xxhash_wasm = __toESM$1(xxhash_wasm, 1);
|
|
46
|
+
let _mastra_core_storage = require("@mastra/core/storage");
|
|
47
|
+
let crypto$1 = require("crypto");
|
|
48
|
+
let _mastra_core_tools = require("@mastra/core/tools");
|
|
46
49
|
let _mastra_core_llm = require("@mastra/core/llm");
|
|
47
50
|
let fs = require("fs");
|
|
48
51
|
let path = require("path");
|
|
49
52
|
let _mastra_core_request_context = require("@mastra/core/request-context");
|
|
50
53
|
let tokenx = require("tokenx");
|
|
51
|
-
let crypto$1 = require("crypto");
|
|
52
|
-
let _mastra_core_storage = require("@mastra/core/storage");
|
|
53
54
|
let async_hooks = require("async_hooks");
|
|
54
55
|
let probe_image_size_sync_js = require("probe-image-size/sync.js");
|
|
55
56
|
probe_image_size_sync_js = __toESM$1(probe_image_size_sync_js, 1);
|
|
56
|
-
let _mastra_core_tools = require("@mastra/core/tools");
|
|
57
57
|
let _mastra_core_schema = require("@mastra/core/schema");
|
|
58
58
|
let _mastra_core_error = require("@mastra/core/error");
|
|
59
59
|
let _mastra_core_processors = require("@mastra/core/processors");
|
|
@@ -14637,162 +14637,6 @@ createIdGenerator({
|
|
|
14637
14637
|
size: 24
|
|
14638
14638
|
});
|
|
14639
14639
|
//#endregion
|
|
14640
|
-
//#region src/processors/observational-memory/constants.ts
|
|
14641
|
-
var constants_exports = /* @__PURE__ */ __exportAll({
|
|
14642
|
-
OBSERVATIONAL_MEMORY_DEFAULTS: () => OBSERVATIONAL_MEMORY_DEFAULTS,
|
|
14643
|
-
OBSERVATION_CONTEXT_INSTRUCTIONS: () => OBSERVATION_CONTEXT_INSTRUCTIONS,
|
|
14644
|
-
OBSERVATION_CONTEXT_PROMPT: () => OBSERVATION_CONTEXT_PROMPT,
|
|
14645
|
-
OBSERVATION_CONTINUATION_HINT: () => OBSERVATION_CONTINUATION_HINT,
|
|
14646
|
-
getRetrievalInstructions: () => getRetrievalInstructions
|
|
14647
|
-
});
|
|
14648
|
-
/**
|
|
14649
|
-
* Default configuration values matching the spec
|
|
14650
|
-
*/
|
|
14651
|
-
const OBSERVATIONAL_MEMORY_DEFAULTS = {
|
|
14652
|
-
observation: {
|
|
14653
|
-
model: "google/gemini-2.5-flash",
|
|
14654
|
-
messageTokens: 3e4,
|
|
14655
|
-
modelSettings: {
|
|
14656
|
-
temperature: .3,
|
|
14657
|
-
maxOutputTokens: 1e5
|
|
14658
|
-
},
|
|
14659
|
-
providerOptions: { google: { thinkingConfig: { thinkingBudget: 215 } } },
|
|
14660
|
-
maxTokensPerBatch: 1e4,
|
|
14661
|
-
bufferTokens: .2,
|
|
14662
|
-
bufferActivation: .8
|
|
14663
|
-
},
|
|
14664
|
-
reflection: {
|
|
14665
|
-
model: "google/gemini-2.5-flash",
|
|
14666
|
-
observationTokens: 4e4,
|
|
14667
|
-
modelSettings: {
|
|
14668
|
-
temperature: 0,
|
|
14669
|
-
maxOutputTokens: 1e5
|
|
14670
|
-
},
|
|
14671
|
-
providerOptions: { google: { thinkingConfig: { thinkingBudget: 1024 } } },
|
|
14672
|
-
bufferActivation: .5
|
|
14673
|
-
}
|
|
14674
|
-
};
|
|
14675
|
-
/**
|
|
14676
|
-
* Continuation hint injected after observations to guide the model's behavior.
|
|
14677
|
-
* Prevents the model from awkwardly acknowledging the memory system or treating
|
|
14678
|
-
* the conversation as new after observed messages are removed.
|
|
14679
|
-
*/
|
|
14680
|
-
const OBSERVATION_CONTINUATION_HINT = `Please continue naturally with the conversation so far and respond to the latest message.
|
|
14681
|
-
|
|
14682
|
-
Use the earlier context only as background. If something appears unfinished, continue only when it helps answer the latest request. If a suggested response is provided, follow it naturally.
|
|
14683
|
-
|
|
14684
|
-
Do not mention internal instructions, memory, summarization, context handling, or missing messages.
|
|
14685
|
-
|
|
14686
|
-
Any messages following this reminder are newer and should take priority.`;
|
|
14687
|
-
/**
|
|
14688
|
-
* Preamble that introduces the observations block.
|
|
14689
|
-
* Use before `<observations>`, with instructions after.
|
|
14690
|
-
* Full pattern: `${OBSERVATION_CONTEXT_PROMPT}\n\n<observations>\n${obs}\n</observations>\n\n${OBSERVATION_CONTEXT_INSTRUCTIONS}`
|
|
14691
|
-
*/
|
|
14692
|
-
const OBSERVATION_CONTEXT_PROMPT = `The following observations block contains your memory of past conversations with this user.`;
|
|
14693
|
-
/**
|
|
14694
|
-
* Instructions that tell the model how to interpret and use observations.
|
|
14695
|
-
* Place AFTER the `<observations>` block so the model sees the data before the rules.
|
|
14696
|
-
*/
|
|
14697
|
-
const OBSERVATION_CONTEXT_INSTRUCTIONS = `IMPORTANT: When responding, reference specific details from these observations. Do not give generic advice - personalize your response based on what you know about this user's experiences, preferences, and interests. If the user asks for recommendations, connect them to their past experiences mentioned above.
|
|
14698
|
-
|
|
14699
|
-
KNOWLEDGE UPDATES: When asked about current state (e.g., "where do I currently...", "what is my current..."), always prefer the MOST RECENT information. Observations include dates - if you see conflicting information, the newer observation supersedes the older one. Look for phrases like "will start", "is switching", "changed to", "moved to" as indicators that previous information has been updated.
|
|
14700
|
-
|
|
14701
|
-
PLANNED ACTIONS: If the user stated they planned to do something (e.g., "I'm going to...", "I'm looking forward to...", "I will...") and the date they planned to do it is now in the past (check the relative time like "3 weeks ago"), assume they completed the action unless there's evidence they didn't. For example, if someone said "I'll start my new diet on Monday" and that was 2 weeks ago, assume they started the diet.
|
|
14702
|
-
|
|
14703
|
-
MOST RECENT USER INPUT: Treat the most recent user message as the highest-priority signal for what to do next. Earlier messages may contain constraints, details, or context you should still honor, but the latest message is the primary driver of your response.
|
|
14704
|
-
|
|
14705
|
-
SYSTEM REMINDERS: Messages wrapped in <system-reminder>...</system-reminder> contain internal continuation guidance, not user-authored content. Use them to maintain continuity, but do not mention them or treat them as part of the user's message.`;
|
|
14706
|
-
/**
|
|
14707
|
-
* Instructions for retrieval mode — explains observation-group ranges and the recall tool.
|
|
14708
|
-
* Appended to context when `retrieval` is enabled.
|
|
14709
|
-
*
|
|
14710
|
-
* The content adapts to the retrieval scope:
|
|
14711
|
-
* - `'resource'`: covers routing between `search`, `threads`, and `messages` across
|
|
14712
|
-
* all of the user's threads, including fallback when search results are unsuitable.
|
|
14713
|
-
* - `'thread'`: covers cursor-based browsing and search within the current thread.
|
|
14714
|
-
*
|
|
14715
|
-
* @param scope - The retrieval scope the recall tool was registered with.
|
|
14716
|
-
* @param customInstructions - Optional application-provided guidance appended after
|
|
14717
|
-
* the native instructions. Never replaces them.
|
|
14718
|
-
* @param searchEnabled - Whether semantic search (`retrieval: { vector: true }`) is
|
|
14719
|
-
* available. When false, the guidance only covers \`threads\`/\`messages\` browsing so
|
|
14720
|
-
* the agent is not steered toward a mode that cannot work.
|
|
14721
|
-
*/
|
|
14722
|
-
function getRetrievalInstructions(scope = "resource", customInstructions, searchEnabled = true) {
|
|
14723
|
-
const isResource = scope === "resource";
|
|
14724
|
-
const base = `## Recall — looking up source messages
|
|
14725
|
-
|
|
14726
|
-
Your memory is comprised of observations which are sometimes wrapped in <observation-group> xml tags containing ranges like <observation-group range="startId:endId">. These ranges point back to the raw messages that each observation group was derived from. The original messages are still available — use the **recall** tool to retrieve them.
|
|
14727
|
-
|
|
14728
|
-
### When to use recall
|
|
14729
|
-
- The user asks you to **repeat, show, or reproduce** something from a past conversation
|
|
14730
|
-
- The user asks for **exact content** — code, text, quotes, error messages, URLs, file paths, specific numbers
|
|
14731
|
-
- Your observations mention something but your memory lacks the detail needed to fully answer (e.g. you know a blog post was shared but only have a summary of it)
|
|
14732
|
-
- You want to **verify or expand on** an observation before responding${isResource ? `
|
|
14733
|
-
- The user references another conversation that your observations don't cover — even if you have no observations yet, their other threads may contain it` : ""}
|
|
14734
|
-
|
|
14735
|
-
**Default to using recall when the user references specific past content.** Your observations capture the gist, not the details. If there's any doubt whether your memory is complete enough, use recall.
|
|
14736
|
-
|
|
14737
|
-
${isResource ? searchEnabled ? `### Choosing a mode
|
|
14738
|
-
The recall tool works across ALL of this user's conversation threads, not just the current one.
|
|
14739
|
-
|
|
14740
|
-
- Use \`mode: "search"\` with a \`query\` when you don't know which thread contains the answer. Each result includes its thread ID and the raw message IDs it came from, which you can use as a \`cursor\`.
|
|
14741
|
-
- Use \`mode: "messages"\` when you already know the thread — pass \`threadId\` to read another thread, or a \`cursor\` from an observation-group range or a search result.
|
|
14742
|
-
- Use \`mode: "threads"\` to list the user's threads (IDs, titles, dates) when you need to discover where something was discussed. Use \`before\`/\`after\` to narrow by date.
|
|
14743
|
-
|
|
14744
|
-
**If search results look irrelevant, do not give up.** Search only covers content that has been indexed — a short or recent conversation may exist in raw message history before any observation of it was created. When search returns nothing suitable but the user is clearly referring to a past conversation, call \`mode: "threads"\` to find candidate threads (titles and dates are strong clues), then read them with \`mode: "messages"\`. If a search result already gives you a thread ID, go straight to \`mode: "messages"\`.` : `### Choosing a mode
|
|
14745
|
-
The recall tool works across ALL of this user's conversation threads, not just the current one.
|
|
14746
|
-
|
|
14747
|
-
- Use \`mode: "threads"\` to list the user's threads (IDs, titles, dates) when you need to discover where something was discussed. Use \`before\`/\`after\` to narrow by date.
|
|
14748
|
-
- Use \`mode: "messages"\` when you know the thread — pass \`threadId\` to read another thread, or a \`cursor\` from an observation-group range.
|
|
14749
|
-
|
|
14750
|
-
When the user refers to a past conversation you don't have a cursor for, call \`mode: "threads"\` to find candidate threads (titles and dates are strong clues), then read them with \`mode: "messages"\`. Raw history may exist for threads that have no observations yet.` : `### Choosing a mode
|
|
14751
|
-
The recall tool is limited to the current conversation thread.
|
|
14752
|
-
|
|
14753
|
-
- Use \`mode: "messages"\` (default) to page through this thread's message history near a cursor.${searchEnabled ? `
|
|
14754
|
-
- Use \`mode: "search"\` with a \`query\` to find messages by content within this thread.` : ""}
|
|
14755
|
-
- Use \`mode: "threads"\` to get the current thread's ID, title, and dates.`}
|
|
14756
|
-
|
|
14757
|
-
### How to use recall with a cursor
|
|
14758
|
-
Each range has the format \`startId:endId\` where both are message IDs separated by a colon.
|
|
14759
|
-
|
|
14760
|
-
1. Find the observation group relevant to the user's question and extract the start or end ID from its range.
|
|
14761
|
-
2. Call \`recall\` with that ID as the \`cursor\`.
|
|
14762
|
-
3. Use \`page: 1\` (or omit) to read forward from the cursor, \`page: -1\` to read backward.
|
|
14763
|
-
4. If the first page doesn't have what you need, increment the page number to keep paginating.
|
|
14764
|
-
5. Check \`hasNextPage\`/\`hasPrevPage\` in the result to know if more pages exist in each direction.
|
|
14765
|
-
|
|
14766
|
-
### Detail levels
|
|
14767
|
-
By default recall returns **low** detail: truncated text and tool names only. Each message shows its ID and each part has a positional index like \`[p0]\`, \`[p1]\`, etc.
|
|
14768
|
-
|
|
14769
|
-
- Use \`detail: "high"\` to get full message content including tool arguments and results. This will only return the high detail version of a single message part at a time.
|
|
14770
|
-
- Use \`partIndex\` with a cursor to fetch a single part at full detail — for example, to read one specific tool result or code block without loading every part.
|
|
14771
|
-
|
|
14772
|
-
If the result says \`truncated: true\`, the output was cut to fit the token budget. You can paginate or use \`partIndex\` to target specific content. If a single part is itself too large, follow the returned \`nextCharOffset\` as described below.
|
|
14773
|
-
|
|
14774
|
-
### Following up on truncated parts
|
|
14775
|
-
Low-detail results may include truncation hints like:
|
|
14776
|
-
\`[truncated — call recall cursor="..." partIndex=N detail="high" for full content]\`
|
|
14777
|
-
|
|
14778
|
-
**When you see these hints and need the full content, make the exact call described in the hint.** This is the normal workflow: first recall at low detail to scan, then drill into specific parts at high detail. Do not stop at the low-detail result if the user asked for exact content.
|
|
14779
|
-
|
|
14780
|
-
If a single part is larger than the token budget, the \`partIndex\` result is \`truncated: true\` and includes \`nextCharOffset\`. Repeat the same call with \`charOffset\` set to that exact value to read the next chunk from where the previous one ended. Keep following \`nextCharOffset\` until the result no longer includes it — the chunks together contain the full part. Retrying without \`charOffset\` returns the same prefix again.
|
|
14781
|
-
|
|
14782
|
-
### When recall is NOT needed
|
|
14783
|
-
- The user is asking for a high-level summary and your observations already cover it
|
|
14784
|
-
- The question is about general preferences or facts that don't require source text
|
|
14785
|
-
${isResource ? `- No relevant observation range exists AND ${searchEnabled ? "`search`/`threads`" : "`threads`"} turned up nothing — but remember that raw history may exist for threads that have no observations yet, so check before concluding the information is unavailable` : `- There is no relevant range in your observations for the topic`}
|
|
14786
|
-
|
|
14787
|
-
Observation groups with range IDs and your recall tool allows you to think back and remember details you're fuzzy on.`;
|
|
14788
|
-
const custom = customInstructions?.trim();
|
|
14789
|
-
if (!custom) return base;
|
|
14790
|
-
return `${base}
|
|
14791
|
-
|
|
14792
|
-
### Additional recall guidance
|
|
14793
|
-
${custom}`;
|
|
14794
|
-
}
|
|
14795
|
-
//#endregion
|
|
14796
14640
|
//#region src/processors/observational-memory/extractor.ts
|
|
14797
14641
|
const BUILT_IN_SLUGS = /* @__PURE__ */ new Set([
|
|
14798
14642
|
"current-task",
|
|
@@ -14867,24 +14711,29 @@ var Extractor = class Extractor {
|
|
|
14867
14711
|
const name = config.name.trim();
|
|
14868
14712
|
const instructions = typeof config.instructions === "string" ? config.instructions.trim() : void 0;
|
|
14869
14713
|
const slug = slugifyExtractorName(name);
|
|
14714
|
+
const isHook = config.mode === "hook";
|
|
14870
14715
|
if (!name) throw new Error("Extractor name is required.");
|
|
14716
|
+
if (!isHook && !config.instructions) throw new Error(`Extractor "${name}" must include instructions.`);
|
|
14871
14717
|
if (instructions !== void 0 && !instructions) throw new Error(`Extractor "${name}" must include instructions.`);
|
|
14718
|
+
if (isHook && !config.onExtracted) throw new Error(`Hook extractor "${name}" must include an onExtracted handler.`);
|
|
14719
|
+
if (isHook && (config.instructions || config.schema)) throw new Error(`Hook extractor "${name}" cannot include instructions or a schema.`);
|
|
14872
14720
|
assertValidSlug(slug, name);
|
|
14873
14721
|
if (!internal && RESERVED_XML_TAGS.has(slug)) throw new Error(`Extractor slug "${slug}" is reserved by Observational Memory.`);
|
|
14874
14722
|
this.name = name;
|
|
14875
14723
|
this.slug = slug;
|
|
14876
|
-
this.instructionsConfig = config.instructions;
|
|
14724
|
+
this.instructionsConfig = config.instructions ?? "";
|
|
14877
14725
|
this.schemaConfig = config.schema;
|
|
14878
14726
|
this.instructions = instructions ?? "";
|
|
14879
|
-
this.schema = typeof config.schema === "function" ? zod.z.string() : config.schema ?? zod.z.string();
|
|
14880
|
-
this.mode = internal || !config.schema ? "inline" : "structured";
|
|
14881
|
-
this.includePreviousExtraction = config.includePreviousExtraction ?? true;
|
|
14882
|
-
this.metadataKeyPath = config.metadataKeyPath ?? `extracted.${slug}`;
|
|
14727
|
+
this.schema = isHook ? zod.z.unknown() : typeof config.schema === "function" ? zod.z.string() : config.schema ?? zod.z.string();
|
|
14728
|
+
this.mode = isHook ? "hook" : internal || !config.schema ? "inline" : "structured";
|
|
14729
|
+
this.includePreviousExtraction = isHook ? false : config.includePreviousExtraction ?? true;
|
|
14730
|
+
this.metadataKeyPath = isHook ? false : config.metadataKeyPath ?? `extracted.${slug}`;
|
|
14883
14731
|
this.onExtracted = config.onExtracted;
|
|
14884
14732
|
this.retryStructuredExtractionOnEmptyObject = config.retryStructuredExtractionOnEmptyObject ?? false;
|
|
14885
14733
|
this.internal = internal;
|
|
14886
14734
|
}
|
|
14887
14735
|
async resolve(context) {
|
|
14736
|
+
if (this.mode === "hook") return this;
|
|
14888
14737
|
const instructions = typeof this.instructionsConfig === "function" ? (await this.instructionsConfig(context)).trim() : this.instructionsConfig.trim();
|
|
14889
14738
|
if (!instructions) throw new Error(`Extractor "${this.name}" must include instructions.`);
|
|
14890
14739
|
const schema = typeof this.schemaConfig === "function" ? await this.schemaConfig(context) : this.schemaConfig;
|
|
@@ -15021,81 +14870,2416 @@ function buildExtractorPriorLines(extractors, priorExtractedValues) {
|
|
|
15021
14870
|
return lines;
|
|
15022
14871
|
}
|
|
15023
14872
|
//#endregion
|
|
15024
|
-
//#region src/processors/observational-memory/
|
|
15025
|
-
const
|
|
15026
|
-
|
|
15027
|
-
|
|
15028
|
-
|
|
15029
|
-
|
|
15030
|
-
|
|
15031
|
-
|
|
15032
|
-
|
|
15033
|
-
|
|
15034
|
-
|
|
15035
|
-
|
|
15036
|
-
|
|
14873
|
+
//#region src/processors/observational-memory/subconscious/activity.ts
|
|
14874
|
+
const SUBCONSCIOUS_ACTIVITY_STATE_ID = "subconscious-activity";
|
|
14875
|
+
async function getActivityTarget(store, event, scope) {
|
|
14876
|
+
if (event.recordType === "node") {
|
|
14877
|
+
const node = await store.getNode(event.recordId);
|
|
14878
|
+
if (!node || !(0, _mastra_core_storage.isKnowledgeScopeVisible)(node.scope, scope)) return {
|
|
14879
|
+
id: event.recordId,
|
|
14880
|
+
type: "node"
|
|
14881
|
+
};
|
|
14882
|
+
return {
|
|
14883
|
+
id: node.id,
|
|
14884
|
+
name: node.name,
|
|
14885
|
+
type: "node"
|
|
14886
|
+
};
|
|
15037
14887
|
}
|
|
15038
|
-
|
|
15039
|
-
|
|
15040
|
-
|
|
14888
|
+
const record = await store.getKnowledge({
|
|
14889
|
+
id: event.recordId,
|
|
14890
|
+
includeDeleted: true
|
|
14891
|
+
});
|
|
14892
|
+
const node = record ? await store.getNode(record.node) : void 0;
|
|
14893
|
+
if (!node || !(0, _mastra_core_storage.isKnowledgeScopeVisible)(node.scope, scope)) return {
|
|
14894
|
+
id: event.recordId,
|
|
14895
|
+
type: "node"
|
|
14896
|
+
};
|
|
14897
|
+
return {
|
|
14898
|
+
id: node.id,
|
|
14899
|
+
name: node.name,
|
|
14900
|
+
type: "node"
|
|
14901
|
+
};
|
|
15041
14902
|
}
|
|
15042
|
-
function
|
|
15043
|
-
|
|
15044
|
-
|
|
15045
|
-
|
|
14903
|
+
async function buildSubconsciousActivitySnapshot(input) {
|
|
14904
|
+
const events = await input.store.listActivity({
|
|
14905
|
+
scope: input.scope,
|
|
14906
|
+
limit: input.recentUpdates
|
|
14907
|
+
});
|
|
14908
|
+
const resolvedUpdates = await Promise.all(events.map(async (event) => {
|
|
14909
|
+
const target = await getActivityTarget(input.store, event, input.scope);
|
|
14910
|
+
return {
|
|
14911
|
+
action: event.action,
|
|
14912
|
+
type: event.recordType,
|
|
14913
|
+
name: target.name,
|
|
14914
|
+
targetId: target.id,
|
|
14915
|
+
targetType: target.type,
|
|
14916
|
+
createdAt: event.createdAt.toISOString()
|
|
14917
|
+
};
|
|
14918
|
+
}));
|
|
14919
|
+
const hotByRecord = /* @__PURE__ */ new Map();
|
|
14920
|
+
for (const update of resolvedUpdates) {
|
|
14921
|
+
if (!update.name) continue;
|
|
14922
|
+
const key = `${update.targetType}:${update.targetId}`;
|
|
14923
|
+
const existing = hotByRecord.get(key);
|
|
14924
|
+
if (existing) existing.updates += 1;
|
|
14925
|
+
else hotByRecord.set(key, {
|
|
14926
|
+
type: update.targetType,
|
|
14927
|
+
name: update.name,
|
|
14928
|
+
updates: 1
|
|
14929
|
+
});
|
|
14930
|
+
}
|
|
14931
|
+
const hot = [...hotByRecord.values()].sort((a, b) => b.updates - a.updates || a.name.localeCompare(b.name)).slice(0, input.recentUpdates);
|
|
14932
|
+
const updates = resolvedUpdates.map((update) => ({
|
|
14933
|
+
action: update.action,
|
|
14934
|
+
type: update.type,
|
|
14935
|
+
...update.name ? { name: update.name } : {},
|
|
14936
|
+
createdAt: update.createdAt
|
|
14937
|
+
}));
|
|
14938
|
+
const errors = input.errors?.filter(Boolean).slice(0, input.recentUpdates);
|
|
14939
|
+
return {
|
|
14940
|
+
updates,
|
|
14941
|
+
hot,
|
|
14942
|
+
...errors?.length ? { errors } : {}
|
|
14943
|
+
};
|
|
15046
14944
|
}
|
|
15047
|
-
function
|
|
15048
|
-
const
|
|
15049
|
-
|
|
15050
|
-
|
|
15051
|
-
|
|
15052
|
-
}
|
|
15053
|
-
return
|
|
14945
|
+
function renderSubconsciousActivity(snapshot) {
|
|
14946
|
+
const lines = snapshot.updates.map((update) => {
|
|
14947
|
+
const target = update.name ? `${update.type} [[${update.name}]]` : update.type;
|
|
14948
|
+
return `- ${update.action}: ${target}`;
|
|
14949
|
+
});
|
|
14950
|
+
const hot = snapshot.hot.map((record) => `[[${record.name}]] (${record.updates})`).join(", ");
|
|
14951
|
+
return [
|
|
14952
|
+
hot ? `Hot: ${hot}` : "Hot: none",
|
|
14953
|
+
"Recent updates:",
|
|
14954
|
+
...lines.length ? lines : ["- none"],
|
|
14955
|
+
...snapshot.errors?.length ? ["Errors:", ...snapshot.errors.map((error) => `- ${error}`)] : []
|
|
14956
|
+
].join("\n");
|
|
14957
|
+
}
|
|
14958
|
+
async function publishSubconsciousError(input) {
|
|
14959
|
+
await input.writer?.custom({
|
|
14960
|
+
type: "data-subconscious-error",
|
|
14961
|
+
data: {
|
|
14962
|
+
error: input.error,
|
|
14963
|
+
agent: input.agent
|
|
14964
|
+
}
|
|
14965
|
+
});
|
|
14966
|
+
if (!input.sendStateSignal) return;
|
|
14967
|
+
const snapshot = {
|
|
14968
|
+
updates: [],
|
|
14969
|
+
hot: [],
|
|
14970
|
+
errors: [input.error]
|
|
14971
|
+
};
|
|
14972
|
+
const contents = renderSubconsciousActivity(snapshot);
|
|
14973
|
+
await input.sendStateSignal({
|
|
14974
|
+
id: SUBCONSCIOUS_ACTIVITY_STATE_ID,
|
|
14975
|
+
mode: "snapshot",
|
|
14976
|
+
cacheKey: (0, crypto$1.createHash)("sha256").update(contents).digest("hex"),
|
|
14977
|
+
tagName: "state",
|
|
14978
|
+
attributes: { id: SUBCONSCIOUS_ACTIVITY_STATE_ID },
|
|
14979
|
+
metadata: { origin: "subconscious" },
|
|
14980
|
+
contents,
|
|
14981
|
+
value: snapshot
|
|
14982
|
+
});
|
|
15054
14983
|
}
|
|
15055
|
-
function
|
|
15056
|
-
|
|
15057
|
-
|
|
14984
|
+
async function publishSubconsciousActivity(input) {
|
|
14985
|
+
if (!input.sendStateSignal) return void 0;
|
|
14986
|
+
const snapshot = await buildSubconsciousActivitySnapshot(input);
|
|
14987
|
+
const contents = renderSubconsciousActivity(snapshot);
|
|
14988
|
+
const cacheKey = (0, crypto$1.createHash)("sha256").update(contents).digest("hex");
|
|
14989
|
+
await input.sendStateSignal({
|
|
14990
|
+
id: SUBCONSCIOUS_ACTIVITY_STATE_ID,
|
|
14991
|
+
mode: "snapshot",
|
|
14992
|
+
cacheKey,
|
|
14993
|
+
tagName: "state",
|
|
14994
|
+
attributes: { id: SUBCONSCIOUS_ACTIVITY_STATE_ID },
|
|
14995
|
+
metadata: { origin: "subconscious" },
|
|
14996
|
+
contents,
|
|
14997
|
+
value: snapshot
|
|
14998
|
+
});
|
|
14999
|
+
return snapshot;
|
|
15058
15000
|
}
|
|
15059
|
-
|
|
15060
|
-
|
|
15061
|
-
|
|
15062
|
-
|
|
15063
|
-
|
|
15064
|
-
|
|
15065
|
-
|
|
15066
|
-
|
|
15067
|
-
|
|
15068
|
-
|
|
15001
|
+
//#endregion
|
|
15002
|
+
//#region src/processors/observational-memory/subconscious/pinned.ts
|
|
15003
|
+
/** Processor id and state-signal id for the pinned-knowledge lane. */
|
|
15004
|
+
const SUBCONSCIOUS_PINS_STATE_ID = "subconscious-pins";
|
|
15005
|
+
/** Snapshot tag the model sees; the delta tag appends `-update`. */
|
|
15006
|
+
const PINNED_SNAPSHOT_TAG = "pinned-knowledge";
|
|
15007
|
+
const PINNED_DELTA_TAG = "pinned-knowledge-update";
|
|
15008
|
+
/** Reserved node holding the pin set. One record, at one fixed scope level. */
|
|
15009
|
+
const PINNED_NODE_NAME = "pinned";
|
|
15010
|
+
const PINNED_NODE_KIND = "system";
|
|
15011
|
+
const PINNED_NODE_SCOPE_LEVEL = "resource";
|
|
15012
|
+
const DEFAULT_PINNED_MAX_CHARACTERS = 2e3;
|
|
15013
|
+
const MAX_PINNED_MAX_CHARACTERS = 8e3;
|
|
15014
|
+
const PIN_IDENTITY = "subconscious:pin";
|
|
15015
|
+
function pinnedNodeScope(scope, maxScope) {
|
|
15016
|
+
return (0, _mastra_core_storage.expandKnowledgeScope)(scope, maxScope === "thread" ? "thread" : PINNED_NODE_SCOPE_LEVEL);
|
|
15017
|
+
}
|
|
15018
|
+
async function resolvePinnedNodeId(store, scope) {
|
|
15019
|
+
return (await store.resolveNode({
|
|
15020
|
+
name: PINNED_NODE_NAME,
|
|
15021
|
+
scope
|
|
15022
|
+
}))?.id;
|
|
15023
|
+
}
|
|
15024
|
+
/** Reuse the node wherever it is visible; otherwise create it. `createNode` is an idempotent upsert on (name, scope). */
|
|
15025
|
+
async function ensurePinnedNodeId(store, scope, maxScope) {
|
|
15026
|
+
const existing = await resolvePinnedNodeId(store, scope);
|
|
15027
|
+
if (existing) return existing;
|
|
15028
|
+
return (await store.createNode({
|
|
15029
|
+
name: PINNED_NODE_NAME,
|
|
15030
|
+
kind: PINNED_NODE_KIND,
|
|
15031
|
+
scope: pinnedNodeScope(scope, maxScope)
|
|
15032
|
+
})).id;
|
|
15069
15033
|
}
|
|
15070
|
-
|
|
15071
|
-
|
|
15072
|
-
|
|
15073
|
-
|
|
15074
|
-
|
|
15075
|
-
|
|
15076
|
-
|
|
15077
|
-
|
|
15078
|
-
|
|
15079
|
-
return
|
|
15034
|
+
/**
|
|
15035
|
+
* Assembles the current pin set.
|
|
15036
|
+
*
|
|
15037
|
+
* Reads use the FULL visible scope context, never a level-narrowed write scope: visibility is
|
|
15038
|
+
* subset containment, so querying at the node's level would drop pins written at narrower
|
|
15039
|
+
* levels. Deleted records are excluded explicitly.
|
|
15040
|
+
*/
|
|
15041
|
+
async function listPinnedKnowledge(input) {
|
|
15042
|
+
const nodeId = await resolvePinnedNodeId(input.store, input.scope);
|
|
15043
|
+
if (!nodeId) return { pins: [] };
|
|
15044
|
+
const pins = [];
|
|
15045
|
+
let after;
|
|
15046
|
+
do {
|
|
15047
|
+
const page = await input.store.listKnowledgeAbout({
|
|
15048
|
+
node: nodeId,
|
|
15049
|
+
scope: input.scope,
|
|
15050
|
+
after,
|
|
15051
|
+
includeDeleted: false
|
|
15052
|
+
});
|
|
15053
|
+
pins.push(...page.records);
|
|
15054
|
+
after = page.nextCursor;
|
|
15055
|
+
} while (after);
|
|
15056
|
+
return {
|
|
15057
|
+
nodeId,
|
|
15058
|
+
pins
|
|
15059
|
+
};
|
|
15080
15060
|
}
|
|
15081
|
-
function
|
|
15082
|
-
|
|
15083
|
-
const segments = getMetadataPathSegments(keyPath);
|
|
15084
|
-
if (!segments) return;
|
|
15085
|
-
let current = metadata;
|
|
15086
|
-
for (const segment of segments.slice(0, -1)) {
|
|
15087
|
-
const next = current[segment];
|
|
15088
|
-
if (!next || typeof next !== "object" || Array.isArray(next)) current[segment] = {};
|
|
15089
|
-
current = current[segment];
|
|
15090
|
-
}
|
|
15091
|
-
current[segments[segments.length - 1]] = value;
|
|
15061
|
+
function totalCharacters(pins) {
|
|
15062
|
+
return pins.reduce((sum, pin) => sum + pin.text.length, 0);
|
|
15092
15063
|
}
|
|
15093
|
-
function
|
|
15094
|
-
const
|
|
15095
|
-
|
|
15096
|
-
|
|
15097
|
-
|
|
15098
|
-
|
|
15064
|
+
function assertBudget(options, pins, incomingText, replacing) {
|
|
15065
|
+
const kept = replacing ? pins.filter((pin) => pin.id !== replacing.id) : pins;
|
|
15066
|
+
if (!replacing && kept.length >= options.maxPins) throw new Error(`Pin limit reached: the set holds at most ${options.maxPins}. Unpin something first.`);
|
|
15067
|
+
if (totalCharacters(kept) + incomingText.length > options.maxCharacters) throw new Error(`Pin budget exceeded: the pin set is limited to ${options.maxCharacters} characters in total.`);
|
|
15068
|
+
}
|
|
15069
|
+
function clampPinLevel(level) {
|
|
15070
|
+
return level === "org" ? "resource" : level;
|
|
15071
|
+
}
|
|
15072
|
+
function resolveWriteScope$1(options, level) {
|
|
15073
|
+
let effective = clampPinLevel(level ?? options.defaultScope);
|
|
15074
|
+
if (!level && options.maxScope === "thread") effective = "thread";
|
|
15075
|
+
const scope = (0, _mastra_core_storage.expandKnowledgeScope)(options.scope, effective);
|
|
15076
|
+
(0, _mastra_core_storage.assertKnowledgeScopeWithinCeiling)(scope, options.maxScope);
|
|
15077
|
+
return scope;
|
|
15078
|
+
}
|
|
15079
|
+
const scopeLevelSchema$1 = {
|
|
15080
|
+
type: "string",
|
|
15081
|
+
enum: ["resource", "thread"]
|
|
15082
|
+
};
|
|
15083
|
+
/** Shared pin write path used by the tool and capture-time pinning. */
|
|
15084
|
+
async function writePinnedKnowledge(store, options, text, level, metadata) {
|
|
15085
|
+
const { pins } = await listPinnedKnowledge({
|
|
15086
|
+
store,
|
|
15087
|
+
scope: options.scope
|
|
15088
|
+
});
|
|
15089
|
+
assertBudget(options, pins, text);
|
|
15090
|
+
const nodeId = await ensurePinnedNodeId(store, options.scope, options.maxScope);
|
|
15091
|
+
return store.appendKnowledge({
|
|
15092
|
+
node: nodeId,
|
|
15093
|
+
text,
|
|
15094
|
+
scope: resolveWriteScope$1(options, level),
|
|
15095
|
+
sourceThreadId: options.sourceThreadId,
|
|
15096
|
+
maxScope: options.maxScope,
|
|
15097
|
+
metadata,
|
|
15098
|
+
resolutionScope: options.scope,
|
|
15099
|
+
defaultScope: (0, _mastra_core_storage.expandKnowledgeScope)(options.scope, options.defaultScope)
|
|
15100
|
+
});
|
|
15101
|
+
}
|
|
15102
|
+
async function getStore$1(memory) {
|
|
15103
|
+
const store = await memory.storage.getStore("knowledge");
|
|
15104
|
+
if (!store) throw new Error("Pinned knowledge requires a configured knowledge storage domain.");
|
|
15105
|
+
return store;
|
|
15106
|
+
}
|
|
15107
|
+
async function requirePin(store, recordId, options) {
|
|
15108
|
+
const record = await store.getKnowledge({
|
|
15109
|
+
id: recordId,
|
|
15110
|
+
includeDeleted: false
|
|
15111
|
+
});
|
|
15112
|
+
if (!record) throw new Error(`Pin not found: ${recordId}`);
|
|
15113
|
+
const nodeId = await resolvePinnedNodeId(store, options.scope);
|
|
15114
|
+
if (!nodeId || record.node !== nodeId) throw new Error(`Record is not a pin: ${recordId}`);
|
|
15115
|
+
if (!(0, _mastra_core_storage.isKnowledgeScopeVisible)(record.scope, options.scope)) throw new Error("Pin is outside the visible scope.");
|
|
15116
|
+
return record;
|
|
15117
|
+
}
|
|
15118
|
+
/**
|
|
15119
|
+
* Pin lifecycle tools. Pin appends a record on the reserved node; unpin soft-deletes it
|
|
15120
|
+
* (auditable, restorable); edit is remove plus append because knowledge records are immutable,
|
|
15121
|
+
* so an edited pin carries a new record id.
|
|
15122
|
+
*/
|
|
15123
|
+
function createPinnedTools(memory, options) {
|
|
15124
|
+
return {
|
|
15125
|
+
knowledge_pin: (0, _mastra_core_tools.createTool)({
|
|
15126
|
+
id: "knowledge_pin",
|
|
15127
|
+
description: "Pin knowledge that must stay in context every turn without being asked for. Pins cost context permanently; pin only what is unconditionally relevant.",
|
|
15128
|
+
inputSchema: {
|
|
15129
|
+
type: "object",
|
|
15130
|
+
properties: {
|
|
15131
|
+
text: {
|
|
15132
|
+
type: "string",
|
|
15133
|
+
minLength: 1
|
|
15134
|
+
},
|
|
15135
|
+
scope: scopeLevelSchema$1,
|
|
15136
|
+
reason: {
|
|
15137
|
+
type: "string",
|
|
15138
|
+
minLength: 1,
|
|
15139
|
+
description: "One short sentence: why this must stay in context permanently."
|
|
15140
|
+
}
|
|
15141
|
+
},
|
|
15142
|
+
required: ["text"],
|
|
15143
|
+
additionalProperties: false
|
|
15144
|
+
},
|
|
15145
|
+
execute: async (input) => {
|
|
15146
|
+
const value = input;
|
|
15147
|
+
return writePinnedKnowledge(await getStore$1(memory), options, value.text, value.scope, value.reason ? { reason: value.reason } : void 0);
|
|
15148
|
+
}
|
|
15149
|
+
}),
|
|
15150
|
+
knowledge_unpin: (0, _mastra_core_tools.createTool)({
|
|
15151
|
+
id: "knowledge_unpin",
|
|
15152
|
+
description: "Remove a pin. The underlying knowledge record is soft-deleted and drops out of the pinned context.",
|
|
15153
|
+
inputSchema: {
|
|
15154
|
+
type: "object",
|
|
15155
|
+
properties: { recordId: {
|
|
15156
|
+
type: "string",
|
|
15157
|
+
minLength: 1
|
|
15158
|
+
} },
|
|
15159
|
+
required: ["recordId"],
|
|
15160
|
+
additionalProperties: false
|
|
15161
|
+
},
|
|
15162
|
+
execute: async (input) => {
|
|
15163
|
+
const store = await getStore$1(memory);
|
|
15164
|
+
const record = await requirePin(store, input.recordId, options);
|
|
15165
|
+
return store.removeKnowledge({
|
|
15166
|
+
id: record.id,
|
|
15167
|
+
deletedBy: PIN_IDENTITY
|
|
15168
|
+
});
|
|
15169
|
+
}
|
|
15170
|
+
}),
|
|
15171
|
+
knowledge_edit_pin: (0, _mastra_core_tools.createTool)({
|
|
15172
|
+
id: "knowledge_edit_pin",
|
|
15173
|
+
description: "Replace the text of an existing pin. The replacement carries a new knowledge record id.",
|
|
15174
|
+
inputSchema: {
|
|
15175
|
+
type: "object",
|
|
15176
|
+
properties: {
|
|
15177
|
+
recordId: {
|
|
15178
|
+
type: "string",
|
|
15179
|
+
minLength: 1
|
|
15180
|
+
},
|
|
15181
|
+
text: {
|
|
15182
|
+
type: "string",
|
|
15183
|
+
minLength: 1
|
|
15184
|
+
},
|
|
15185
|
+
reason: {
|
|
15186
|
+
type: "string",
|
|
15187
|
+
minLength: 1,
|
|
15188
|
+
description: "One short sentence: why this must stay in context permanently."
|
|
15189
|
+
}
|
|
15190
|
+
},
|
|
15191
|
+
required: ["recordId", "text"],
|
|
15192
|
+
additionalProperties: false
|
|
15193
|
+
},
|
|
15194
|
+
execute: async (input) => {
|
|
15195
|
+
const value = input;
|
|
15196
|
+
const store = await getStore$1(memory);
|
|
15197
|
+
const record = await requirePin(store, value.recordId, options);
|
|
15198
|
+
const { pins } = await listPinnedKnowledge({
|
|
15199
|
+
store,
|
|
15200
|
+
scope: options.scope
|
|
15201
|
+
});
|
|
15202
|
+
assertBudget(options, pins, value.text, record);
|
|
15203
|
+
await store.removeKnowledge({
|
|
15204
|
+
id: record.id,
|
|
15205
|
+
deletedBy: PIN_IDENTITY
|
|
15206
|
+
});
|
|
15207
|
+
return store.appendKnowledge({
|
|
15208
|
+
node: record.node,
|
|
15209
|
+
text: value.text,
|
|
15210
|
+
scope: record.scope,
|
|
15211
|
+
sourceThreadId: options.sourceThreadId,
|
|
15212
|
+
maxScope: record.maxScope,
|
|
15213
|
+
metadata: value.reason ? {
|
|
15214
|
+
...record.metadata,
|
|
15215
|
+
reason: value.reason
|
|
15216
|
+
} : record.metadata,
|
|
15217
|
+
resolutionScope: options.scope,
|
|
15218
|
+
defaultScope: (0, _mastra_core_storage.expandKnowledgeScope)(options.scope, options.defaultScope)
|
|
15219
|
+
});
|
|
15220
|
+
}
|
|
15221
|
+
})
|
|
15222
|
+
};
|
|
15223
|
+
}
|
|
15224
|
+
//#endregion
|
|
15225
|
+
//#region src/processors/observational-memory/subconscious/scope.ts
|
|
15226
|
+
/**
|
|
15227
|
+
* Resolves the resource rung of a subconscious knowledge scope.
|
|
15228
|
+
*
|
|
15229
|
+
* When the request context carries a `knowledgeResourceId` override, every
|
|
15230
|
+
* scope resolver anchors the resource rung on it instead of the run's
|
|
15231
|
+
* resourceId. This lets a host (for example, the factory) share one knowledge
|
|
15232
|
+
* graph across many runs of the same project while leaving thread identity and
|
|
15233
|
+
* observational memory untouched. Without the override, behavior is unchanged:
|
|
15234
|
+
* the fallback (the run's resourceId) is used as-is.
|
|
15235
|
+
*/
|
|
15236
|
+
function resolveKnowledgeResourceId(requestContext, fallback) {
|
|
15237
|
+
const override = requestContext?.get?.("knowledgeResourceId");
|
|
15238
|
+
if (typeof override === "string" && override.trim()) return override;
|
|
15239
|
+
return fallback;
|
|
15240
|
+
}
|
|
15241
|
+
//#endregion
|
|
15242
|
+
//#region src/processors/observational-memory/subconscious/capture.ts
|
|
15243
|
+
const CAPTURE_GUIDANCE_PAGE = "capture-guidance";
|
|
15244
|
+
const MAX_CAPTURE_GUIDANCE_LENGTH = 4e3;
|
|
15245
|
+
const SCOPE_ORDER = {
|
|
15246
|
+
org: 0,
|
|
15247
|
+
resource: 1,
|
|
15248
|
+
thread: 2
|
|
15249
|
+
};
|
|
15250
|
+
const subconsciousCaptureSchema = zod.z.object({ nodes: zod.z.array(zod.z.object({
|
|
15251
|
+
name: zod.z.string().trim().min(1),
|
|
15252
|
+
kind: zod.z.string().trim().min(1),
|
|
15253
|
+
scope: zod.z.enum([
|
|
15254
|
+
"org",
|
|
15255
|
+
"resource",
|
|
15256
|
+
"thread"
|
|
15257
|
+
]).optional(),
|
|
15258
|
+
records: zod.z.array(zod.z.object({
|
|
15259
|
+
text: zod.z.string().trim().min(1),
|
|
15260
|
+
scope: zod.z.enum([
|
|
15261
|
+
"org",
|
|
15262
|
+
"resource",
|
|
15263
|
+
"thread"
|
|
15264
|
+
]).optional(),
|
|
15265
|
+
when: zod.z.string().trim().min(1).optional(),
|
|
15266
|
+
reason: zod.z.string().trim().min(1)
|
|
15267
|
+
}))
|
|
15268
|
+
})) });
|
|
15269
|
+
const subconsciousCapturePinningSchema = zod.z.object({ nodes: zod.z.array(zod.z.object({
|
|
15270
|
+
name: zod.z.string().trim().min(1),
|
|
15271
|
+
kind: zod.z.string().trim().min(1),
|
|
15272
|
+
scope: zod.z.enum([
|
|
15273
|
+
"org",
|
|
15274
|
+
"resource",
|
|
15275
|
+
"thread"
|
|
15276
|
+
]).optional(),
|
|
15277
|
+
records: zod.z.array(zod.z.object({
|
|
15278
|
+
text: zod.z.string().trim().min(1),
|
|
15279
|
+
scope: zod.z.enum([
|
|
15280
|
+
"org",
|
|
15281
|
+
"resource",
|
|
15282
|
+
"thread"
|
|
15283
|
+
]).optional(),
|
|
15284
|
+
when: zod.z.string().trim().min(1).optional(),
|
|
15285
|
+
reason: zod.z.string().trim().min(1),
|
|
15286
|
+
pin: zod.z.boolean().optional()
|
|
15287
|
+
}))
|
|
15288
|
+
})) });
|
|
15289
|
+
const CAPTURE_PINNING_INSTRUCTIONS = `Mark pin: true only for durable user preferences or hard constraints that should apply in every future session without being asked for.`;
|
|
15290
|
+
const CAPTURE_INSTRUCTIONS = `Extract durable, explicitly stated knowledge from the observations.
|
|
15291
|
+
Return nodes with short stable names, a freeform kind, and knowledge records nested under the node each record is about.
|
|
15292
|
+
Use common kinds such as person, task, event, project, organization, or document when they fit.
|
|
15293
|
+
Set node scope to the narrowest level where that identity and content should be shared. Omit it to use the configured default scope.
|
|
15294
|
+
Knowledge records must be grounded in the conversation, concise, and written as prose. Do not infer unstated information.
|
|
15295
|
+
Wrap every named node mentioned in record text in [[wikilinks]].
|
|
15296
|
+
Set a record scope only when the conversation establishes where it applies. Use org for organization-wide records, resource for records shared across this resource's conversations, and thread for conversation-private records.
|
|
15297
|
+
Omit scope when uncertain; omitted record scopes stay private to the current thread.
|
|
15298
|
+
Emit when only when the conversation anchors the referred time. Resolve relative dates against the current date and use ISO 8601.
|
|
15299
|
+
Capture what was learned through the work, not what the session was told: skip records that merely restate standing instructions, configured rules, or the text of the task or issue the session was handed. The exception is an explicit request from the user to remember something, which is always captured even when it duplicates an existing instruction.`;
|
|
15300
|
+
const CAPTURE_REASON_INSTRUCTIONS = `Every record requires a reason: the concrete why behind capturing it, in one short sentence - what it cost to learn or when it will matter again (and for pinned records, why it must stay in context). Never write generic filler such as "seemed relevant" or "useful context".`;
|
|
15301
|
+
function clampScope(level, ceiling) {
|
|
15302
|
+
return ceiling && SCOPE_ORDER[level] < SCOPE_ORDER[ceiling] ? ceiling : level;
|
|
15303
|
+
}
|
|
15304
|
+
function requireScopeContext(context) {
|
|
15305
|
+
const organizationId = context.requestContext?.get("organizationId");
|
|
15306
|
+
if (typeof organizationId !== "string" || !organizationId.trim()) throw new Error("Subconscious requires requestContext.organizationId to derive scoped knowledge. Set organizationId on the request context for this conversation.");
|
|
15307
|
+
const resourceId = resolveKnowledgeResourceId(context.requestContext, context.resourceId);
|
|
15308
|
+
if (!resourceId) throw new Error("Subconscious requires resourceId to derive scoped knowledge.");
|
|
15309
|
+
if (!context.threadId) throw new Error("Subconscious requires threadId to derive scoped knowledge.");
|
|
15310
|
+
return [
|
|
15311
|
+
`org:${organizationId}`,
|
|
15312
|
+
`resource:${resourceId}`,
|
|
15313
|
+
`thread:${context.threadId}`
|
|
15314
|
+
];
|
|
15315
|
+
}
|
|
15316
|
+
async function getKnowledgeStore$1(context) {
|
|
15317
|
+
if (!context.memory) throw new Error("Subconscious capture requires an active Memory instance.");
|
|
15318
|
+
const store = await context.memory.storage.getStore("knowledge");
|
|
15319
|
+
if (!store) throw new Error("Subconscious requires a knowledge storage domain. Configure a storage adapter that provides stores.knowledge.");
|
|
15320
|
+
return store;
|
|
15321
|
+
}
|
|
15322
|
+
function parseWhen(value) {
|
|
15323
|
+
if (!value) return void 0;
|
|
15324
|
+
const when = new Date(value);
|
|
15325
|
+
if (Number.isNaN(when.getTime())) throw new Error(`Invalid Subconscious record time: ${value}`);
|
|
15326
|
+
return when;
|
|
15327
|
+
}
|
|
15328
|
+
var SubconsciousCaptureExtractor = class extends Extractor {
|
|
15329
|
+
constructor(options) {
|
|
15330
|
+
const capturePinning = options.pins !== false && options.pins !== void 0 && options.pins.capturePinning;
|
|
15331
|
+
const pinNotes = /* @__PURE__ */ new WeakMap();
|
|
15332
|
+
const defaultImplementation = async (context) => {
|
|
15333
|
+
const scopeContext = requireScopeContext(context);
|
|
15334
|
+
const store = await getKnowledgeStore$1(context);
|
|
15335
|
+
const droppedPins = [];
|
|
15336
|
+
for (const extractedNode of context.current.nodes) {
|
|
15337
|
+
const nodeScope = (0, _mastra_core_storage.expandKnowledgeScope)(scopeContext, clampScope(extractedNode.scope ?? options.defaultScope, options.maxScope));
|
|
15338
|
+
const node = await store.createNode({
|
|
15339
|
+
name: extractedNode.name,
|
|
15340
|
+
kind: extractedNode.kind,
|
|
15341
|
+
scope: nodeScope
|
|
15342
|
+
});
|
|
15343
|
+
for (const extractedKnowledge of extractedNode.records) {
|
|
15344
|
+
if (capturePinning && extractedKnowledge.pin === true && options.pins) {
|
|
15345
|
+
try {
|
|
15346
|
+
await writePinnedKnowledge(store, {
|
|
15347
|
+
scope: scopeContext,
|
|
15348
|
+
sourceThreadId: context.threadId,
|
|
15349
|
+
defaultScope: options.defaultScope,
|
|
15350
|
+
maxScope: options.maxScope,
|
|
15351
|
+
maxPins: options.pins.maxPins,
|
|
15352
|
+
maxCharacters: options.pins.maxCharacters
|
|
15353
|
+
}, extractedKnowledge.text, extractedKnowledge.scope, extractedKnowledge.reason ? { reason: extractedKnowledge.reason } : void 0);
|
|
15354
|
+
} catch (error) {
|
|
15355
|
+
droppedPins.push(`Capture-time pin dropped: ${error instanceof Error ? error.message : String(error)}`);
|
|
15356
|
+
}
|
|
15357
|
+
continue;
|
|
15358
|
+
}
|
|
15359
|
+
const knowledgeLevel = clampScope(extractedKnowledge.scope ?? "thread", options.maxScope);
|
|
15360
|
+
await store.appendKnowledge({
|
|
15361
|
+
node,
|
|
15362
|
+
text: extractedKnowledge.text,
|
|
15363
|
+
scope: (0, _mastra_core_storage.expandKnowledgeScope)(scopeContext, knowledgeLevel),
|
|
15364
|
+
sourceThreadId: context.threadId,
|
|
15365
|
+
when: parseWhen(extractedKnowledge.when),
|
|
15366
|
+
maxScope: options.maxScope,
|
|
15367
|
+
metadata: extractedKnowledge.reason ? { reason: extractedKnowledge.reason } : void 0,
|
|
15368
|
+
resolutionScope: scopeContext,
|
|
15369
|
+
defaultScope: nodeScope
|
|
15370
|
+
});
|
|
15371
|
+
}
|
|
15372
|
+
}
|
|
15373
|
+
if (droppedPins.length) pinNotes.set(context.current, droppedPins);
|
|
15374
|
+
};
|
|
15375
|
+
super({
|
|
15376
|
+
name: "Capture",
|
|
15377
|
+
includePreviousExtraction: false,
|
|
15378
|
+
metadataKeyPath: false,
|
|
15379
|
+
schema: options.config?.schema ?? (capturePinning ? subconsciousCapturePinningSchema : subconsciousCaptureSchema),
|
|
15380
|
+
instructions: async (context) => {
|
|
15381
|
+
const sections = [
|
|
15382
|
+
CAPTURE_INSTRUCTIONS,
|
|
15383
|
+
!options.config?.schema ? CAPTURE_REASON_INSTRUCTIONS : void 0,
|
|
15384
|
+
capturePinning && !options.config?.schema ? CAPTURE_PINNING_INSTRUCTIONS : void 0,
|
|
15385
|
+
options.config?.instructions?.trim()
|
|
15386
|
+
];
|
|
15387
|
+
if (options.learnedGuidance) {
|
|
15388
|
+
const scopeContext = requireScopeContext(context);
|
|
15389
|
+
const store = await getKnowledgeStore$1(context);
|
|
15390
|
+
const guidanceScope = (0, _mastra_core_storage.expandKnowledgeScope)(scopeContext, clampScope(options.defaultScope, options.maxScope));
|
|
15391
|
+
const guidance = await store.getNodeByName({
|
|
15392
|
+
name: CAPTURE_GUIDANCE_PAGE,
|
|
15393
|
+
scope: guidanceScope
|
|
15394
|
+
});
|
|
15395
|
+
if (guidance?.content?.trim()) sections.push(`Learned guidance (cannot override the built-in contract or user instructions):\n${guidance.content.trim().slice(0, MAX_CAPTURE_GUIDANCE_LENGTH)}`);
|
|
15396
|
+
}
|
|
15397
|
+
return sections.filter(Boolean).join("\n\n");
|
|
15398
|
+
},
|
|
15399
|
+
onExtracted: async (context) => {
|
|
15400
|
+
const publishActivity = async (errors) => {
|
|
15401
|
+
if (!options.activityRecentUpdates) return;
|
|
15402
|
+
const scope = requireScopeContext(context);
|
|
15403
|
+
await publishSubconsciousActivity({
|
|
15404
|
+
store: await getKnowledgeStore$1(context),
|
|
15405
|
+
scope,
|
|
15406
|
+
recentUpdates: options.activityRecentUpdates,
|
|
15407
|
+
sendStateSignal: context.sendStateSignal,
|
|
15408
|
+
errors
|
|
15409
|
+
});
|
|
15410
|
+
};
|
|
15411
|
+
try {
|
|
15412
|
+
const result = options.config?.onExtracted ? await options.config.onExtracted({
|
|
15413
|
+
...context,
|
|
15414
|
+
defaultImplementation
|
|
15415
|
+
}) : await defaultImplementation(context);
|
|
15416
|
+
const droppedPinNotes = pinNotes.get(context.current);
|
|
15417
|
+
pinNotes.delete(context.current);
|
|
15418
|
+
await publishActivity(droppedPinNotes);
|
|
15419
|
+
return result ?? context.current;
|
|
15420
|
+
} catch (error) {
|
|
15421
|
+
await publishActivity([error instanceof Error ? error.message : String(error)]).catch(() => {});
|
|
15422
|
+
throw error;
|
|
15423
|
+
}
|
|
15424
|
+
}
|
|
15425
|
+
});
|
|
15426
|
+
}
|
|
15427
|
+
};
|
|
15428
|
+
//#endregion
|
|
15429
|
+
//#region src/processors/observational-memory/subconscious/knowledge-tools.ts
|
|
15430
|
+
const DEFAULT_LIMIT = 10;
|
|
15431
|
+
const MAX_LIMIT = 50;
|
|
15432
|
+
function resolveScope$3(context) {
|
|
15433
|
+
const organizationId = context?.requestContext?.get("organizationId");
|
|
15434
|
+
const resourceId = resolveKnowledgeResourceId(context?.requestContext, context?.agent?.resourceId);
|
|
15435
|
+
const threadId = context?.agent?.threadId;
|
|
15436
|
+
if (typeof organizationId !== "string" || !organizationId.trim()) throw new Error("Knowledge tools require requestContext.organizationId.");
|
|
15437
|
+
if (!resourceId) throw new Error("Knowledge tools require an active resourceId.");
|
|
15438
|
+
if (!threadId) throw new Error("Knowledge tools require an active threadId.");
|
|
15439
|
+
return [
|
|
15440
|
+
`org:${organizationId}`,
|
|
15441
|
+
`resource:${resourceId}`,
|
|
15442
|
+
`thread:${threadId}`
|
|
15443
|
+
];
|
|
15444
|
+
}
|
|
15445
|
+
async function getKnowledgeStore(memory) {
|
|
15446
|
+
const store = await memory.storage.getStore("knowledge");
|
|
15447
|
+
if (!store) throw new Error("Knowledge tools require a configured knowledge storage domain.");
|
|
15448
|
+
return store;
|
|
15449
|
+
}
|
|
15450
|
+
function normalizeLimit(limit) {
|
|
15451
|
+
return Math.min(Math.max(limit ?? DEFAULT_LIMIT, 1), MAX_LIMIT);
|
|
15452
|
+
}
|
|
15453
|
+
function serializeRecord(record) {
|
|
15454
|
+
return {
|
|
15455
|
+
id: record.id,
|
|
15456
|
+
text: record.text,
|
|
15457
|
+
scope: record.scope,
|
|
15458
|
+
sourceThreadId: record.sourceThreadId,
|
|
15459
|
+
capturedAt: record.capturedAt.toISOString(),
|
|
15460
|
+
when: record.when?.toISOString()
|
|
15461
|
+
};
|
|
15462
|
+
}
|
|
15463
|
+
function serializeNode(node) {
|
|
15464
|
+
return {
|
|
15465
|
+
id: node.id,
|
|
15466
|
+
type: node.type,
|
|
15467
|
+
name: node.name,
|
|
15468
|
+
kind: node.kind,
|
|
15469
|
+
content: node.content,
|
|
15470
|
+
scope: node.scope,
|
|
15471
|
+
version: node.version,
|
|
15472
|
+
updatedAt: node.updatedAt.toISOString()
|
|
15473
|
+
};
|
|
15474
|
+
}
|
|
15475
|
+
async function loadSemanticResult(store, scope, candidate) {
|
|
15476
|
+
const type = candidate.metadata?.document_type;
|
|
15477
|
+
if (type === "node") {
|
|
15478
|
+
const node = await store.getNode(candidate.id.slice(15));
|
|
15479
|
+
if (!node || node.mergedInto || !(0, _mastra_core_storage.isKnowledgeScopeVisible)(node.scope, scope)) return null;
|
|
15480
|
+
return {
|
|
15481
|
+
type: "node",
|
|
15482
|
+
id: node.id,
|
|
15483
|
+
recordId: node.id,
|
|
15484
|
+
name: node.name,
|
|
15485
|
+
text: `${node.name}\n${node.content ?? ""}`,
|
|
15486
|
+
scope: node.scope,
|
|
15487
|
+
semanticScore: candidate.score
|
|
15488
|
+
};
|
|
15489
|
+
}
|
|
15490
|
+
if (type === "record") {
|
|
15491
|
+
const record = await store.getKnowledge({ id: candidate.id.slice(17) });
|
|
15492
|
+
if (!record || !(0, _mastra_core_storage.isKnowledgeScopeVisible)(record.scope, scope)) return null;
|
|
15493
|
+
const node = await store.getNode(record.node);
|
|
15494
|
+
const parentVisible = Boolean(node && !node.mergedInto && (0, _mastra_core_storage.isKnowledgeScopeVisible)(node.scope, scope));
|
|
15495
|
+
return {
|
|
15496
|
+
type: "record",
|
|
15497
|
+
id: record.id,
|
|
15498
|
+
recordId: parentVisible ? node.id : record.id,
|
|
15499
|
+
name: parentVisible ? node.name : "(private node)",
|
|
15500
|
+
text: record.text,
|
|
15501
|
+
scope: record.scope,
|
|
15502
|
+
semanticScore: candidate.score
|
|
15503
|
+
};
|
|
15504
|
+
}
|
|
15505
|
+
return null;
|
|
15506
|
+
}
|
|
15507
|
+
function mergeHybridResults(lexical, semantic, limit) {
|
|
15508
|
+
const ranked = /* @__PURE__ */ new Map();
|
|
15509
|
+
lexical.forEach((result, index) => {
|
|
15510
|
+
ranked.set(`${result.type}:${result.id}`, {
|
|
15511
|
+
...result,
|
|
15512
|
+
score: 1 / (60 + index + 1),
|
|
15513
|
+
sources: ["lexical"]
|
|
15514
|
+
});
|
|
15515
|
+
});
|
|
15516
|
+
semantic.forEach((result, index) => {
|
|
15517
|
+
const key = `${result.type}:${result.id}`;
|
|
15518
|
+
const existing = ranked.get(key);
|
|
15519
|
+
const reciprocalRank = 1 / (60 + index + 1);
|
|
15520
|
+
ranked.set(key, {
|
|
15521
|
+
...result,
|
|
15522
|
+
score: (existing?.score ?? 0) + reciprocalRank,
|
|
15523
|
+
sources: existing ? ["lexical", "semantic"] : ["semantic"],
|
|
15524
|
+
semanticScore: result.semanticScore
|
|
15525
|
+
});
|
|
15526
|
+
});
|
|
15527
|
+
return [...ranked.values()].sort((left, right) => right.score - left.score || left.id.localeCompare(right.id)).slice(0, limit);
|
|
15528
|
+
}
|
|
15529
|
+
function createKnowledgeTools(memory, fixedScope) {
|
|
15530
|
+
return {
|
|
15531
|
+
knowledge_search: (0, _mastra_core_tools.createTool)({
|
|
15532
|
+
id: "knowledge_search",
|
|
15533
|
+
description: "Search durable scoped knowledge across nodes and knowledge records using lexical and semantic retrieval.",
|
|
15534
|
+
inputSchema: {
|
|
15535
|
+
type: "object",
|
|
15536
|
+
properties: {
|
|
15537
|
+
query: {
|
|
15538
|
+
type: "string",
|
|
15539
|
+
minLength: 1,
|
|
15540
|
+
description: "The knowledge to search for."
|
|
15541
|
+
},
|
|
15542
|
+
limit: {
|
|
15543
|
+
type: "integer",
|
|
15544
|
+
minimum: 1,
|
|
15545
|
+
maximum: MAX_LIMIT,
|
|
15546
|
+
description: "Maximum results. Defaults to 10."
|
|
15547
|
+
}
|
|
15548
|
+
},
|
|
15549
|
+
required: ["query"],
|
|
15550
|
+
additionalProperties: false
|
|
15551
|
+
},
|
|
15552
|
+
execute: async (input, context) => {
|
|
15553
|
+
const { query, limit: requestedLimit } = input;
|
|
15554
|
+
const scope = fixedScope ?? resolveScope$3(context);
|
|
15555
|
+
const limit = normalizeLimit(requestedLimit);
|
|
15556
|
+
const store = await getKnowledgeStore(memory);
|
|
15557
|
+
const semanticCandidates = await memory.getKnowledgeSemanticIndex().then((index) => index.search(query, scope, limit * 2));
|
|
15558
|
+
return {
|
|
15559
|
+
query,
|
|
15560
|
+
results: mergeHybridResults(await store.search({
|
|
15561
|
+
query,
|
|
15562
|
+
scope,
|
|
15563
|
+
limit: limit * 2
|
|
15564
|
+
}), (await Promise.all(semanticCandidates.map((candidate) => loadSemanticResult(store, scope, candidate)))).filter((result) => Boolean(result)), limit)
|
|
15565
|
+
};
|
|
15566
|
+
}
|
|
15567
|
+
}),
|
|
15568
|
+
knowledge_read: (0, _mastra_core_tools.createTool)({
|
|
15569
|
+
id: "knowledge_read",
|
|
15570
|
+
description: "Read a knowledge node, its content, and knowledge records about or linked to it by name or ID.",
|
|
15571
|
+
inputSchema: {
|
|
15572
|
+
type: "object",
|
|
15573
|
+
properties: {
|
|
15574
|
+
id: {
|
|
15575
|
+
type: "string",
|
|
15576
|
+
minLength: 1
|
|
15577
|
+
},
|
|
15578
|
+
name: {
|
|
15579
|
+
type: "string",
|
|
15580
|
+
minLength: 1
|
|
15581
|
+
},
|
|
15582
|
+
relationship: {
|
|
15583
|
+
type: "string",
|
|
15584
|
+
enum: [
|
|
15585
|
+
"about",
|
|
15586
|
+
"mentioning",
|
|
15587
|
+
"related"
|
|
15588
|
+
],
|
|
15589
|
+
description: "Knowledge relationship to query. Defaults to about."
|
|
15590
|
+
},
|
|
15591
|
+
cursor: {
|
|
15592
|
+
type: "string",
|
|
15593
|
+
minLength: 1,
|
|
15594
|
+
description: "Return knowledge records after this record ULID."
|
|
15595
|
+
},
|
|
15596
|
+
limit: {
|
|
15597
|
+
type: "integer",
|
|
15598
|
+
minimum: 1,
|
|
15599
|
+
maximum: MAX_LIMIT
|
|
15600
|
+
}
|
|
15601
|
+
},
|
|
15602
|
+
additionalProperties: false
|
|
15603
|
+
},
|
|
15604
|
+
execute: async (input, context) => {
|
|
15605
|
+
const { id, name, relationship = "about", cursor, limit: requestedLimit } = input;
|
|
15606
|
+
if (!id && !name) throw new Error("knowledge_read requires id or name.");
|
|
15607
|
+
const scope = fixedScope ?? resolveScope$3(context);
|
|
15608
|
+
const store = await getKnowledgeStore(memory);
|
|
15609
|
+
const node = id ? await store.getNode(id) : await store.resolveNode({
|
|
15610
|
+
name,
|
|
15611
|
+
scope
|
|
15612
|
+
});
|
|
15613
|
+
if (!node || node.mergedInto || !(0, _mastra_core_storage.isKnowledgeScopeVisible)(node.scope, scope)) return { found: false };
|
|
15614
|
+
const result = await (relationship === "related" ? store.listKnowledgeRelatedTo : relationship === "mentioning" ? store.listKnowledgeMentioning : store.listKnowledgeAbout).call(store, {
|
|
15615
|
+
node,
|
|
15616
|
+
scope,
|
|
15617
|
+
after: cursor,
|
|
15618
|
+
limit: normalizeLimit(requestedLimit)
|
|
15619
|
+
});
|
|
15620
|
+
return {
|
|
15621
|
+
found: true,
|
|
15622
|
+
node: serializeNode(node),
|
|
15623
|
+
records: result.records.map(serializeRecord),
|
|
15624
|
+
nextCursor: result.nextCursor
|
|
15625
|
+
};
|
|
15626
|
+
}
|
|
15627
|
+
}),
|
|
15628
|
+
knowledge_browse: (0, _mastra_core_tools.createTool)({
|
|
15629
|
+
id: "knowledge_browse",
|
|
15630
|
+
description: "Browse visible knowledge nodes by scope and name prefix, or follow a node’s mentions and backlinks.",
|
|
15631
|
+
inputSchema: {
|
|
15632
|
+
type: "object",
|
|
15633
|
+
properties: {
|
|
15634
|
+
namePrefix: { type: "string" },
|
|
15635
|
+
kind: {
|
|
15636
|
+
type: "string",
|
|
15637
|
+
description: "Optional node kind filter."
|
|
15638
|
+
},
|
|
15639
|
+
hasContent: {
|
|
15640
|
+
type: "boolean",
|
|
15641
|
+
description: "Filter nodes by whether they have long-form content."
|
|
15642
|
+
},
|
|
15643
|
+
node: {
|
|
15644
|
+
type: "string",
|
|
15645
|
+
minLength: 1,
|
|
15646
|
+
description: "When set, return knowledge related to this node."
|
|
15647
|
+
},
|
|
15648
|
+
cursor: {
|
|
15649
|
+
type: "string",
|
|
15650
|
+
minLength: 1
|
|
15651
|
+
},
|
|
15652
|
+
limit: {
|
|
15653
|
+
type: "integer",
|
|
15654
|
+
minimum: 1,
|
|
15655
|
+
maximum: MAX_LIMIT
|
|
15656
|
+
}
|
|
15657
|
+
},
|
|
15658
|
+
additionalProperties: false
|
|
15659
|
+
},
|
|
15660
|
+
execute: async (input, context) => {
|
|
15661
|
+
const { namePrefix, kind, hasContent, node: nodeReference, cursor, limit: requestedLimit } = input;
|
|
15662
|
+
const scope = fixedScope ?? resolveScope$3(context);
|
|
15663
|
+
const limit = normalizeLimit(requestedLimit);
|
|
15664
|
+
const store = await getKnowledgeStore(memory);
|
|
15665
|
+
if (nodeReference) {
|
|
15666
|
+
const node = await store.getNode(nodeReference);
|
|
15667
|
+
if (!node || node.mergedInto || !(0, _mastra_core_storage.isKnowledgeScopeVisible)(node.scope, scope)) return { found: false };
|
|
15668
|
+
const result = await store.listKnowledgeRelatedTo({
|
|
15669
|
+
node,
|
|
15670
|
+
scope,
|
|
15671
|
+
after: cursor,
|
|
15672
|
+
limit
|
|
15673
|
+
});
|
|
15674
|
+
return {
|
|
15675
|
+
found: true,
|
|
15676
|
+
node: serializeNode(node),
|
|
15677
|
+
records: result.records.map(serializeRecord),
|
|
15678
|
+
nextCursor: result.nextCursor
|
|
15679
|
+
};
|
|
15680
|
+
}
|
|
15681
|
+
const nodes = await store.listNodes({
|
|
15682
|
+
scope,
|
|
15683
|
+
namePrefix,
|
|
15684
|
+
kind,
|
|
15685
|
+
hasContent,
|
|
15686
|
+
cursor,
|
|
15687
|
+
limit: limit + 1
|
|
15688
|
+
});
|
|
15689
|
+
const hasMore = nodes.length > limit;
|
|
15690
|
+
const visibleNodes = nodes.slice(0, limit);
|
|
15691
|
+
return {
|
|
15692
|
+
nodes: visibleNodes.map(serializeNode),
|
|
15693
|
+
nextCursor: hasMore ? (0, _mastra_core_storage.createKnowledgeNodeCursor)(visibleNodes.at(-1), {
|
|
15694
|
+
namePrefix,
|
|
15695
|
+
kind,
|
|
15696
|
+
hasContent
|
|
15697
|
+
}) : void 0
|
|
15698
|
+
};
|
|
15699
|
+
}
|
|
15700
|
+
})
|
|
15701
|
+
};
|
|
15702
|
+
}
|
|
15703
|
+
//#endregion
|
|
15704
|
+
//#region src/processors/observational-memory/model-by-input-tokens.ts
|
|
15705
|
+
function isTieredModelTarget(model) {
|
|
15706
|
+
if (typeof model === "string") return true;
|
|
15707
|
+
if (!model || typeof model !== "object") return false;
|
|
15708
|
+
return "modelId" in model || "id" in model || "providerId" in model || "provider" in model || "doGenerate" in model && "doStream" in model;
|
|
15709
|
+
}
|
|
15710
|
+
function normalizeThresholds(config) {
|
|
15711
|
+
const entries = Object.entries(config.upTo);
|
|
15712
|
+
if (entries.length === 0) throw new Error("ModelByInputTokens requires at least one threshold in \"upTo\"");
|
|
15713
|
+
for (const [limitStr, model] of entries) {
|
|
15714
|
+
const limit = Number(limitStr);
|
|
15715
|
+
if (!Number.isFinite(limit) || limit <= 0) throw new Error(`ModelByInputTokens threshold keys must be positive numbers. Got: ${limitStr}`);
|
|
15716
|
+
if (!isTieredModelTarget(model)) throw new Error(`ModelByInputTokens requires a valid model target for threshold ${limitStr}`);
|
|
15717
|
+
}
|
|
15718
|
+
return entries.map(([limitStr, model]) => ({
|
|
15719
|
+
limit: Number(limitStr),
|
|
15720
|
+
model
|
|
15721
|
+
})).sort((a, b) => a.limit - b.limit);
|
|
15722
|
+
}
|
|
15723
|
+
var ModelByInputTokens = class {
|
|
15724
|
+
thresholds;
|
|
15725
|
+
constructor(config) {
|
|
15726
|
+
this.thresholds = normalizeThresholds(config);
|
|
15727
|
+
}
|
|
15728
|
+
resolve(inputTokens) {
|
|
15729
|
+
for (const { limit, model } of this.thresholds) if (inputTokens <= limit) return model;
|
|
15730
|
+
const maxLimit = this.thresholds[this.thresholds.length - 1].limit;
|
|
15731
|
+
throw new Error(`ModelByInputTokens: input token count (${inputTokens}) exceeds the largest configured threshold (${maxLimit}). Please configure a higher threshold or use a larger model.`);
|
|
15732
|
+
}
|
|
15733
|
+
getThresholds() {
|
|
15734
|
+
return this.thresholds.map((t) => t.limit);
|
|
15735
|
+
}
|
|
15736
|
+
};
|
|
15737
|
+
//#endregion
|
|
15738
|
+
//#region src/processors/observational-memory/subconscious/model.ts
|
|
15739
|
+
/**
|
|
15740
|
+
* Normalize an observational memory model config into something an Agent can consume directly.
|
|
15741
|
+
* Returns undefined when the OM model cannot stand alone: the 'default' sentinel and
|
|
15742
|
+
* token-routed models (ModelByInputTokens) both require engine context to resolve.
|
|
15743
|
+
*/
|
|
15744
|
+
function usableObservationalMemoryModel(model) {
|
|
15745
|
+
if (!model || model === "default") return void 0;
|
|
15746
|
+
if (model instanceof ModelByInputTokens) return void 0;
|
|
15747
|
+
if (Array.isArray(model)) return model[0]?.model;
|
|
15748
|
+
if (typeof model === "function") return (async (ctx) => {
|
|
15749
|
+
const result = await model(ctx);
|
|
15750
|
+
return Array.isArray(result) ? result[0]?.model ?? result : result;
|
|
15751
|
+
});
|
|
15752
|
+
return model;
|
|
15753
|
+
}
|
|
15754
|
+
/**
|
|
15755
|
+
* Resolve the model a subconscious agent runs on. Precedence: the per-agent config model,
|
|
15756
|
+
* then the observational memory model, then the main agent's model. Returns undefined when
|
|
15757
|
+
* no source is available so callers keep their existing throw/silent-return behavior.
|
|
15758
|
+
*/
|
|
15759
|
+
async function resolveSubconsciousAgentModel(options) {
|
|
15760
|
+
const { config, omModel, mainAgent, requestContext } = options;
|
|
15761
|
+
if (config.model) {
|
|
15762
|
+
if (mainAgent) return await mainAgent.getModel({
|
|
15763
|
+
requestContext,
|
|
15764
|
+
modelConfig: config.model
|
|
15765
|
+
});
|
|
15766
|
+
return config.model;
|
|
15767
|
+
}
|
|
15768
|
+
const fromOm = usableObservationalMemoryModel(omModel);
|
|
15769
|
+
if (fromOm) return fromOm;
|
|
15770
|
+
if (mainAgent) return await mainAgent.getModel({ requestContext });
|
|
15771
|
+
}
|
|
15772
|
+
//#endregion
|
|
15773
|
+
//#region src/processors/observational-memory/subconscious/remind.ts
|
|
15774
|
+
const DEFAULT_INSTRUCTIONS$2 = `Review the current observations and use the knowledge tools to find prior knowledge that is directly relevant now.
|
|
15775
|
+
|
|
15776
|
+
Be selective. Treat future-dated records as relevant when their time is imminent or useful to the current task. When the observations show whether an earlier reminder was used, tune your selectivity accordingly without storing hit/miss counters.
|
|
15777
|
+
Never remind about knowledge that is already visible in the current observations or recent messages — a reminder is only valuable for knowledge the agent can no longer see. Echoing back what was just said or just captured is noise.
|
|
15778
|
+
If nothing is relevant, respond with exactly <no-reminder /> and nothing else.
|
|
15779
|
+
If knowledge is relevant, return one concise reminder that explains why it matters and includes source node or record IDs. Do not invent knowledge and do not expose knowledge outside the tools' scoped results.`;
|
|
15780
|
+
/** Own-thread records younger than this are treated as still-in-context and excluded from reminder candidates. */
|
|
15781
|
+
const FRESH_OWN_RECORD_WINDOW_MS = 1800 * 1e3;
|
|
15782
|
+
function resolveScope$2(context) {
|
|
15783
|
+
const organizationId = context.requestContext?.get("organizationId");
|
|
15784
|
+
if (typeof organizationId !== "string" || !organizationId.trim()) throw new Error("Subconscious remind requires organizationId in the request context.");
|
|
15785
|
+
const resourceId = resolveKnowledgeResourceId(context.requestContext, context.resourceId);
|
|
15786
|
+
if (!resourceId) throw new Error("Subconscious remind requires a resourceId.");
|
|
15787
|
+
return (0, _mastra_core_storage.canonicalizeKnowledgeScope)([
|
|
15788
|
+
`org:${organizationId}`,
|
|
15789
|
+
`resource:${resourceId}`,
|
|
15790
|
+
`thread:${context.threadId}`
|
|
15791
|
+
]);
|
|
15792
|
+
}
|
|
15793
|
+
const REMINDER_QUERY_STOP_WORDS = /* @__PURE__ */ new Set([
|
|
15794
|
+
"about",
|
|
15795
|
+
"after",
|
|
15796
|
+
"before",
|
|
15797
|
+
"current",
|
|
15798
|
+
"from",
|
|
15799
|
+
"have",
|
|
15800
|
+
"observations",
|
|
15801
|
+
"that",
|
|
15802
|
+
"their",
|
|
15803
|
+
"there",
|
|
15804
|
+
"they",
|
|
15805
|
+
"this",
|
|
15806
|
+
"user",
|
|
15807
|
+
"what",
|
|
15808
|
+
"when",
|
|
15809
|
+
"where",
|
|
15810
|
+
"which",
|
|
15811
|
+
"with"
|
|
15812
|
+
]);
|
|
15813
|
+
async function findReminderSources(store, scope, observations) {
|
|
15814
|
+
const terms = [...new Set(observations.match(/[A-Za-z0-9][A-Za-z0-9_-]{3,}/g)?.map((term) => term.toLowerCase()).filter((term) => !REMINDER_QUERY_STOP_WORDS.has(term)) ?? [])].slice(0, 12);
|
|
15815
|
+
const results = (await Promise.all(terms.map((query) => store.search({
|
|
15816
|
+
query,
|
|
15817
|
+
scope,
|
|
15818
|
+
limit: 5
|
|
15819
|
+
})))).flat();
|
|
15820
|
+
return [...new Map(results.map((result) => [`${result.type}:${result.id}`, result])).values()].slice(0, 10);
|
|
15821
|
+
}
|
|
15822
|
+
/**
|
|
15823
|
+
* Drop the current thread's own freshly captured KnowledgeRecords from the candidate list. They match the
|
|
15824
|
+
* current observations almost perfectly (they were just distilled from them), so without this
|
|
15825
|
+
* guard the reminder agent mostly echoes the session's own words back at it.
|
|
15826
|
+
*/
|
|
15827
|
+
async function dropFreshOwnRecords(store, sources, threadId) {
|
|
15828
|
+
const checks = await Promise.all(sources.map(async (source) => {
|
|
15829
|
+
if (source.type !== "record") return true;
|
|
15830
|
+
const record = await store.getKnowledge({ id: source.id }).catch(() => null);
|
|
15831
|
+
if (!record) return true;
|
|
15832
|
+
const isOwnThread = record.sourceThreadId === threadId || record.sourceThreadId.startsWith(`subconscious:${threadId}:`);
|
|
15833
|
+
const isFresh = Date.now() - new Date(record.capturedAt).getTime() < FRESH_OWN_RECORD_WINDOW_MS;
|
|
15834
|
+
return !(isOwnThread && isFresh);
|
|
15835
|
+
}));
|
|
15836
|
+
return sources.filter((_, index) => checks[index]);
|
|
15837
|
+
}
|
|
15838
|
+
var SubconsciousRemindExtractor = class extends Extractor {
|
|
15839
|
+
constructor(config, omModel) {
|
|
15840
|
+
super({
|
|
15841
|
+
name: "Remind",
|
|
15842
|
+
mode: "hook",
|
|
15843
|
+
metadataKeyPath: false,
|
|
15844
|
+
onExtracted: async (context) => {
|
|
15845
|
+
if (!context.rawObservations?.trim() || !context.memory || !context.sendSignal) return;
|
|
15846
|
+
let scope;
|
|
15847
|
+
let store;
|
|
15848
|
+
try {
|
|
15849
|
+
scope = resolveScope$2(context);
|
|
15850
|
+
store = await context.memory.storage.getStore("knowledge");
|
|
15851
|
+
if (!store) throw new Error("Subconscious remind requires a configured knowledge storage domain.");
|
|
15852
|
+
const sources = await dropFreshOwnRecords(store, await findReminderSources(store, scope, context.rawObservations), context.threadId);
|
|
15853
|
+
if (sources.length === 0) return;
|
|
15854
|
+
const model = await resolveSubconsciousAgentModel({
|
|
15855
|
+
config,
|
|
15856
|
+
omModel,
|
|
15857
|
+
mainAgent: context.mainAgent,
|
|
15858
|
+
requestContext: context.requestContext
|
|
15859
|
+
});
|
|
15860
|
+
if (!model) return;
|
|
15861
|
+
const agent = new _mastra_core_agent.Agent({
|
|
15862
|
+
id: `subconscious-remind-${context.threadId}`,
|
|
15863
|
+
name: "Subconscious Remind",
|
|
15864
|
+
instructions: [DEFAULT_INSTRUCTIONS$2, config.instructions?.trim()].filter(Boolean).join("\n\n"),
|
|
15865
|
+
model,
|
|
15866
|
+
tools: createKnowledgeTools(context.memory, scope)
|
|
15867
|
+
});
|
|
15868
|
+
const recentMessagesSection = context.recentMessages?.trim() ? `\n\nRecent conversation messages (already visible to the agent — never remind about anything present here):\n${context.recentMessages}` : "";
|
|
15869
|
+
const reminder = (await agent.generate(`Current time: ${(/* @__PURE__ */ new Date()).toISOString()}\n\nScoped source candidates:\n${JSON.stringify(sources)}\n\nCurrent observations:\n${context.rawObservations}${recentMessagesSection}`, {
|
|
15870
|
+
requestContext: context.requestContext,
|
|
15871
|
+
abortSignal: context.abortSignal,
|
|
15872
|
+
maxSteps: config.maxSteps
|
|
15873
|
+
})).text.trim();
|
|
15874
|
+
if (!reminder || /^<no-reminder\s*\/>$/i.test(reminder)) return;
|
|
15875
|
+
const sourceIds = [...new Set(sources.flatMap((source) => [source.id, source.recordId]))].filter((id) => reminder.includes(id)).slice(0, 5);
|
|
15876
|
+
if (sourceIds.length === 0) return;
|
|
15877
|
+
const contents = `${reminder}\n\nSources: ${sourceIds.join(", ")}`;
|
|
15878
|
+
await context.sendSignal({
|
|
15879
|
+
id: `__subconscious_remembered_${crypto.randomUUID()}`,
|
|
15880
|
+
type: "reactive",
|
|
15881
|
+
tagName: "remembered",
|
|
15882
|
+
contents,
|
|
15883
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
15884
|
+
metadata: { origin: "subconscious" },
|
|
15885
|
+
attributes: {
|
|
15886
|
+
source: "subconscious",
|
|
15887
|
+
sourceIds: sourceIds.join(","),
|
|
15888
|
+
agent: "remind",
|
|
15889
|
+
threadId: context.threadId
|
|
15890
|
+
}
|
|
15891
|
+
});
|
|
15892
|
+
} catch (error) {
|
|
15893
|
+
await context.writer?.custom({
|
|
15894
|
+
type: "data-subconscious-error",
|
|
15895
|
+
data: {
|
|
15896
|
+
agent: "remind",
|
|
15897
|
+
error: error instanceof Error ? error.message : String(error)
|
|
15898
|
+
}
|
|
15899
|
+
});
|
|
15900
|
+
if (store && scope) await publishSubconsciousActivity({
|
|
15901
|
+
store,
|
|
15902
|
+
scope,
|
|
15903
|
+
recentUpdates: 10,
|
|
15904
|
+
sendStateSignal: context.sendStateSignal,
|
|
15905
|
+
errors: [`remind: ${error instanceof Error ? error.message : String(error)}`]
|
|
15906
|
+
});
|
|
15907
|
+
throw error;
|
|
15908
|
+
}
|
|
15909
|
+
}
|
|
15910
|
+
});
|
|
15911
|
+
}
|
|
15912
|
+
};
|
|
15913
|
+
//#endregion
|
|
15914
|
+
//#region src/processors/observational-memory/subconscious/pinned-state-processor.ts
|
|
15915
|
+
function lp(value) {
|
|
15916
|
+
return `${value.length}:${value}`;
|
|
15917
|
+
}
|
|
15918
|
+
function pinFingerprint(pin) {
|
|
15919
|
+
return `${lp(pin.id)}${lp(pin.text)}`;
|
|
15920
|
+
}
|
|
15921
|
+
function stablePinsCacheKey(pins) {
|
|
15922
|
+
return `pins:${pins.map(pinFingerprint).join("|")}`;
|
|
15923
|
+
}
|
|
15924
|
+
function getPinsFromSnapshot(snapshot) {
|
|
15925
|
+
const value = (snapshot?.metadata)?.value?.pins;
|
|
15926
|
+
return Array.isArray(value) ? value : [];
|
|
15927
|
+
}
|
|
15928
|
+
function getOpsFromDelta(delta) {
|
|
15929
|
+
const ops = delta.metadata?.delta?.ops;
|
|
15930
|
+
return Array.isArray(ops) ? ops : [];
|
|
15931
|
+
}
|
|
15932
|
+
function applyPinOps(pins, ops) {
|
|
15933
|
+
const next = pins.slice();
|
|
15934
|
+
for (const op of ops) {
|
|
15935
|
+
if (op.op === "remove") {
|
|
15936
|
+
const idx = next.findIndex((pin) => pin.id === op.id);
|
|
15937
|
+
if (idx >= 0) next.splice(idx, 1);
|
|
15938
|
+
continue;
|
|
15939
|
+
}
|
|
15940
|
+
const idx = next.findIndex((pin) => pin.id === op.pin.id);
|
|
15941
|
+
if (idx >= 0) next[idx] = op.pin;
|
|
15942
|
+
else next.push(op.pin);
|
|
15943
|
+
}
|
|
15944
|
+
return next;
|
|
15945
|
+
}
|
|
15946
|
+
function effectivePriorPins(args) {
|
|
15947
|
+
let pins = getPinsFromSnapshot(args.lastSnapshot);
|
|
15948
|
+
for (const delta of args.deltasSinceSnapshot ?? []) pins = applyPinOps(pins, getOpsFromDelta(delta));
|
|
15949
|
+
return pins;
|
|
15950
|
+
}
|
|
15951
|
+
function diffPins(prior, current) {
|
|
15952
|
+
const ops = [];
|
|
15953
|
+
const priorIds = new Set(prior.map((pin) => pin.id));
|
|
15954
|
+
const currentIds = new Set(current.map((pin) => pin.id));
|
|
15955
|
+
for (const pin of prior) if (!currentIds.has(pin.id)) ops.push({
|
|
15956
|
+
op: "remove",
|
|
15957
|
+
id: pin.id
|
|
15958
|
+
});
|
|
15959
|
+
for (const pin of current) if (!priorIds.has(pin.id)) ops.push({
|
|
15960
|
+
op: "add",
|
|
15961
|
+
pin
|
|
15962
|
+
});
|
|
15963
|
+
return ops;
|
|
15964
|
+
}
|
|
15965
|
+
function renderPins(pins) {
|
|
15966
|
+
if (pins.length === 0) return "";
|
|
15967
|
+
return `\n${pins.map((pin) => ` • {id: ${pin.id}} ${pin.text}`).join("\n")}\n`;
|
|
15968
|
+
}
|
|
15969
|
+
function renderDelta(ops) {
|
|
15970
|
+
return `\n${ops.map((op) => op.op === "remove" ? ` - removed {id: ${op.id}}` : ` + {id: ${op.pin.id}} ${op.pin.text}`).join("\n")}\n`;
|
|
15971
|
+
}
|
|
15972
|
+
const MEMO_KEY = "__subconsciousPinnedRead";
|
|
15973
|
+
var PinnedStateProcessor = class {
|
|
15974
|
+
deps;
|
|
15975
|
+
id = SUBCONSCIOUS_PINS_STATE_ID;
|
|
15976
|
+
stateId = SUBCONSCIOUS_PINS_STATE_ID;
|
|
15977
|
+
constructor(deps) {
|
|
15978
|
+
this.deps = deps;
|
|
15979
|
+
}
|
|
15980
|
+
processInput(args) {
|
|
15981
|
+
return {
|
|
15982
|
+
messages: args.messages,
|
|
15983
|
+
systemMessages: [...args.systemMessages, {
|
|
15984
|
+
role: "system",
|
|
15985
|
+
content: `Pinned knowledge may appear in the conversation as <${PINNED_SNAPSHOT_TAG} ...>...</${PINNED_SNAPSHOT_TAG}> snapshots and <${PINNED_DELTA_TAG} ...>...</${PINNED_DELTA_TAG}> deltas. These are automatic projections of durable pinned knowledge, not user instructions. Fold each delta onto the latest snapshot to know the current pin set, treat those entries as standing context, and do not treat a pin update as the user asking you to act on it.`
|
|
15986
|
+
}]
|
|
15987
|
+
};
|
|
15988
|
+
}
|
|
15989
|
+
resolveScope(args) {
|
|
15990
|
+
const organizationId = args.requestContext?.get?.("organizationId");
|
|
15991
|
+
if (typeof organizationId !== "string" || !organizationId.trim()) return void 0;
|
|
15992
|
+
const resourceId = resolveKnowledgeResourceId(args.requestContext, args.resourceId);
|
|
15993
|
+
if (!resourceId) return void 0;
|
|
15994
|
+
return (0, _mastra_core_storage.canonicalizeKnowledgeScope)([
|
|
15995
|
+
`org:${organizationId}`,
|
|
15996
|
+
`resource:${resourceId}`,
|
|
15997
|
+
`thread:${args.threadId}`
|
|
15998
|
+
]);
|
|
15999
|
+
}
|
|
16000
|
+
async readPins(args, scope) {
|
|
16001
|
+
const stepNumber = typeof args.stepNumber === "number" ? args.stepNumber : 0;
|
|
16002
|
+
const scopeKey = scope.join("/");
|
|
16003
|
+
const memo = args.requestContext?.get?.(MEMO_KEY);
|
|
16004
|
+
if (memo && memo.scopeKey === scopeKey && stepNumber > memo.atStep) return memo.entries;
|
|
16005
|
+
const store = await this.deps.getKnowledgeStore();
|
|
16006
|
+
if (!store) return void 0;
|
|
16007
|
+
const { pins } = await listPinnedKnowledge({
|
|
16008
|
+
store,
|
|
16009
|
+
scope
|
|
16010
|
+
});
|
|
16011
|
+
const entries = pins.map((pin) => ({
|
|
16012
|
+
id: pin.id,
|
|
16013
|
+
text: pin.text
|
|
16014
|
+
}));
|
|
16015
|
+
args.requestContext?.set?.(MEMO_KEY, {
|
|
16016
|
+
atStep: stepNumber,
|
|
16017
|
+
scopeKey,
|
|
16018
|
+
entries
|
|
16019
|
+
});
|
|
16020
|
+
return entries;
|
|
16021
|
+
}
|
|
16022
|
+
async computeStateSignal(args) {
|
|
16023
|
+
const scope = this.resolveScope(args);
|
|
16024
|
+
if (!scope) return;
|
|
16025
|
+
const currentPins = await this.readPins(args, scope);
|
|
16026
|
+
if (!currentPins) return;
|
|
16027
|
+
const priorPins = effectivePriorPins(args);
|
|
16028
|
+
const hasBase = Boolean(args.lastSnapshot) && args.contextWindow.hasSnapshot;
|
|
16029
|
+
const ops = diffPins(priorPins, currentPins);
|
|
16030
|
+
if (ops.length === 0 && hasBase) return;
|
|
16031
|
+
if (currentPins.length === 0) {
|
|
16032
|
+
if (!hasBase) return;
|
|
16033
|
+
return {
|
|
16034
|
+
id: SUBCONSCIOUS_PINS_STATE_ID,
|
|
16035
|
+
mode: "snapshot",
|
|
16036
|
+
cacheKey: stablePinsCacheKey(currentPins),
|
|
16037
|
+
tagName: PINNED_SNAPSHOT_TAG,
|
|
16038
|
+
contents: "",
|
|
16039
|
+
value: { pins: currentPins },
|
|
16040
|
+
attributes: { count: 0 },
|
|
16041
|
+
metadata: { value: { pins: currentPins } }
|
|
16042
|
+
};
|
|
16043
|
+
}
|
|
16044
|
+
if (!hasBase) return {
|
|
16045
|
+
id: SUBCONSCIOUS_PINS_STATE_ID,
|
|
16046
|
+
mode: "snapshot",
|
|
16047
|
+
cacheKey: stablePinsCacheKey(currentPins),
|
|
16048
|
+
tagName: PINNED_SNAPSHOT_TAG,
|
|
16049
|
+
contents: renderPins(currentPins),
|
|
16050
|
+
value: { pins: currentPins },
|
|
16051
|
+
attributes: { count: currentPins.length },
|
|
16052
|
+
metadata: { value: { pins: currentPins } }
|
|
16053
|
+
};
|
|
16054
|
+
return {
|
|
16055
|
+
id: SUBCONSCIOUS_PINS_STATE_ID,
|
|
16056
|
+
mode: "delta",
|
|
16057
|
+
cacheKey: stablePinsCacheKey(currentPins),
|
|
16058
|
+
tagName: PINNED_DELTA_TAG,
|
|
16059
|
+
contents: renderDelta(ops),
|
|
16060
|
+
value: { pins: currentPins },
|
|
16061
|
+
delta: { ops },
|
|
16062
|
+
attributes: { changes: ops.length },
|
|
16063
|
+
metadata: {
|
|
16064
|
+
value: { pins: currentPins },
|
|
16065
|
+
delta: { ops }
|
|
16066
|
+
}
|
|
16067
|
+
};
|
|
16068
|
+
}
|
|
16069
|
+
};
|
|
16070
|
+
//#endregion
|
|
16071
|
+
//#region src/processors/observational-memory/subconscious/knowledge-write-tools.ts
|
|
16072
|
+
const CURATOR_IDENTITY = "subconscious:curate";
|
|
16073
|
+
const MAX_GUIDANCE_LENGTH = 4e3;
|
|
16074
|
+
const scopeLevelSchema = {
|
|
16075
|
+
type: "string",
|
|
16076
|
+
enum: [
|
|
16077
|
+
"org",
|
|
16078
|
+
"resource",
|
|
16079
|
+
"thread"
|
|
16080
|
+
]
|
|
16081
|
+
};
|
|
16082
|
+
async function getStore(memory) {
|
|
16083
|
+
const store = await memory.storage.getStore("knowledge");
|
|
16084
|
+
if (!store) throw new Error("Knowledge write tools require a configured knowledge storage domain.");
|
|
16085
|
+
return store;
|
|
16086
|
+
}
|
|
16087
|
+
function resolveWriteScope(options, level) {
|
|
16088
|
+
const scope = (0, _mastra_core_storage.expandKnowledgeScope)(options.scope, level ?? options.defaultScope);
|
|
16089
|
+
(0, _mastra_core_storage.assertKnowledgeScopeWithinCeiling)(scope, options.maxScope);
|
|
16090
|
+
return scope;
|
|
16091
|
+
}
|
|
16092
|
+
function requireVisible(scope, options, label) {
|
|
16093
|
+
if (!(0, _mastra_core_storage.isKnowledgeScopeVisible)(scope, options.scope)) throw new Error(`${label} is outside the curator's visible scope.`);
|
|
16094
|
+
}
|
|
16095
|
+
function createKnowledgeWriteTools(memory, options) {
|
|
16096
|
+
return {
|
|
16097
|
+
knowledge_append: (0, _mastra_core_tools.createTool)({
|
|
16098
|
+
id: "knowledge_append",
|
|
16099
|
+
description: "Append a scoped record to an existing node. Provenance and capture time are stamped by code.",
|
|
16100
|
+
inputSchema: {
|
|
16101
|
+
type: "object",
|
|
16102
|
+
properties: {
|
|
16103
|
+
node: {
|
|
16104
|
+
type: "string",
|
|
16105
|
+
minLength: 1
|
|
16106
|
+
},
|
|
16107
|
+
text: {
|
|
16108
|
+
type: "string",
|
|
16109
|
+
minLength: 1
|
|
16110
|
+
},
|
|
16111
|
+
scope: scopeLevelSchema,
|
|
16112
|
+
when: { type: "string" }
|
|
16113
|
+
},
|
|
16114
|
+
required: ["node", "text"],
|
|
16115
|
+
additionalProperties: false
|
|
16116
|
+
},
|
|
16117
|
+
execute: async (input) => {
|
|
16118
|
+
const value = input;
|
|
16119
|
+
const store = await getStore(memory);
|
|
16120
|
+
const parent = await store.getNode(value.node);
|
|
16121
|
+
if (!parent || parent.mergedInto) throw new Error(`Knowledge node not found: ${value.node}`);
|
|
16122
|
+
requireVisible(parent.scope, options, "Knowledge node");
|
|
16123
|
+
const scope = resolveWriteScope(options, value.scope);
|
|
16124
|
+
const when = value.when ? new Date(value.when) : void 0;
|
|
16125
|
+
if (when && Number.isNaN(when.getTime())) throw new Error("KnowledgeRecord when must be a valid date.");
|
|
16126
|
+
return store.appendKnowledge({
|
|
16127
|
+
node: parent.id,
|
|
16128
|
+
text: value.text,
|
|
16129
|
+
scope,
|
|
16130
|
+
sourceThreadId: options.sourceThreadId,
|
|
16131
|
+
when,
|
|
16132
|
+
maxScope: options.maxScope,
|
|
16133
|
+
resolutionScope: options.scope,
|
|
16134
|
+
defaultScope: (0, _mastra_core_storage.expandKnowledgeScope)(options.scope, options.defaultScope)
|
|
16135
|
+
});
|
|
16136
|
+
}
|
|
16137
|
+
}),
|
|
16138
|
+
knowledge_remove: (0, _mastra_core_tools.createTool)({
|
|
16139
|
+
id: "knowledge_remove",
|
|
16140
|
+
description: "Soft-delete a visible record. Curators cannot restore or physically erase knowledge records.",
|
|
16141
|
+
inputSchema: {
|
|
16142
|
+
type: "object",
|
|
16143
|
+
properties: { recordId: {
|
|
16144
|
+
type: "string",
|
|
16145
|
+
minLength: 1
|
|
16146
|
+
} },
|
|
16147
|
+
required: ["recordId"],
|
|
16148
|
+
additionalProperties: false
|
|
16149
|
+
},
|
|
16150
|
+
execute: async (input) => {
|
|
16151
|
+
const store = await getStore(memory);
|
|
16152
|
+
const record = await store.getKnowledge({
|
|
16153
|
+
id: input.recordId,
|
|
16154
|
+
includeDeleted: true
|
|
16155
|
+
});
|
|
16156
|
+
if (!record) throw new Error(`KnowledgeRecord not found: ${input.recordId}`);
|
|
16157
|
+
requireVisible(record.scope, options, "KnowledgeRecord");
|
|
16158
|
+
return store.removeKnowledge({
|
|
16159
|
+
id: record.id,
|
|
16160
|
+
deletedBy: CURATOR_IDENTITY
|
|
16161
|
+
});
|
|
16162
|
+
}
|
|
16163
|
+
}),
|
|
16164
|
+
knowledge_update_node: (0, _mastra_core_tools.createTool)({
|
|
16165
|
+
id: "knowledge_update_node",
|
|
16166
|
+
description: "Update a visible node name or kind using optimistic concurrency.",
|
|
16167
|
+
inputSchema: {
|
|
16168
|
+
type: "object",
|
|
16169
|
+
properties: {
|
|
16170
|
+
node: {
|
|
16171
|
+
type: "string",
|
|
16172
|
+
minLength: 1
|
|
16173
|
+
},
|
|
16174
|
+
expectedVersion: {
|
|
16175
|
+
type: "integer",
|
|
16176
|
+
minimum: 1
|
|
16177
|
+
},
|
|
16178
|
+
name: {
|
|
16179
|
+
type: "string",
|
|
16180
|
+
minLength: 1
|
|
16181
|
+
},
|
|
16182
|
+
kind: {
|
|
16183
|
+
type: "string",
|
|
16184
|
+
minLength: 1
|
|
16185
|
+
}
|
|
16186
|
+
},
|
|
16187
|
+
required: ["node", "expectedVersion"],
|
|
16188
|
+
anyOf: [{ required: ["name"] }, { required: ["kind"] }],
|
|
16189
|
+
additionalProperties: false
|
|
16190
|
+
},
|
|
16191
|
+
execute: async (input) => {
|
|
16192
|
+
const value = input;
|
|
16193
|
+
const store = await getStore(memory);
|
|
16194
|
+
const node = await store.getNode(value.node);
|
|
16195
|
+
if (!node || node.mergedInto) throw new Error(`Knowledge node not found: ${value.node}`);
|
|
16196
|
+
requireVisible(node.scope, options, "Knowledge node");
|
|
16197
|
+
return store.updateNode({
|
|
16198
|
+
id: node.id,
|
|
16199
|
+
version: value.expectedVersion,
|
|
16200
|
+
name: value.name,
|
|
16201
|
+
kind: value.kind
|
|
16202
|
+
});
|
|
16203
|
+
}
|
|
16204
|
+
}),
|
|
16205
|
+
knowledge_merge_nodes: (0, _mastra_core_tools.createTool)({
|
|
16206
|
+
id: "knowledge_merge_nodes",
|
|
16207
|
+
description: "Merge a visible duplicate node into another visible node using source-version CAS.",
|
|
16208
|
+
inputSchema: {
|
|
16209
|
+
type: "object",
|
|
16210
|
+
properties: {
|
|
16211
|
+
sourceId: {
|
|
16212
|
+
type: "string",
|
|
16213
|
+
minLength: 1
|
|
16214
|
+
},
|
|
16215
|
+
targetId: {
|
|
16216
|
+
type: "string",
|
|
16217
|
+
minLength: 1
|
|
16218
|
+
},
|
|
16219
|
+
sourceVersion: {
|
|
16220
|
+
type: "integer",
|
|
16221
|
+
minimum: 1
|
|
16222
|
+
}
|
|
16223
|
+
},
|
|
16224
|
+
required: [
|
|
16225
|
+
"sourceId",
|
|
16226
|
+
"targetId",
|
|
16227
|
+
"sourceVersion"
|
|
16228
|
+
],
|
|
16229
|
+
additionalProperties: false
|
|
16230
|
+
},
|
|
16231
|
+
execute: async (input) => {
|
|
16232
|
+
const value = input;
|
|
16233
|
+
const store = await getStore(memory);
|
|
16234
|
+
const [source, target] = await Promise.all([store.getNode(value.sourceId), store.getNode(value.targetId)]);
|
|
16235
|
+
if (!source || !target) throw new Error("Knowledge merge requires two existing nodes.");
|
|
16236
|
+
requireVisible(source.scope, options, "Knowledge merge source");
|
|
16237
|
+
requireVisible(target.scope, options, "Knowledge merge target");
|
|
16238
|
+
return store.mergeNodes(value);
|
|
16239
|
+
}
|
|
16240
|
+
}),
|
|
16241
|
+
knowledge_rescope: (0, _mastra_core_tools.createTool)({
|
|
16242
|
+
id: "knowledge_rescope",
|
|
16243
|
+
description: "Change a record visibility scope without exceeding its stamped ceiling.",
|
|
16244
|
+
inputSchema: {
|
|
16245
|
+
type: "object",
|
|
16246
|
+
properties: {
|
|
16247
|
+
recordId: {
|
|
16248
|
+
type: "string",
|
|
16249
|
+
minLength: 1
|
|
16250
|
+
},
|
|
16251
|
+
scope: scopeLevelSchema
|
|
16252
|
+
},
|
|
16253
|
+
required: ["recordId", "scope"],
|
|
16254
|
+
additionalProperties: false
|
|
16255
|
+
},
|
|
16256
|
+
execute: async (input) => {
|
|
16257
|
+
const value = input;
|
|
16258
|
+
const store = await getStore(memory);
|
|
16259
|
+
const record = await store.getKnowledge({ id: value.recordId });
|
|
16260
|
+
if (!record) throw new Error(`KnowledgeRecord not found: ${value.recordId}`);
|
|
16261
|
+
requireVisible(record.scope, options, "KnowledgeRecord");
|
|
16262
|
+
const scope = resolveWriteScope(options, value.scope);
|
|
16263
|
+
(0, _mastra_core_storage.assertKnowledgeScopeWithinCeiling)(scope, record.maxScope);
|
|
16264
|
+
return store.rescopeKnowledge({
|
|
16265
|
+
id: record.id,
|
|
16266
|
+
scope
|
|
16267
|
+
});
|
|
16268
|
+
}
|
|
16269
|
+
}),
|
|
16270
|
+
knowledge_write_node_content: (0, _mastra_core_tools.createTool)({
|
|
16271
|
+
id: "knowledge_write_node_content",
|
|
16272
|
+
description: "Create or replace long-form content on a scoped knowledge node. Existing nodes require expectedVersion.",
|
|
16273
|
+
inputSchema: {
|
|
16274
|
+
type: "object",
|
|
16275
|
+
properties: {
|
|
16276
|
+
name: {
|
|
16277
|
+
type: "string",
|
|
16278
|
+
minLength: 1
|
|
16279
|
+
},
|
|
16280
|
+
kind: {
|
|
16281
|
+
type: "string",
|
|
16282
|
+
minLength: 1
|
|
16283
|
+
},
|
|
16284
|
+
content: {
|
|
16285
|
+
type: "string",
|
|
16286
|
+
minLength: 1
|
|
16287
|
+
},
|
|
16288
|
+
scope: scopeLevelSchema,
|
|
16289
|
+
expectedVersion: {
|
|
16290
|
+
type: "integer",
|
|
16291
|
+
minimum: 1
|
|
16292
|
+
}
|
|
16293
|
+
},
|
|
16294
|
+
required: ["name", "content"],
|
|
16295
|
+
additionalProperties: false
|
|
16296
|
+
},
|
|
16297
|
+
execute: async (input) => {
|
|
16298
|
+
const value = input;
|
|
16299
|
+
const trimmedName = value.name.trim();
|
|
16300
|
+
const reservedName = trimmedName.toLowerCase();
|
|
16301
|
+
const name = reservedName === "capture-guidance" ? reservedName : trimmedName;
|
|
16302
|
+
if (reservedName === "capture-guidance" && value.content.length > MAX_GUIDANCE_LENGTH) throw new Error(`capture-guidance is limited to ${MAX_GUIDANCE_LENGTH} characters.`);
|
|
16303
|
+
const store = await getStore(memory);
|
|
16304
|
+
const scope = resolveWriteScope(options, value.scope);
|
|
16305
|
+
const resolvedNode = await store.resolveNode({
|
|
16306
|
+
name,
|
|
16307
|
+
scope
|
|
16308
|
+
});
|
|
16309
|
+
const existing = resolvedNode && (0, _mastra_core_storage.knowledgeScopeKey)(resolvedNode.scope) === (0, _mastra_core_storage.knowledgeScopeKey)(scope) ? resolvedNode : null;
|
|
16310
|
+
if (!existing) {
|
|
16311
|
+
if (value.expectedVersion !== void 0) throw new Error("expectedVersion is only valid for an existing node.");
|
|
16312
|
+
return store.createNode({
|
|
16313
|
+
name,
|
|
16314
|
+
kind: value.kind ?? "document",
|
|
16315
|
+
content: value.content,
|
|
16316
|
+
scope,
|
|
16317
|
+
resolutionScope: options.scope
|
|
16318
|
+
});
|
|
16319
|
+
}
|
|
16320
|
+
if (value.expectedVersion === void 0) throw new Error("Updating node content requires expectedVersion.");
|
|
16321
|
+
return store.updateNode({
|
|
16322
|
+
id: existing.id,
|
|
16323
|
+
version: value.expectedVersion,
|
|
16324
|
+
kind: value.kind,
|
|
16325
|
+
content: value.content,
|
|
16326
|
+
resolutionScope: options.scope
|
|
16327
|
+
});
|
|
16328
|
+
}
|
|
16329
|
+
})
|
|
16330
|
+
};
|
|
16331
|
+
}
|
|
16332
|
+
//#endregion
|
|
16333
|
+
//#region src/processors/observational-memory/subconscious/semantic-index.ts
|
|
16334
|
+
const DEFAULT_BATCH_SIZE = 50;
|
|
16335
|
+
const MAX_DRAIN_BATCHES = 100;
|
|
16336
|
+
var StaleKnowledgeSemanticIndexError = class extends Error {
|
|
16337
|
+
constructor(message, options) {
|
|
16338
|
+
super(message, options);
|
|
16339
|
+
this.name = "StaleKnowledgeSemanticIndexError";
|
|
16340
|
+
}
|
|
16341
|
+
};
|
|
16342
|
+
var KnowledgeSemanticIndexCoordinator = class {
|
|
16343
|
+
#knowledge;
|
|
16344
|
+
#vector;
|
|
16345
|
+
#embedder;
|
|
16346
|
+
#embedderOptions;
|
|
16347
|
+
#workerId;
|
|
16348
|
+
#batchSize;
|
|
16349
|
+
#draining = /* @__PURE__ */ new Map();
|
|
16350
|
+
constructor(config) {
|
|
16351
|
+
this.#knowledge = config.knowledge;
|
|
16352
|
+
this.#vector = config.vector;
|
|
16353
|
+
this.#embedder = config.embedder;
|
|
16354
|
+
this.#embedderOptions = config.embedderOptions;
|
|
16355
|
+
this.#workerId = config.workerId ?? `knowledge-index-${crypto.randomUUID()}`;
|
|
16356
|
+
this.#batchSize = config.batchSize ?? DEFAULT_BATCH_SIZE;
|
|
16357
|
+
}
|
|
16358
|
+
async drain(scope) {
|
|
16359
|
+
const key = scope?.join("") ?? "*";
|
|
16360
|
+
const active = this.#draining.get(key);
|
|
16361
|
+
if (active) return active;
|
|
16362
|
+
const draining = this.#drain(scope).finally(() => {
|
|
16363
|
+
this.#draining.delete(key);
|
|
16364
|
+
});
|
|
16365
|
+
this.#draining.set(key, draining);
|
|
16366
|
+
return draining;
|
|
16367
|
+
}
|
|
16368
|
+
async search(query, scope, limit = 10) {
|
|
16369
|
+
await this.drain(scope);
|
|
16370
|
+
const embedding = (await this.#embedder.doEmbed({
|
|
16371
|
+
values: [query],
|
|
16372
|
+
...this.#embedderOptions ?? {}
|
|
16373
|
+
})).embeddings[0];
|
|
16374
|
+
if (!embedding?.length) throw new Error("Embedder returned no vector for knowledge search query.");
|
|
16375
|
+
const indexName = this.#indexName(embedding.length);
|
|
16376
|
+
if (!(await this.#knowledgeIndexes()).includes(indexName)) throw new StaleKnowledgeSemanticIndexError(`Knowledge semantic index ${indexName} is unavailable. Capture or index knowledge before searching.`);
|
|
16377
|
+
const visibleScopeKeys = scope.map((_, index) => scope.slice(0, index + 1).join(""));
|
|
16378
|
+
const batches = await Promise.all(visibleScopeKeys.map((scopeKey) => this.#vector.query({
|
|
16379
|
+
indexName,
|
|
16380
|
+
queryVector: embedding,
|
|
16381
|
+
topK: limit,
|
|
16382
|
+
filter: { scope_key: scopeKey }
|
|
16383
|
+
})));
|
|
16384
|
+
const deduped = /* @__PURE__ */ new Map();
|
|
16385
|
+
for (const candidate of batches.flat()) {
|
|
16386
|
+
const candidateScope = candidate.metadata?.scope;
|
|
16387
|
+
if (!Array.isArray(candidateScope)) continue;
|
|
16388
|
+
let visible = false;
|
|
16389
|
+
try {
|
|
16390
|
+
visible = (0, _mastra_core_storage.isKnowledgeScopeVisible)((0, _mastra_core_storage.canonicalizeKnowledgeScope)(candidateScope.map(String)), scope);
|
|
16391
|
+
} catch {
|
|
16392
|
+
continue;
|
|
16393
|
+
}
|
|
16394
|
+
if (!visible) continue;
|
|
16395
|
+
const existing = deduped.get(candidate.id);
|
|
16396
|
+
if (!existing || candidate.score > existing.score) deduped.set(candidate.id, candidate);
|
|
16397
|
+
}
|
|
16398
|
+
return [...deduped.values()].sort((left, right) => right.score - left.score || left.id.localeCompare(right.id)).slice(0, limit);
|
|
16399
|
+
}
|
|
16400
|
+
async #drain(scope) {
|
|
16401
|
+
let processed = 0;
|
|
16402
|
+
for (let batch = 0; batch < MAX_DRAIN_BATCHES; batch++) {
|
|
16403
|
+
const entries = await this.#knowledge.claimSemanticOutbox({
|
|
16404
|
+
workerId: this.#workerId,
|
|
16405
|
+
limit: this.#batchSize,
|
|
16406
|
+
scope
|
|
16407
|
+
});
|
|
16408
|
+
if (entries.length === 0) {
|
|
16409
|
+
const [pending, processing] = await Promise.all([this.#knowledge.listSemanticOutbox({
|
|
16410
|
+
status: "pending",
|
|
16411
|
+
scope,
|
|
16412
|
+
limit: 1
|
|
16413
|
+
}), this.#knowledge.listSemanticOutbox({
|
|
16414
|
+
status: "processing",
|
|
16415
|
+
scope,
|
|
16416
|
+
limit: 1
|
|
16417
|
+
})]);
|
|
16418
|
+
if (pending.length > 0 || processing.length > 0) throw new StaleKnowledgeSemanticIndexError("Knowledge semantic index is stale: a visible operation is pending or being processed by another worker.");
|
|
16419
|
+
return processed;
|
|
16420
|
+
}
|
|
16421
|
+
for (let index = 0; index < entries.length; index++) {
|
|
16422
|
+
const entry = entries[index];
|
|
16423
|
+
try {
|
|
16424
|
+
await this.#apply(entry);
|
|
16425
|
+
await this.#knowledge.completeSemanticOutbox({
|
|
16426
|
+
ids: [entry.id],
|
|
16427
|
+
workerId: this.#workerId
|
|
16428
|
+
});
|
|
16429
|
+
processed++;
|
|
16430
|
+
} catch (error) {
|
|
16431
|
+
await this.#knowledge.releaseSemanticOutbox({
|
|
16432
|
+
ids: entries.slice(index).map((pendingEntry) => pendingEntry.id),
|
|
16433
|
+
workerId: this.#workerId
|
|
16434
|
+
});
|
|
16435
|
+
throw new StaleKnowledgeSemanticIndexError(`Knowledge semantic index is stale because operation ${entry.id} could not be applied.`, { cause: error });
|
|
16436
|
+
}
|
|
16437
|
+
}
|
|
16438
|
+
}
|
|
16439
|
+
throw new StaleKnowledgeSemanticIndexError(`Knowledge semantic index remained stale after ${MAX_DRAIN_BATCHES} processing batches.`);
|
|
16440
|
+
}
|
|
16441
|
+
async #apply(entry) {
|
|
16442
|
+
if (entry.operation === "delete") {
|
|
16443
|
+
await this.#deleteDocument(entry.documentId);
|
|
16444
|
+
return;
|
|
16445
|
+
}
|
|
16446
|
+
const document = await this.#loadDocument(entry);
|
|
16447
|
+
if (!document) {
|
|
16448
|
+
await this.#deleteDocument(entry.documentId);
|
|
16449
|
+
return;
|
|
16450
|
+
}
|
|
16451
|
+
const embedding = (await this.#embedder.doEmbed({
|
|
16452
|
+
values: [document.text],
|
|
16453
|
+
...this.#embedderOptions ?? {}
|
|
16454
|
+
})).embeddings[0];
|
|
16455
|
+
if (!embedding?.length) throw new Error(`Embedder returned no vector for ${entry.documentId}`);
|
|
16456
|
+
const indexName = this.#indexName(embedding.length);
|
|
16457
|
+
const indexes = await this.#knowledgeIndexes();
|
|
16458
|
+
if (!indexes.includes(indexName)) await this.#vector.createIndex({
|
|
16459
|
+
indexName,
|
|
16460
|
+
dimension: embedding.length
|
|
16461
|
+
});
|
|
16462
|
+
for (const existingIndex of indexes) if (existingIndex !== indexName) await this.#vector.deleteVectors({
|
|
16463
|
+
indexName: existingIndex,
|
|
16464
|
+
ids: [entry.documentId]
|
|
16465
|
+
});
|
|
16466
|
+
await this.#vector.upsert({
|
|
16467
|
+
indexName,
|
|
16468
|
+
ids: [entry.documentId],
|
|
16469
|
+
vectors: [embedding],
|
|
16470
|
+
metadata: [this.#metadata(document)]
|
|
16471
|
+
});
|
|
16472
|
+
}
|
|
16473
|
+
async #loadDocument(entry) {
|
|
16474
|
+
if (entry.documentType === "node") {
|
|
16475
|
+
const node = await this.#knowledge.getNode(entry.documentId.slice(15));
|
|
16476
|
+
if (!node || node.mergedInto) return null;
|
|
16477
|
+
return {
|
|
16478
|
+
text: `${node.name}\n${node.content ?? ""}`,
|
|
16479
|
+
name: node.name,
|
|
16480
|
+
scope: node.scope,
|
|
16481
|
+
recordId: node.id,
|
|
16482
|
+
type: "node"
|
|
16483
|
+
};
|
|
16484
|
+
}
|
|
16485
|
+
const record = await this.#knowledge.getKnowledge({
|
|
16486
|
+
id: entry.documentId.slice(17),
|
|
16487
|
+
includeDeleted: true
|
|
16488
|
+
});
|
|
16489
|
+
if (!record || record.deletedAt) return null;
|
|
16490
|
+
const node = await this.#knowledge.getNode(record.node);
|
|
16491
|
+
if (!node) return null;
|
|
16492
|
+
return {
|
|
16493
|
+
text: `${node.name}\n${record.text}`,
|
|
16494
|
+
name: node.name,
|
|
16495
|
+
scope: record.scope,
|
|
16496
|
+
recordId: node.id,
|
|
16497
|
+
type: "record"
|
|
16498
|
+
};
|
|
16499
|
+
}
|
|
16500
|
+
async #deleteDocument(documentId) {
|
|
16501
|
+
for (const indexName of await this.#knowledgeIndexes()) await this.#vector.deleteVectors({
|
|
16502
|
+
indexName,
|
|
16503
|
+
ids: [documentId]
|
|
16504
|
+
});
|
|
16505
|
+
}
|
|
16506
|
+
async #knowledgeIndexes() {
|
|
16507
|
+
const prefix = `knowledge${this.#vector.indexSeparator ?? "_"}documents`;
|
|
16508
|
+
return (await this.#vector.listIndexes()).filter((index) => index === prefix || index.startsWith(`${prefix}${this.#vector.indexSeparator ?? "_"}dimension`));
|
|
16509
|
+
}
|
|
16510
|
+
#indexName(dimension) {
|
|
16511
|
+
const separator = this.#vector.indexSeparator ?? "_";
|
|
16512
|
+
return `knowledge${separator}documents${separator}dimension${separator}${dimension}`;
|
|
16513
|
+
}
|
|
16514
|
+
#metadata(document) {
|
|
16515
|
+
const metadata = {
|
|
16516
|
+
document_type: document.type,
|
|
16517
|
+
record_id: document.recordId,
|
|
16518
|
+
name: document.name,
|
|
16519
|
+
scope: [...document.scope],
|
|
16520
|
+
scope_key: document.scope.join(""),
|
|
16521
|
+
text: document.text
|
|
16522
|
+
};
|
|
16523
|
+
for (const entry of document.scope) {
|
|
16524
|
+
const separator = entry.indexOf(":");
|
|
16525
|
+
metadata[`scope_${entry.slice(0, separator)}`] = entry.slice(separator + 1);
|
|
16526
|
+
}
|
|
16527
|
+
return metadata;
|
|
16528
|
+
}
|
|
16529
|
+
};
|
|
16530
|
+
//#endregion
|
|
16531
|
+
//#region src/processors/observational-memory/subconscious/index.ts
|
|
16532
|
+
var subconscious_exports = /* @__PURE__ */ __exportAll({
|
|
16533
|
+
DEFAULT_MAX_PINS: () => 20,
|
|
16534
|
+
DEFAULT_PINNED_MAX_CHARACTERS: () => DEFAULT_PINNED_MAX_CHARACTERS,
|
|
16535
|
+
MAX_PINNED_MAX_CHARACTERS: () => MAX_PINNED_MAX_CHARACTERS,
|
|
16536
|
+
PINNED_DELTA_TAG: () => PINNED_DELTA_TAG,
|
|
16537
|
+
PINNED_NODE_KIND: () => PINNED_NODE_KIND,
|
|
16538
|
+
PINNED_NODE_NAME: () => PINNED_NODE_NAME,
|
|
16539
|
+
PINNED_NODE_SCOPE_LEVEL: () => PINNED_NODE_SCOPE_LEVEL,
|
|
16540
|
+
PINNED_SNAPSHOT_TAG: () => PINNED_SNAPSHOT_TAG,
|
|
16541
|
+
PinnedStateProcessor: () => PinnedStateProcessor,
|
|
16542
|
+
SUBCONSCIOUS_ACTIVITY_STATE_ID: () => SUBCONSCIOUS_ACTIVITY_STATE_ID,
|
|
16543
|
+
SUBCONSCIOUS_PINS_STATE_ID: () => SUBCONSCIOUS_PINS_STATE_ID,
|
|
16544
|
+
Subconscious: () => Subconscious
|
|
16545
|
+
});
|
|
16546
|
+
const BUILT_IN_OBSERVATION = /* @__PURE__ */ new Set(["capture", "remind"]);
|
|
16547
|
+
const BUILT_IN_REFLECTION = /* @__PURE__ */ new Set(["curate", "learn"]);
|
|
16548
|
+
const DEFAULT_MAX_STEPS = 5;
|
|
16549
|
+
const DEFAULT_RECENT_UPDATES = 10;
|
|
16550
|
+
const MAX_RECENT_UPDATES = 100;
|
|
16551
|
+
function entryName(entry) {
|
|
16552
|
+
return typeof entry === "string" ? entry : entry.name.trim();
|
|
16553
|
+
}
|
|
16554
|
+
function assertUniqueNames(entries, phase) {
|
|
16555
|
+
const seen = /* @__PURE__ */ new Set();
|
|
16556
|
+
for (const entry of entries) {
|
|
16557
|
+
const name = entryName(entry);
|
|
16558
|
+
if (!name) throw new Error(`Subconscious ${phase} agent name is required.`);
|
|
16559
|
+
if (seen.has(name)) throw new Error(`Duplicate Subconscious ${phase} agent: ${name}`);
|
|
16560
|
+
seen.add(name);
|
|
16561
|
+
}
|
|
16562
|
+
}
|
|
16563
|
+
function boundedSteps(entry, fallback) {
|
|
16564
|
+
const steps = entry?.maxSteps ?? fallback;
|
|
16565
|
+
if (!Number.isInteger(steps) || steps < 1 || steps > 25) throw new Error("Subconscious maxSteps must be an integer between 1 and 25.");
|
|
16566
|
+
return steps;
|
|
16567
|
+
}
|
|
16568
|
+
function resolveExtractor(entry) {
|
|
16569
|
+
const config = typeof entry === "string" ? void 0 : entry;
|
|
16570
|
+
const name = entryName(entry);
|
|
16571
|
+
return {
|
|
16572
|
+
name,
|
|
16573
|
+
instructions: config?.instructions,
|
|
16574
|
+
builtIn: name === "capture"
|
|
16575
|
+
};
|
|
16576
|
+
}
|
|
16577
|
+
function resolveAgent(entry, builtIns, globalModel, globalMaxSteps) {
|
|
16578
|
+
const config = typeof entry === "string" ? void 0 : entry;
|
|
16579
|
+
const name = entryName(entry);
|
|
16580
|
+
return {
|
|
16581
|
+
name,
|
|
16582
|
+
instructions: config?.instructions,
|
|
16583
|
+
model: config?.model ?? globalModel,
|
|
16584
|
+
agent: config?.agent,
|
|
16585
|
+
maxSteps: boundedSteps(config, globalMaxSteps),
|
|
16586
|
+
builtIn: builtIns.has(name)
|
|
16587
|
+
};
|
|
16588
|
+
}
|
|
16589
|
+
/**
|
|
16590
|
+
* Configures experimental autonomous knowledge extraction and reflection.
|
|
16591
|
+
*
|
|
16592
|
+
* @experimental This API may change without notice.
|
|
16593
|
+
*/
|
|
16594
|
+
var Subconscious = class {
|
|
16595
|
+
config;
|
|
16596
|
+
resolved;
|
|
16597
|
+
constructor(config = {}) {
|
|
16598
|
+
const observation = config.observation ?? ["capture", "remind"];
|
|
16599
|
+
const reflection = config.reflection ?? ["curate", "learn"];
|
|
16600
|
+
assertUniqueNames(observation, "observation");
|
|
16601
|
+
assertUniqueNames(reflection, "reflection");
|
|
16602
|
+
const maxSteps = boundedSteps(config, DEFAULT_MAX_STEPS);
|
|
16603
|
+
for (const entry of observation) this.#validateObservationEntry(entry);
|
|
16604
|
+
for (const entry of reflection) this.#validateReflectionEntry(entry);
|
|
16605
|
+
const recentUpdates = config.activity === false ? false : config.activity?.recentUpdates ?? DEFAULT_RECENT_UPDATES;
|
|
16606
|
+
if (recentUpdates !== false && (!Number.isInteger(recentUpdates) || recentUpdates < 1 || recentUpdates > MAX_RECENT_UPDATES)) throw new Error(`Subconscious activity.recentUpdates must be an integer between 1 and ${MAX_RECENT_UPDATES}.`);
|
|
16607
|
+
const pins = config.pins === void 0 || config.pins === false ? false : {
|
|
16608
|
+
maxPins: (config.pins === true ? void 0 : config.pins.maxPins) ?? 20,
|
|
16609
|
+
maxCharacters: (config.pins === true ? void 0 : config.pins.maxCharacters) ?? 2e3,
|
|
16610
|
+
capturePinning: (config.pins === true ? void 0 : config.pins.capturePinning) ?? false
|
|
16611
|
+
};
|
|
16612
|
+
if (pins !== false) {
|
|
16613
|
+
if (!Number.isInteger(pins.maxPins) || pins.maxPins < 1) throw new Error("Subconscious pins.maxPins must be a positive integer.");
|
|
16614
|
+
if (!Number.isInteger(pins.maxCharacters) || pins.maxCharacters < 1 || pins.maxCharacters > 8e3) throw new Error(`Subconscious pins.maxCharacters must be an integer between 1 and ${MAX_PINNED_MAX_CHARACTERS}.`);
|
|
16615
|
+
}
|
|
16616
|
+
if (config.curationCadence !== void 0 && (!Number.isInteger(config.curationCadence) || config.curationCadence < 1)) throw new Error("Subconscious curationCadence must be a positive integer.");
|
|
16617
|
+
this.config = Object.freeze({
|
|
16618
|
+
...config,
|
|
16619
|
+
observation: [...observation],
|
|
16620
|
+
reflection: [...reflection]
|
|
16621
|
+
});
|
|
16622
|
+
this.resolved = Object.freeze({
|
|
16623
|
+
observation: observation.map((entry) => entryName(entry) === "remind" ? resolveAgent(entry, BUILT_IN_OBSERVATION, config.model, maxSteps) : resolveExtractor(entry)),
|
|
16624
|
+
reflection: reflection.map((entry) => resolveAgent(entry, BUILT_IN_REFLECTION, config.model, maxSteps)),
|
|
16625
|
+
defaultScope: config.defaultScope ?? "resource",
|
|
16626
|
+
maxScope: config.maxScope,
|
|
16627
|
+
learnedGuidance: config.learnedGuidance !== false,
|
|
16628
|
+
tools: config.tools !== false,
|
|
16629
|
+
activity: recentUpdates === false ? false : { recentUpdates },
|
|
16630
|
+
pins,
|
|
16631
|
+
curationCadence: config.curationCadence
|
|
16632
|
+
});
|
|
16633
|
+
}
|
|
16634
|
+
createObservationExtractors(omModel) {
|
|
16635
|
+
const extractors = [];
|
|
16636
|
+
for (const entry of this.config.observation ?? []) {
|
|
16637
|
+
const name = entryName(entry);
|
|
16638
|
+
if (name === "capture") extractors.push(new SubconsciousCaptureExtractor({
|
|
16639
|
+
config: typeof entry === "string" ? void 0 : entry,
|
|
16640
|
+
defaultScope: this.resolved.defaultScope,
|
|
16641
|
+
maxScope: this.resolved.maxScope,
|
|
16642
|
+
learnedGuidance: this.resolved.learnedGuidance,
|
|
16643
|
+
activityRecentUpdates: this.resolved.activity === false ? void 0 : this.resolved.activity.recentUpdates,
|
|
16644
|
+
pins: this.resolved.pins
|
|
16645
|
+
}));
|
|
16646
|
+
else if (name === "remind") {
|
|
16647
|
+
const resolved = this.resolved.observation.find((agent) => agent.name === name);
|
|
16648
|
+
if (resolved) extractors.push(new SubconsciousRemindExtractor(resolved, omModel));
|
|
16649
|
+
} else if (!BUILT_IN_OBSERVATION.has(name)) {
|
|
16650
|
+
const custom = entry;
|
|
16651
|
+
extractors.push(new Extractor({
|
|
16652
|
+
name: custom.name,
|
|
16653
|
+
instructions: custom.instructions?.trim() || `Extract ${custom.name} from the current observations.`,
|
|
16654
|
+
schema: custom.schema,
|
|
16655
|
+
metadataKeyPath: false,
|
|
16656
|
+
includePreviousExtraction: false,
|
|
16657
|
+
onExtracted: custom.onExtracted
|
|
16658
|
+
}));
|
|
16659
|
+
}
|
|
16660
|
+
}
|
|
16661
|
+
return extractors;
|
|
16662
|
+
}
|
|
16663
|
+
#validateObservationEntry(entry) {
|
|
16664
|
+
const name = entryName(entry);
|
|
16665
|
+
if (typeof entry === "string") {
|
|
16666
|
+
if (!BUILT_IN_OBSERVATION.has(name)) throw new Error(`Unknown Subconscious observation agent: ${name}`);
|
|
16667
|
+
return;
|
|
16668
|
+
}
|
|
16669
|
+
if (BUILT_IN_OBSERVATION.has(name)) {
|
|
16670
|
+
if (name === "capture") {
|
|
16671
|
+
if ("model" in entry || "maxSteps" in entry) throw new Error("Subconscious capture shares the Observer model and does not accept model or maxSteps.");
|
|
16672
|
+
if ("schema" in entry && entry.schema && (!("onExtracted" in entry) || typeof entry.onExtracted !== "function")) throw new Error("A custom capture schema requires an onExtracted hook that handles its output.");
|
|
16673
|
+
}
|
|
16674
|
+
return;
|
|
16675
|
+
}
|
|
16676
|
+
if ("model" in entry || "maxSteps" in entry) throw new Error(`Subconscious observation extractor "${name}" shares the Observer model and does not accept model or maxSteps.`);
|
|
16677
|
+
if (!("schema" in entry) || !entry.schema || !("onExtracted" in entry) || typeof entry.onExtracted !== "function") throw new Error(`Custom Subconscious observation agent "${name}" requires schema and onExtracted.`);
|
|
16678
|
+
}
|
|
16679
|
+
#validateReflectionEntry(entry) {
|
|
16680
|
+
const name = entryName(entry);
|
|
16681
|
+
if (typeof entry === "string") {
|
|
16682
|
+
if (!BUILT_IN_REFLECTION.has(name)) throw new Error(`Unknown Subconscious reflection agent: ${name}`);
|
|
16683
|
+
return;
|
|
16684
|
+
}
|
|
16685
|
+
if (BUILT_IN_REFLECTION.has(name) && "agent" in entry && entry.agent) throw new Error(`Built-in Subconscious reflection agent "${name}" cannot be replaced with a custom agent.`);
|
|
16686
|
+
if (!BUILT_IN_REFLECTION.has(name) && !entry.instructions?.trim() && !("agent" in entry && entry.agent)) throw new Error(`Custom Subconscious reflection agent "${name}" requires instructions or agent.`);
|
|
16687
|
+
}
|
|
16688
|
+
};
|
|
16689
|
+
//#endregion
|
|
16690
|
+
//#region src/processors/observational-memory/subconscious/curate.ts
|
|
16691
|
+
const CURATION_AGENT = "curate";
|
|
16692
|
+
const DEFAULT_INSTRUCTIONS$1 = `Maintain durable scoped knowledge from the committed observation worklist.
|
|
16693
|
+
|
|
16694
|
+
Use the read tools to inspect existing nodes, knowledge records, mentions, backlinks, and long-form node content. Use the write tools to merge true duplicates, repair names and links, soft-delete superseded knowledge records, rescope knowledge records only when justified and permitted by their ceilings, and synthesize useful node content. Never restore deleted knowledge records. Never invent provenance, capture timestamps, scopes, ceilings, IDs, or versions; those are enforced by code. Resolve optimistic-concurrency conflicts by reading the latest record and retrying the intended mutation. Keep the reserved capture-guidance node concise and update it only with durable guidance that will improve future capture.
|
|
16695
|
+
|
|
16696
|
+
Process the worklist in ID order. Every time you finish processing a KnowledgeRecord, include <curation-complete through="RECORD_ID" /> in your next text response with that record's ID. The latest marker is your acknowledged cursor, so progress survives if you run out of steps mid-batch. Your final response must end with the marker for the last KnowledgeRecord you fully processed. If you cannot finish the batch, acknowledge only the last KnowledgeRecord you did finish. Do not emit a completion marker when no KnowledgeRecord was fully processed.`;
|
|
16697
|
+
const PINNED_INSTRUCTIONS = `Maintain the pin set with knowledge_pin, knowledge_edit_pin, and knowledge_unpin. Pinned entries are delivered to the main agent on every turn, so they cost tokens permanently and must stay short. Pin only knowledge that should apply without being asked for, such as standing instructions, durable preferences, and hard constraints. Pin only knowledge that is BOTH costly to rediscover AND not the kind of thing a future agent would think to search for; anything a reminder can surface on demand does not belong in the pin set. Unpin an entry as soon as it stops being unconditionally true.`;
|
|
16698
|
+
function resolveScope$1(context) {
|
|
16699
|
+
const organizationId = context.requestContext?.get("organizationId");
|
|
16700
|
+
if (typeof organizationId !== "string" || !organizationId.trim()) throw new Error("Subconscious curate requires organizationId in the request context.");
|
|
16701
|
+
return (0, _mastra_core_storage.canonicalizeKnowledgeScope)([
|
|
16702
|
+
`org:${organizationId}`,
|
|
16703
|
+
`resource:${resolveKnowledgeResourceId(context.requestContext, context.resourceId)}`,
|
|
16704
|
+
`thread:${context.parentThreadId}`
|
|
16705
|
+
]);
|
|
16706
|
+
}
|
|
16707
|
+
async function readWorklist$1(store, sourceThreadId, scope, after) {
|
|
16708
|
+
const records = [];
|
|
16709
|
+
let cursor = after;
|
|
16710
|
+
do {
|
|
16711
|
+
const page = await store.knowledgeBySource({
|
|
16712
|
+
sourceThreadId,
|
|
16713
|
+
scope,
|
|
16714
|
+
after: cursor,
|
|
16715
|
+
limit: 100,
|
|
16716
|
+
includeDeleted: true
|
|
16717
|
+
});
|
|
16718
|
+
records.push(...page.records);
|
|
16719
|
+
cursor = page.nextCursor;
|
|
16720
|
+
} while (cursor && records.length < 500);
|
|
16721
|
+
return {
|
|
16722
|
+
records,
|
|
16723
|
+
hasMore: Boolean(cursor)
|
|
16724
|
+
};
|
|
16725
|
+
}
|
|
16726
|
+
function createCuratorHandler(memory, subconscious, curatorMemory = memory, options) {
|
|
16727
|
+
const config = subconscious.reflection.find((agent) => agent.name === CURATION_AGENT);
|
|
16728
|
+
if (!config) return async () => "no-op";
|
|
16729
|
+
return async (context) => {
|
|
16730
|
+
let store;
|
|
16731
|
+
let scope;
|
|
16732
|
+
try {
|
|
16733
|
+
scope = resolveScope$1(context);
|
|
16734
|
+
store = await memory.storage.getStore("knowledge");
|
|
16735
|
+
if (!store) throw new Error("Subconscious curate requires a configured knowledge storage domain.");
|
|
16736
|
+
const cursor = await store.getCurationCursor({
|
|
16737
|
+
sourceThreadId: context.parentThreadId,
|
|
16738
|
+
agent: CURATION_AGENT
|
|
16739
|
+
});
|
|
16740
|
+
const worklist = await readWorklist$1(store, context.parentThreadId, scope, cursor?.lastKnowledgeId);
|
|
16741
|
+
if (!worklist.records.length && !context.observations.trim()) return "no-op";
|
|
16742
|
+
const result = await (await createCuratorAgent(memory, curatorMemory, context, scope, config, subconscious, options?.omModel)).generate(`Parent thread: ${context.parentThreadId}\nCurrent time: ${(/* @__PURE__ */ new Date()).toISOString()}\nWorklist truncated: ${worklist.hasMore}\n\nCommitted pre-reflection observations:\n${context.observations}\n\nNew KnowledgeRecord worklist:\n${JSON.stringify(worklist.records)}`, {
|
|
16743
|
+
requestContext: context.requestContext,
|
|
16744
|
+
abortSignal: context.abortSignal,
|
|
16745
|
+
maxSteps: config.maxSteps,
|
|
16746
|
+
memory: {
|
|
16747
|
+
thread: `subconscious:${context.parentThreadId}:curate`,
|
|
16748
|
+
resource: context.resourceId
|
|
16749
|
+
}
|
|
16750
|
+
});
|
|
16751
|
+
if (worklist.records.length) {
|
|
16752
|
+
const acknowledgedId = [...result.text.matchAll(/<curation-complete\s+through=["']([^"']+)["']\s*\/>/gi)].at(-1)?.[1];
|
|
16753
|
+
if (!acknowledgedId || !worklist.records.some((record) => record.id === acknowledgedId)) throw new Error("Curator did not acknowledge a valid processed KnowledgeRecord cursor.");
|
|
16754
|
+
await store.advanceCurationCursor({
|
|
16755
|
+
sourceThreadId: context.parentThreadId,
|
|
16756
|
+
agent: CURATION_AGENT,
|
|
16757
|
+
lastKnowledgeId: acknowledgedId
|
|
16758
|
+
});
|
|
16759
|
+
}
|
|
16760
|
+
return "ran";
|
|
16761
|
+
} catch (error) {
|
|
16762
|
+
const message = `curate: ${error instanceof Error ? error.message : String(error)}`;
|
|
16763
|
+
await context.writer?.custom({
|
|
16764
|
+
type: "data-subconscious-error",
|
|
16765
|
+
data: {
|
|
16766
|
+
agent: "curate",
|
|
16767
|
+
error: message
|
|
16768
|
+
}
|
|
16769
|
+
});
|
|
16770
|
+
if (store && scope) await publishSubconsciousActivity({
|
|
16771
|
+
store,
|
|
16772
|
+
scope,
|
|
16773
|
+
recentUpdates: subconscious.activity === false ? 10 : subconscious.activity.recentUpdates,
|
|
16774
|
+
sendStateSignal: context.sendStateSignal,
|
|
16775
|
+
errors: [message]
|
|
16776
|
+
});
|
|
16777
|
+
else await publishSubconsciousError({
|
|
16778
|
+
error: message,
|
|
16779
|
+
sendStateSignal: context.sendStateSignal
|
|
16780
|
+
});
|
|
16781
|
+
throw error;
|
|
16782
|
+
}
|
|
16783
|
+
};
|
|
16784
|
+
}
|
|
16785
|
+
async function createCuratorAgent(memory, curatorMemory, context, scope, config, subconscious, omModel) {
|
|
16786
|
+
const model = await resolveSubconsciousAgentModel({
|
|
16787
|
+
config,
|
|
16788
|
+
omModel,
|
|
16789
|
+
mainAgent: context.mainAgent,
|
|
16790
|
+
requestContext: context.requestContext
|
|
16791
|
+
});
|
|
16792
|
+
if (!model) throw new Error("Subconscious curate requires the main agent to resolve its model.");
|
|
16793
|
+
return new _mastra_core_agent.Agent({
|
|
16794
|
+
id: `subconscious-curate-${context.parentThreadId}`,
|
|
16795
|
+
name: "Subconscious Curate",
|
|
16796
|
+
instructions: [
|
|
16797
|
+
DEFAULT_INSTRUCTIONS$1,
|
|
16798
|
+
subconscious.pins ? PINNED_INSTRUCTIONS : void 0,
|
|
16799
|
+
config.instructions?.trim()
|
|
16800
|
+
].filter(Boolean).join("\n\n"),
|
|
16801
|
+
model,
|
|
16802
|
+
memory: curatorMemory,
|
|
16803
|
+
tools: {
|
|
16804
|
+
...createKnowledgeTools(memory),
|
|
16805
|
+
...createKnowledgeWriteTools(memory, {
|
|
16806
|
+
scope,
|
|
16807
|
+
sourceThreadId: context.parentThreadId,
|
|
16808
|
+
defaultScope: subconscious.defaultScope,
|
|
16809
|
+
maxScope: subconscious.maxScope
|
|
16810
|
+
}),
|
|
16811
|
+
...subconscious.pins ? createPinnedTools(memory, {
|
|
16812
|
+
scope,
|
|
16813
|
+
sourceThreadId: context.parentThreadId,
|
|
16814
|
+
defaultScope: subconscious.defaultScope,
|
|
16815
|
+
maxScope: subconscious.maxScope,
|
|
16816
|
+
maxPins: subconscious.pins.maxPins,
|
|
16817
|
+
maxCharacters: subconscious.pins.maxCharacters
|
|
16818
|
+
}) : {}
|
|
16819
|
+
}
|
|
16820
|
+
});
|
|
16821
|
+
}
|
|
16822
|
+
//#endregion
|
|
16823
|
+
//#region src/processors/observational-memory/subconscious/learn.ts
|
|
16824
|
+
const LEARN_AGENT = "learn";
|
|
16825
|
+
const ULID_ALPHABET = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
16826
|
+
const DEFAULT_INSTRUCTIONS = `Learn reusable skills from the full pre-reflection observations and pending knowledge records.
|
|
16827
|
+
|
|
16828
|
+
A skill is a repeatable procedure with ordered actions, a trigger or context, and a success or recovery outcome. Do not learn one-off events, isolated preferences, knowledge records, or procedures supported by fewer than two distinct pending knowledge records. Search existing kind:skill nodes by exact name before writing so updates extend one skill rather than creating duplicates.
|
|
16829
|
+
|
|
16830
|
+
Use knowledge_record_skill for every skill creation or evidence update. It validates the evidence frontier and writes retry-safe evidence. You may use the other scoped knowledge tools for research and maintenance, but never restore deleted records, invent provenance or versions, or write outside the source scope.
|
|
16831
|
+
|
|
16832
|
+
Process pending records in ID order. End with <learning-complete through="RECORD_ID" /> naming the last pending record you reviewed, even when no reusable skill was found. Acknowledge only records you fully reviewed.`;
|
|
16833
|
+
function resolveScope(context) {
|
|
16834
|
+
const organizationId = context.requestContext?.get("organizationId");
|
|
16835
|
+
if (typeof organizationId !== "string" || !organizationId.trim()) throw new Error("Subconscious learn requires organizationId in the request context.");
|
|
16836
|
+
return (0, _mastra_core_storage.canonicalizeKnowledgeScope)([
|
|
16837
|
+
`org:${organizationId}`,
|
|
16838
|
+
`resource:${resolveKnowledgeResourceId(context.requestContext, context.resourceId)}`,
|
|
16839
|
+
`thread:${context.parentThreadId}`
|
|
16840
|
+
]);
|
|
16841
|
+
}
|
|
16842
|
+
async function readWorklist(store, sourceThreadId, scope, after) {
|
|
16843
|
+
const records = [];
|
|
16844
|
+
let cursor = after;
|
|
16845
|
+
do {
|
|
16846
|
+
const page = await store.knowledgeBySource({
|
|
16847
|
+
sourceThreadId,
|
|
16848
|
+
scope,
|
|
16849
|
+
after: cursor,
|
|
16850
|
+
limit: 100
|
|
16851
|
+
});
|
|
16852
|
+
records.push(...page.records);
|
|
16853
|
+
cursor = page.nextCursor;
|
|
16854
|
+
} while (cursor && records.length < 500);
|
|
16855
|
+
return {
|
|
16856
|
+
records,
|
|
16857
|
+
hasMore: Boolean(cursor)
|
|
16858
|
+
};
|
|
16859
|
+
}
|
|
16860
|
+
function evidenceRecordId(sourceRecordId, skillName) {
|
|
16861
|
+
const hash = (0, crypto$1.createHash)("sha256").update(`${skillName.trim().toLocaleLowerCase()}\0${sourceRecordId}`).digest();
|
|
16862
|
+
let suffix = "";
|
|
16863
|
+
for (let index = 0; index < 16; index++) suffix += ULID_ALPHABET[hash[index] & 31];
|
|
16864
|
+
return `${sourceRecordId.slice(0, 10)}${suffix}`;
|
|
16865
|
+
}
|
|
16866
|
+
function createLearnerRecordSkillTool(input) {
|
|
16867
|
+
return (0, _mastra_core_tools.createTool)({
|
|
16868
|
+
id: "knowledge_record_skill",
|
|
16869
|
+
description: "Create or update one reusable skill using at least two distinct pending knowledge records. Evidence writes are idempotent across retries.",
|
|
16870
|
+
inputSchema: {
|
|
16871
|
+
type: "object",
|
|
16872
|
+
properties: {
|
|
16873
|
+
name: {
|
|
16874
|
+
type: "string",
|
|
16875
|
+
minLength: 1
|
|
16876
|
+
},
|
|
16877
|
+
procedure: {
|
|
16878
|
+
type: "string",
|
|
16879
|
+
minLength: 1
|
|
16880
|
+
},
|
|
16881
|
+
sourceRecordIds: {
|
|
16882
|
+
type: "array",
|
|
16883
|
+
items: {
|
|
16884
|
+
type: "string",
|
|
16885
|
+
minLength: 1
|
|
16886
|
+
},
|
|
16887
|
+
minItems: 2,
|
|
16888
|
+
uniqueItems: true
|
|
16889
|
+
}
|
|
16890
|
+
},
|
|
16891
|
+
required: [
|
|
16892
|
+
"name",
|
|
16893
|
+
"procedure",
|
|
16894
|
+
"sourceRecordIds"
|
|
16895
|
+
],
|
|
16896
|
+
additionalProperties: false
|
|
16897
|
+
},
|
|
16898
|
+
execute: async (raw) => {
|
|
16899
|
+
const value = raw;
|
|
16900
|
+
const sourceIds = [...new Set(value.sourceRecordIds)];
|
|
16901
|
+
const pending = new Map(input.pendingRecords.map((record) => [record.id, record]));
|
|
16902
|
+
if (sourceIds.length < 2 || sourceIds.some((id) => !pending.has(id))) throw new Error("Skill evidence requires at least two distinct records from the pending learner worklist.");
|
|
16903
|
+
const normalizedName = value.name.trim();
|
|
16904
|
+
if (input.state.recordedName && input.state.recordedName.toLocaleLowerCase() !== normalizedName.toLocaleLowerCase()) throw new Error("The learner may record at most one skill per reflection.");
|
|
16905
|
+
input.state.recordedName = normalizedName;
|
|
16906
|
+
const nodeScope = (0, _mastra_core_storage.expandKnowledgeScope)(input.scope, input.defaultScope);
|
|
16907
|
+
let node = await input.store.resolveNode({
|
|
16908
|
+
name: normalizedName,
|
|
16909
|
+
scope: input.scope
|
|
16910
|
+
});
|
|
16911
|
+
if (node && node.kind !== "skill") throw new Error(`Knowledge node is not a skill: ${normalizedName}`);
|
|
16912
|
+
node ??= await input.store.createNode({
|
|
16913
|
+
name: normalizedName,
|
|
16914
|
+
kind: "skill",
|
|
16915
|
+
scope: nodeScope
|
|
16916
|
+
});
|
|
16917
|
+
const evidence = [];
|
|
16918
|
+
for (const sourceId of sourceIds) {
|
|
16919
|
+
const id = evidenceRecordId(sourceId, normalizedName);
|
|
16920
|
+
const existing = await input.store.getKnowledge({ id });
|
|
16921
|
+
if (existing) {
|
|
16922
|
+
evidence.push(existing);
|
|
16923
|
+
continue;
|
|
16924
|
+
}
|
|
16925
|
+
const source = pending.get(sourceId);
|
|
16926
|
+
try {
|
|
16927
|
+
evidence.push(await input.store.appendKnowledge({
|
|
16928
|
+
id,
|
|
16929
|
+
node: node.id,
|
|
16930
|
+
text: `Procedure: ${value.procedure.trim()} Evidence source: ${source.id}.`,
|
|
16931
|
+
scope: source.scope,
|
|
16932
|
+
sourceThreadId: `subconscious:${input.parentThreadId}:learn`,
|
|
16933
|
+
maxScope: source.maxScope ?? input.maxScope,
|
|
16934
|
+
resolutionScope: input.scope,
|
|
16935
|
+
defaultScope: nodeScope
|
|
16936
|
+
}));
|
|
16937
|
+
} catch (error) {
|
|
16938
|
+
const raced = await input.store.getKnowledge({ id });
|
|
16939
|
+
if (!raced) throw error;
|
|
16940
|
+
evidence.push(raced);
|
|
16941
|
+
}
|
|
16942
|
+
}
|
|
16943
|
+
return {
|
|
16944
|
+
node,
|
|
16945
|
+
evidence
|
|
16946
|
+
};
|
|
16947
|
+
}
|
|
16948
|
+
});
|
|
16949
|
+
}
|
|
16950
|
+
function composeReflectionAgentHandlers(handlers) {
|
|
16951
|
+
return async (context) => {
|
|
16952
|
+
for (const handler of handlers) try {
|
|
16953
|
+
await handler(context);
|
|
16954
|
+
} catch (error) {
|
|
16955
|
+
if (context.abortSignal?.aborted) throw error;
|
|
16956
|
+
}
|
|
16957
|
+
};
|
|
16958
|
+
}
|
|
16959
|
+
function createLearnerHandler(memory, subconscious, learnerMemory = memory, options) {
|
|
16960
|
+
const config = subconscious.reflection.find((agent) => agent.name === LEARN_AGENT);
|
|
16961
|
+
if (!config) return async () => {};
|
|
16962
|
+
return async (context) => {
|
|
16963
|
+
let store;
|
|
16964
|
+
let scope;
|
|
16965
|
+
try {
|
|
16966
|
+
scope = resolveScope(context);
|
|
16967
|
+
store = await memory.storage.getStore("knowledge");
|
|
16968
|
+
if (!store) throw new Error("Subconscious learn requires a configured knowledge storage domain.");
|
|
16969
|
+
const cursor = await store.getCurationCursor({
|
|
16970
|
+
sourceThreadId: context.parentThreadId,
|
|
16971
|
+
agent: LEARN_AGENT
|
|
16972
|
+
});
|
|
16973
|
+
const worklist = await readWorklist(store, context.parentThreadId, scope, cursor?.lastKnowledgeId);
|
|
16974
|
+
if (!worklist.records.length) return;
|
|
16975
|
+
const acknowledgedId = (await (await createLearnerAgent(memory, learnerMemory, context, scope, worklist.records, config, subconscious, options?.omModel)).generate(`Parent thread: ${context.parentThreadId}\nCurrent time: ${(/* @__PURE__ */ new Date()).toISOString()}\nWorklist truncated: ${worklist.hasMore}\n\nFull pre-reflection observations:\n${context.observations}\n\nPending knowledge records:\n${JSON.stringify(worklist.records)}`, {
|
|
16976
|
+
requestContext: context.requestContext,
|
|
16977
|
+
abortSignal: context.abortSignal,
|
|
16978
|
+
maxSteps: config.maxSteps,
|
|
16979
|
+
memory: {
|
|
16980
|
+
thread: `subconscious:${context.parentThreadId}:learn`,
|
|
16981
|
+
resource: context.resourceId
|
|
16982
|
+
}
|
|
16983
|
+
})).text.match(/<learning-complete\s+through=["']([^"']+)["']\s*\/>/i)?.[1];
|
|
16984
|
+
if (!acknowledgedId || !worklist.records.some((record) => record.id === acknowledgedId)) throw new Error("Learner did not acknowledge a valid reviewed record cursor.");
|
|
16985
|
+
await store.advanceCurationCursor({
|
|
16986
|
+
sourceThreadId: context.parentThreadId,
|
|
16987
|
+
agent: LEARN_AGENT,
|
|
16988
|
+
lastKnowledgeId: acknowledgedId
|
|
16989
|
+
});
|
|
16990
|
+
} catch (error) {
|
|
16991
|
+
const message = `learn: ${error instanceof Error ? error.message : String(error)}`;
|
|
16992
|
+
await context.writer?.custom({
|
|
16993
|
+
type: "data-subconscious-error",
|
|
16994
|
+
data: {
|
|
16995
|
+
agent: "learn",
|
|
16996
|
+
error: message
|
|
16997
|
+
}
|
|
16998
|
+
});
|
|
16999
|
+
if (store && scope) await publishSubconsciousActivity({
|
|
17000
|
+
store,
|
|
17001
|
+
scope,
|
|
17002
|
+
recentUpdates: subconscious.activity === false ? 10 : subconscious.activity.recentUpdates,
|
|
17003
|
+
sendStateSignal: context.sendStateSignal,
|
|
17004
|
+
errors: [message]
|
|
17005
|
+
});
|
|
17006
|
+
else await publishSubconsciousError({
|
|
17007
|
+
error: message,
|
|
17008
|
+
sendStateSignal: context.sendStateSignal
|
|
17009
|
+
});
|
|
17010
|
+
throw error;
|
|
17011
|
+
}
|
|
17012
|
+
};
|
|
17013
|
+
}
|
|
17014
|
+
async function createLearnerAgent(memory, learnerMemory, context, scope, pendingRecords, config, subconscious, omModel) {
|
|
17015
|
+
const model = await resolveSubconsciousAgentModel({
|
|
17016
|
+
config,
|
|
17017
|
+
omModel,
|
|
17018
|
+
mainAgent: context.mainAgent,
|
|
17019
|
+
requestContext: context.requestContext
|
|
17020
|
+
});
|
|
17021
|
+
if (!model) throw new Error("Subconscious learn requires the main agent to resolve its model.");
|
|
17022
|
+
const store = await memory.storage.getStore("knowledge");
|
|
17023
|
+
if (!store) throw new Error("Subconscious learn requires a configured knowledge storage domain.");
|
|
17024
|
+
const state = {};
|
|
17025
|
+
return new _mastra_core_agent.Agent({
|
|
17026
|
+
id: `subconscious-learn-${context.parentThreadId}`,
|
|
17027
|
+
name: "Subconscious Learn",
|
|
17028
|
+
instructions: [DEFAULT_INSTRUCTIONS, config.instructions?.trim()].filter(Boolean).join("\n\n"),
|
|
17029
|
+
model,
|
|
17030
|
+
memory: learnerMemory,
|
|
17031
|
+
tools: {
|
|
17032
|
+
...createKnowledgeTools(memory, scope),
|
|
17033
|
+
...createKnowledgeWriteTools(memory, {
|
|
17034
|
+
scope,
|
|
17035
|
+
sourceThreadId: context.parentThreadId,
|
|
17036
|
+
defaultScope: subconscious.defaultScope,
|
|
17037
|
+
maxScope: subconscious.maxScope
|
|
17038
|
+
}),
|
|
17039
|
+
knowledge_record_skill: createLearnerRecordSkillTool({
|
|
17040
|
+
store,
|
|
17041
|
+
scope,
|
|
17042
|
+
pendingRecords,
|
|
17043
|
+
parentThreadId: context.parentThreadId,
|
|
17044
|
+
defaultScope: subconscious.defaultScope,
|
|
17045
|
+
maxScope: subconscious.maxScope,
|
|
17046
|
+
state
|
|
17047
|
+
})
|
|
17048
|
+
}
|
|
17049
|
+
});
|
|
17050
|
+
}
|
|
17051
|
+
//#endregion
|
|
17052
|
+
//#region src/processors/observational-memory/constants.ts
|
|
17053
|
+
var constants_exports = /* @__PURE__ */ __exportAll({
|
|
17054
|
+
OBSERVATIONAL_MEMORY_DEFAULTS: () => OBSERVATIONAL_MEMORY_DEFAULTS,
|
|
17055
|
+
OBSERVATION_CONTEXT_INSTRUCTIONS: () => OBSERVATION_CONTEXT_INSTRUCTIONS,
|
|
17056
|
+
OBSERVATION_CONTEXT_PROMPT: () => OBSERVATION_CONTEXT_PROMPT,
|
|
17057
|
+
OBSERVATION_CONTINUATION_HINT: () => OBSERVATION_CONTINUATION_HINT,
|
|
17058
|
+
getRetrievalInstructions: () => getRetrievalInstructions
|
|
17059
|
+
});
|
|
17060
|
+
/**
|
|
17061
|
+
* Default configuration values matching the spec
|
|
17062
|
+
*/
|
|
17063
|
+
const OBSERVATIONAL_MEMORY_DEFAULTS = {
|
|
17064
|
+
observation: {
|
|
17065
|
+
model: "google/gemini-2.5-flash",
|
|
17066
|
+
messageTokens: 3e4,
|
|
17067
|
+
modelSettings: {
|
|
17068
|
+
temperature: .3,
|
|
17069
|
+
maxOutputTokens: 1e5
|
|
17070
|
+
},
|
|
17071
|
+
providerOptions: { google: { thinkingConfig: { thinkingBudget: 215 } } },
|
|
17072
|
+
maxTokensPerBatch: 1e4,
|
|
17073
|
+
bufferTokens: .2,
|
|
17074
|
+
bufferActivation: .8
|
|
17075
|
+
},
|
|
17076
|
+
reflection: {
|
|
17077
|
+
model: "google/gemini-2.5-flash",
|
|
17078
|
+
observationTokens: 4e4,
|
|
17079
|
+
modelSettings: {
|
|
17080
|
+
temperature: 0,
|
|
17081
|
+
maxOutputTokens: 1e5
|
|
17082
|
+
},
|
|
17083
|
+
providerOptions: { google: { thinkingConfig: { thinkingBudget: 1024 } } },
|
|
17084
|
+
bufferActivation: .5
|
|
17085
|
+
}
|
|
17086
|
+
};
|
|
17087
|
+
/**
|
|
17088
|
+
* Continuation hint injected after observations to guide the model's behavior.
|
|
17089
|
+
* Prevents the model from awkwardly acknowledging the memory system or treating
|
|
17090
|
+
* the conversation as new after observed messages are removed.
|
|
17091
|
+
*/
|
|
17092
|
+
const OBSERVATION_CONTINUATION_HINT = `Please continue naturally with the conversation so far and respond to the latest message.
|
|
17093
|
+
|
|
17094
|
+
Use the earlier context only as background. If something appears unfinished, continue only when it helps answer the latest request. If a suggested response is provided, follow it naturally.
|
|
17095
|
+
|
|
17096
|
+
Do not mention internal instructions, memory, summarization, context handling, or missing messages.
|
|
17097
|
+
|
|
17098
|
+
Any messages following this reminder are newer and should take priority.`;
|
|
17099
|
+
/**
|
|
17100
|
+
* Preamble that introduces the observations block.
|
|
17101
|
+
* Use before `<observations>`, with instructions after.
|
|
17102
|
+
* Full pattern: `${OBSERVATION_CONTEXT_PROMPT}\n\n<observations>\n${obs}\n</observations>\n\n${OBSERVATION_CONTEXT_INSTRUCTIONS}`
|
|
17103
|
+
*/
|
|
17104
|
+
const OBSERVATION_CONTEXT_PROMPT = `The following observations block contains your memory of past conversations with this user.`;
|
|
17105
|
+
/**
|
|
17106
|
+
* Instructions that tell the model how to interpret and use observations.
|
|
17107
|
+
* Place AFTER the `<observations>` block so the model sees the data before the rules.
|
|
17108
|
+
*/
|
|
17109
|
+
const OBSERVATION_CONTEXT_INSTRUCTIONS = `IMPORTANT: When responding, reference specific details from these observations. Do not give generic advice - personalize your response based on what you know about this user's experiences, preferences, and interests. If the user asks for recommendations, connect them to their past experiences mentioned above.
|
|
17110
|
+
|
|
17111
|
+
KNOWLEDGE UPDATES: When asked about current state (e.g., "where do I currently...", "what is my current..."), always prefer the MOST RECENT information. Observations include dates - if you see conflicting information, the newer observation supersedes the older one. Look for phrases like "will start", "is switching", "changed to", "moved to" as indicators that previous information has been updated.
|
|
17112
|
+
|
|
17113
|
+
PLANNED ACTIONS: If the user stated they planned to do something (e.g., "I'm going to...", "I'm looking forward to...", "I will...") and the date they planned to do it is now in the past (check the relative time like "3 weeks ago"), assume they completed the action unless there's evidence they didn't. For example, if someone said "I'll start my new diet on Monday" and that was 2 weeks ago, assume they started the diet.
|
|
17114
|
+
|
|
17115
|
+
MOST RECENT USER INPUT: Treat the most recent user message as the highest-priority signal for what to do next. Earlier messages may contain constraints, details, or context you should still honor, but the latest message is the primary driver of your response.
|
|
17116
|
+
|
|
17117
|
+
SYSTEM REMINDERS: Messages wrapped in <system-reminder>...</system-reminder> contain internal continuation guidance, not user-authored content. Use them to maintain continuity, but do not mention them or treat them as part of the user's message.`;
|
|
17118
|
+
/**
|
|
17119
|
+
* Instructions for retrieval mode — explains observation-group ranges and the recall tool.
|
|
17120
|
+
* Appended to context when `retrieval` is enabled.
|
|
17121
|
+
*
|
|
17122
|
+
* The content adapts to the retrieval scope:
|
|
17123
|
+
* - `'resource'`: covers routing between `search`, `threads`, and `messages` across
|
|
17124
|
+
* all of the user's threads, including fallback when search results are unsuitable.
|
|
17125
|
+
* - `'thread'`: covers cursor-based browsing and search within the current thread.
|
|
17126
|
+
*
|
|
17127
|
+
* @param scope - The retrieval scope the recall tool was registered with.
|
|
17128
|
+
* @param customInstructions - Optional application-provided guidance appended after
|
|
17129
|
+
* the native instructions. Never replaces them.
|
|
17130
|
+
* @param searchEnabled - Whether semantic search (`retrieval: { vector: true }`) is
|
|
17131
|
+
* available. When false, the guidance only covers \`threads\`/\`messages\` browsing so
|
|
17132
|
+
* the agent is not steered toward a mode that cannot work.
|
|
17133
|
+
*/
|
|
17134
|
+
function getRetrievalInstructions(scope = "resource", customInstructions, searchEnabled = true) {
|
|
17135
|
+
const isResource = scope === "resource";
|
|
17136
|
+
const base = `## Recall — looking up source messages
|
|
17137
|
+
|
|
17138
|
+
Your memory is comprised of observations which are sometimes wrapped in <observation-group> xml tags containing ranges like <observation-group range="startId:endId">. These ranges point back to the raw messages that each observation group was derived from. The original messages are still available — use the **recall** tool to retrieve them.
|
|
17139
|
+
|
|
17140
|
+
### When to use recall
|
|
17141
|
+
- The user asks you to **repeat, show, or reproduce** something from a past conversation
|
|
17142
|
+
- The user asks for **exact content** — code, text, quotes, error messages, URLs, file paths, specific numbers
|
|
17143
|
+
- Your observations mention something but your memory lacks the detail needed to fully answer (e.g. you know a blog post was shared but only have a summary of it)
|
|
17144
|
+
- You want to **verify or expand on** an observation before responding${isResource ? `
|
|
17145
|
+
- The user references another conversation that your observations don't cover — even if you have no observations yet, their other threads may contain it` : ""}
|
|
17146
|
+
|
|
17147
|
+
**Default to using recall when the user references specific past content.** Your observations capture the gist, not the details. If there's any doubt whether your memory is complete enough, use recall.
|
|
17148
|
+
|
|
17149
|
+
${isResource ? searchEnabled ? `### Choosing a mode
|
|
17150
|
+
The recall tool works across ALL of this user's conversation threads, not just the current one.
|
|
17151
|
+
|
|
17152
|
+
- Use \`mode: "search"\` with a \`query\` when you don't know which thread contains the answer. Each result includes its thread ID and the raw message IDs it came from, which you can use as a \`cursor\`.
|
|
17153
|
+
- Use \`mode: "messages"\` when you already know the thread — pass \`threadId\` to read another thread, or a \`cursor\` from an observation-group range or a search result.
|
|
17154
|
+
- Use \`mode: "threads"\` to list the user's threads (IDs, titles, dates) when you need to discover where something was discussed. Use \`before\`/\`after\` to narrow by date.
|
|
17155
|
+
|
|
17156
|
+
**If search results look irrelevant, do not give up.** Search only covers content that has been indexed — a short or recent conversation may exist in raw message history before any observation of it was created. When search returns nothing suitable but the user is clearly referring to a past conversation, call \`mode: "threads"\` to find candidate threads (titles and dates are strong clues), then read them with \`mode: "messages"\`. If a search result already gives you a thread ID, go straight to \`mode: "messages"\`.` : `### Choosing a mode
|
|
17157
|
+
The recall tool works across ALL of this user's conversation threads, not just the current one.
|
|
17158
|
+
|
|
17159
|
+
- Use \`mode: "threads"\` to list the user's threads (IDs, titles, dates) when you need to discover where something was discussed. Use \`before\`/\`after\` to narrow by date.
|
|
17160
|
+
- Use \`mode: "messages"\` when you know the thread — pass \`threadId\` to read another thread, or a \`cursor\` from an observation-group range.
|
|
17161
|
+
|
|
17162
|
+
When the user refers to a past conversation you don't have a cursor for, call \`mode: "threads"\` to find candidate threads (titles and dates are strong clues), then read them with \`mode: "messages"\`. Raw history may exist for threads that have no observations yet.` : `### Choosing a mode
|
|
17163
|
+
The recall tool is limited to the current conversation thread.
|
|
17164
|
+
|
|
17165
|
+
- Use \`mode: "messages"\` (default) to page through this thread's message history near a cursor.${searchEnabled ? `
|
|
17166
|
+
- Use \`mode: "search"\` with a \`query\` to find messages by content within this thread.` : ""}
|
|
17167
|
+
- Use \`mode: "threads"\` to get the current thread's ID, title, and dates.`}
|
|
17168
|
+
|
|
17169
|
+
### How to use recall with a cursor
|
|
17170
|
+
Each range has the format \`startId:endId\` where both are message IDs separated by a colon.
|
|
17171
|
+
|
|
17172
|
+
1. Find the observation group relevant to the user's question and extract the start or end ID from its range.
|
|
17173
|
+
2. Call \`recall\` with that ID as the \`cursor\`.
|
|
17174
|
+
3. Use \`page: 1\` (or omit) to read forward from the cursor, \`page: -1\` to read backward.
|
|
17175
|
+
4. If the first page doesn't have what you need, increment the page number to keep paginating.
|
|
17176
|
+
5. Check \`hasNextPage\`/\`hasPrevPage\` in the result to know if more pages exist in each direction.
|
|
17177
|
+
|
|
17178
|
+
### Detail levels
|
|
17179
|
+
By default recall returns **low** detail: truncated text and tool names only. Each message shows its ID and each part has a positional index like \`[p0]\`, \`[p1]\`, etc.
|
|
17180
|
+
|
|
17181
|
+
- Use \`detail: "high"\` to get full message content including tool arguments and results. This will only return the high detail version of a single message part at a time.
|
|
17182
|
+
- Use \`partIndex\` with a cursor to fetch a single part at full detail — for example, to read one specific tool result or code block without loading every part.
|
|
17183
|
+
|
|
17184
|
+
If the result says \`truncated: true\`, the output was cut to fit the token budget. You can paginate or use \`partIndex\` to target specific content. If a single part is itself too large, follow the returned \`nextCharOffset\` as described below.
|
|
17185
|
+
|
|
17186
|
+
### Following up on truncated parts
|
|
17187
|
+
Low-detail results may include truncation hints like:
|
|
17188
|
+
\`[truncated — call recall cursor="..." partIndex=N detail="high" for full content]\`
|
|
17189
|
+
|
|
17190
|
+
**When you see these hints and need the full content, make the exact call described in the hint.** This is the normal workflow: first recall at low detail to scan, then drill into specific parts at high detail. Do not stop at the low-detail result if the user asked for exact content.
|
|
17191
|
+
|
|
17192
|
+
If a single part is larger than the token budget, the \`partIndex\` result is \`truncated: true\` and includes \`nextCharOffset\`. Repeat the same call with \`charOffset\` set to that exact value to read the next chunk from where the previous one ended. Keep following \`nextCharOffset\` until the result no longer includes it — the chunks together contain the full part. Retrying without \`charOffset\` returns the same prefix again.
|
|
17193
|
+
|
|
17194
|
+
### When recall is NOT needed
|
|
17195
|
+
- The user is asking for a high-level summary and your observations already cover it
|
|
17196
|
+
- The question is about general preferences or facts that don't require source text
|
|
17197
|
+
${isResource ? `- No relevant observation range exists AND ${searchEnabled ? "`search`/`threads`" : "`threads`"} turned up nothing — but remember that raw history may exist for threads that have no observations yet, so check before concluding the information is unavailable` : `- There is no relevant range in your observations for the topic`}
|
|
17198
|
+
|
|
17199
|
+
Observation groups with range IDs and your recall tool allows you to think back and remember details you're fuzzy on.`;
|
|
17200
|
+
const custom = customInstructions?.trim();
|
|
17201
|
+
if (!custom) return base;
|
|
17202
|
+
return `${base}
|
|
17203
|
+
|
|
17204
|
+
### Additional recall guidance
|
|
17205
|
+
${custom}`;
|
|
17206
|
+
}
|
|
17207
|
+
//#endregion
|
|
17208
|
+
//#region src/processors/observational-memory/extracted-values.ts
|
|
17209
|
+
const BUILT_IN_METADATA_FIELDS = {
|
|
17210
|
+
"current-task": {
|
|
17211
|
+
metadataField: "currentTask",
|
|
17212
|
+
builtInField: "currentTask"
|
|
17213
|
+
},
|
|
17214
|
+
"suggested-response": {
|
|
17215
|
+
metadataField: "suggestedResponse",
|
|
17216
|
+
builtInField: "suggestedContinuation"
|
|
17217
|
+
},
|
|
17218
|
+
"thread-title": {
|
|
17219
|
+
metadataField: "threadTitle",
|
|
17220
|
+
builtInField: "threadTitle"
|
|
17221
|
+
}
|
|
17222
|
+
};
|
|
17223
|
+
function isPresentExtractedValue(value) {
|
|
17224
|
+
return value !== void 0 && value !== null && value !== "";
|
|
17225
|
+
}
|
|
17226
|
+
function normalizeExtractedValues(values) {
|
|
17227
|
+
if (!values) return;
|
|
17228
|
+
const normalized = Object.fromEntries(Object.entries(values).filter(([, value]) => isPresentExtractedValue(value)));
|
|
17229
|
+
return Object.keys(normalized).length > 0 ? normalized : void 0;
|
|
17230
|
+
}
|
|
17231
|
+
function mergeExtractedValues(...valueSets) {
|
|
17232
|
+
const merged = {};
|
|
17233
|
+
for (const values of valueSets) {
|
|
17234
|
+
const normalized = normalizeExtractedValues(values);
|
|
17235
|
+
if (normalized) Object.assign(merged, normalized);
|
|
17236
|
+
}
|
|
17237
|
+
return Object.keys(merged).length > 0 ? merged : void 0;
|
|
17238
|
+
}
|
|
17239
|
+
function mergeExtractionFailures(...failureSets) {
|
|
17240
|
+
const failures = failureSets.flatMap((set) => set ?? []);
|
|
17241
|
+
return failures.length > 0 ? failures : void 0;
|
|
17242
|
+
}
|
|
17243
|
+
const UNSAFE_METADATA_PATH_SEGMENTS = /* @__PURE__ */ new Set([
|
|
17244
|
+
"__proto__",
|
|
17245
|
+
"constructor",
|
|
17246
|
+
"prototype"
|
|
17247
|
+
]);
|
|
17248
|
+
function getMetadataPathSegments(keyPath) {
|
|
17249
|
+
if (keyPath === false) return;
|
|
17250
|
+
const segments = keyPath.split(".").filter(Boolean);
|
|
17251
|
+
if (segments.some((segment) => UNSAFE_METADATA_PATH_SEGMENTS.has(segment))) throw new Error(`Extractor metadataKeyPath "${keyPath}" contains an unsafe path segment.`);
|
|
17252
|
+
return segments.length > 0 ? segments : void 0;
|
|
17253
|
+
}
|
|
17254
|
+
function getValueAtPath(metadata, keyPath) {
|
|
17255
|
+
if (!metadata) return;
|
|
17256
|
+
const segments = getMetadataPathSegments(keyPath);
|
|
17257
|
+
if (!segments) return;
|
|
17258
|
+
let current = metadata;
|
|
17259
|
+
for (const segment of segments) {
|
|
17260
|
+
if (!current || typeof current !== "object" || Array.isArray(current)) return;
|
|
17261
|
+
current = current[segment];
|
|
17262
|
+
}
|
|
17263
|
+
return current;
|
|
17264
|
+
}
|
|
17265
|
+
function setValueAtPath(metadata, keyPath, value) {
|
|
17266
|
+
if (!isPresentExtractedValue(value)) return;
|
|
17267
|
+
const segments = getMetadataPathSegments(keyPath);
|
|
17268
|
+
if (!segments) return;
|
|
17269
|
+
let current = metadata;
|
|
17270
|
+
for (const segment of segments.slice(0, -1)) {
|
|
17271
|
+
const next = current[segment];
|
|
17272
|
+
if (!next || typeof next !== "object" || Array.isArray(next)) current[segment] = {};
|
|
17273
|
+
current = current[segment];
|
|
17274
|
+
}
|
|
17275
|
+
current[segments[segments.length - 1]] = value;
|
|
17276
|
+
}
|
|
17277
|
+
function readBuiltInMetadataValues(metadata) {
|
|
17278
|
+
const values = {};
|
|
17279
|
+
for (const slug of BUILT_IN_EXTRACTOR_SLUGS) {
|
|
17280
|
+
const { metadataField } = BUILT_IN_METADATA_FIELDS[slug];
|
|
17281
|
+
values[slug] = metadata[metadataField];
|
|
17282
|
+
}
|
|
15099
17283
|
return values;
|
|
15100
17284
|
}
|
|
15101
17285
|
function getPriorExtractedValues(metadata, extractors) {
|
|
@@ -15143,9 +17327,11 @@ async function applyExtractorHooks(opts) {
|
|
|
15143
17327
|
const values = normalizeExtractedValues(opts.values) ?? {};
|
|
15144
17328
|
const failures = [...opts.failures ?? []];
|
|
15145
17329
|
for (const extractor of opts.extractors) {
|
|
15146
|
-
|
|
15147
|
-
const
|
|
15148
|
-
if (!
|
|
17330
|
+
const isHook = extractor.mode === "hook";
|
|
17331
|
+
const hasValue = Object.prototype.hasOwnProperty.call(values, extractor.slug);
|
|
17332
|
+
if (!isHook && !hasValue) continue;
|
|
17333
|
+
const current = isHook ? opts.rawObservations : values[extractor.slug];
|
|
17334
|
+
if (!isHook && !isPresentExtractedValue(current)) {
|
|
15149
17335
|
delete values[extractor.slug];
|
|
15150
17336
|
continue;
|
|
15151
17337
|
}
|
|
@@ -15156,14 +17342,19 @@ async function applyExtractorHooks(opts) {
|
|
|
15156
17342
|
extractor,
|
|
15157
17343
|
threadId: opts.threadId,
|
|
15158
17344
|
resourceId: opts.resourceId,
|
|
15159
|
-
previous: opts.previousValues?.[extractor.slug],
|
|
17345
|
+
previous: isHook ? void 0 : opts.previousValues?.[extractor.slug],
|
|
15160
17346
|
current,
|
|
17347
|
+
rawObservations: opts.rawObservations,
|
|
17348
|
+
recentMessages: opts.recentMessages,
|
|
15161
17349
|
mainAgent: opts.mainAgent,
|
|
15162
17350
|
memory: opts.memory,
|
|
15163
17351
|
sendSignal: opts.sendSignal,
|
|
17352
|
+
sendStateSignal: opts.sendStateSignal,
|
|
17353
|
+
writer: opts.writer,
|
|
17354
|
+
abortSignal: opts.abortSignal,
|
|
15164
17355
|
requestContext: opts.requestContext
|
|
15165
17356
|
});
|
|
15166
|
-
if (hookValue === void 0) continue;
|
|
17357
|
+
if (isHook || hookValue === void 0) continue;
|
|
15167
17358
|
const parsed = extractor.schema.safeParse(hookValue);
|
|
15168
17359
|
if (parsed.success) values[extractor.slug] = parsed.data;
|
|
15169
17360
|
else {
|
|
@@ -15174,7 +17365,8 @@ async function applyExtractorHooks(opts) {
|
|
|
15174
17365
|
});
|
|
15175
17366
|
}
|
|
15176
17367
|
} catch (error) {
|
|
15177
|
-
|
|
17368
|
+
if (opts.abortSignal?.aborted) throw error;
|
|
17369
|
+
if (!isHook) delete values[extractor.slug];
|
|
15178
17370
|
failures.push({
|
|
15179
17371
|
slug: extractor.slug,
|
|
15180
17372
|
error: error instanceof Error ? error.message : String(error)
|
|
@@ -16239,11 +18431,11 @@ function formatObserverAttachmentPlaceholder(part, counter) {
|
|
|
16239
18431
|
const label = resolveObserverAttachmentLabel(part);
|
|
16240
18432
|
return label ? `[${attachmentType} #${attachmentId}: ${label}]` : `[${attachmentType} #${attachmentId}]`;
|
|
16241
18433
|
}
|
|
16242
|
-
function isRecord$
|
|
18434
|
+
function isRecord$3(value) {
|
|
16243
18435
|
return !!value && typeof value === "object";
|
|
16244
18436
|
}
|
|
16245
18437
|
function mapToolResultBlockToAttachment(block) {
|
|
16246
|
-
if (!isRecord$
|
|
18438
|
+
if (!isRecord$3(block) || typeof block.type !== "string") return;
|
|
16247
18439
|
const mediaType = typeof block.mediaType === "string" ? block.mediaType : void 0;
|
|
16248
18440
|
const filename = typeof block.filename === "string" ? block.filename : void 0;
|
|
16249
18441
|
switch (block.type) {
|
|
@@ -16304,7 +18496,7 @@ function mapToolResultBlockToAttachment(block) {
|
|
|
16304
18496
|
}
|
|
16305
18497
|
}
|
|
16306
18498
|
function extractToolResultAttachments(result, counter, attachmentFilter) {
|
|
16307
|
-
if (!isRecord$
|
|
18499
|
+
if (!isRecord$3(result) || result.type !== "content" || !Array.isArray(result.value)) return {
|
|
16308
18500
|
resultWithoutAttachments: result,
|
|
16309
18501
|
attachments: []
|
|
16310
18502
|
};
|
|
@@ -16318,7 +18510,7 @@ function extractToolResultAttachments(result, counter, attachmentFilter) {
|
|
|
16318
18510
|
if (shouldIncludeObserverAttachment(attachment, attachmentFilter)) attachments.push(toObserverInputAttachmentPart(attachment));
|
|
16319
18511
|
const placeholder = formatObserverAttachmentPlaceholder(attachment, counter);
|
|
16320
18512
|
return {
|
|
16321
|
-
type: isRecord$
|
|
18513
|
+
type: isRecord$3(block) ? block.type : void 0,
|
|
16322
18514
|
placeholder
|
|
16323
18515
|
};
|
|
16324
18516
|
});
|
|
@@ -16861,17 +19053,17 @@ const TRANSIENT_MESSAGE_SUBSTRINGS = [
|
|
|
16861
19053
|
"connection reset",
|
|
16862
19054
|
"connection closed"
|
|
16863
19055
|
];
|
|
16864
|
-
function isRecord$
|
|
19056
|
+
function isRecord$2(value) {
|
|
16865
19057
|
return typeof value === "object" && value !== null;
|
|
16866
19058
|
}
|
|
16867
19059
|
function isAbortError(error) {
|
|
16868
|
-
if (!isRecord$
|
|
19060
|
+
if (!isRecord$2(error)) return false;
|
|
16869
19061
|
if (error.name === "AbortError") return true;
|
|
16870
19062
|
if (typeof error.code === "string" && error.code === "ABORT_ERR") return true;
|
|
16871
19063
|
return false;
|
|
16872
19064
|
}
|
|
16873
19065
|
function hasTransientMessage(value) {
|
|
16874
|
-
if (!isRecord$
|
|
19066
|
+
if (!isRecord$2(value)) return false;
|
|
16875
19067
|
const message = typeof value.message === "string" ? value.message.toLowerCase() : "";
|
|
16876
19068
|
if (message && TRANSIENT_MESSAGE_SUBSTRINGS.some((sub) => message.includes(sub))) return true;
|
|
16877
19069
|
if (typeof value.code === "string" && value.code.toUpperCase().startsWith("UND_ERR_")) return true;
|
|
@@ -16882,13 +19074,13 @@ function isRetryableHttpStatus(status) {
|
|
|
16882
19074
|
return status >= 500 && status <= 599;
|
|
16883
19075
|
}
|
|
16884
19076
|
function hasRetryableHttpStatus(value) {
|
|
16885
|
-
if (!isRecord$
|
|
19077
|
+
if (!isRecord$2(value)) return false;
|
|
16886
19078
|
if (typeof value.statusCode === "number" && isRetryableHttpStatus(value.statusCode)) return true;
|
|
16887
19079
|
if (typeof value.code === "number" && isRetryableHttpStatus(value.code)) return true;
|
|
16888
19080
|
return false;
|
|
16889
19081
|
}
|
|
16890
19082
|
function hasIsRetryableFlag(value) {
|
|
16891
|
-
if (!isRecord$
|
|
19083
|
+
if (!isRecord$2(value)) return false;
|
|
16892
19084
|
return value.isRetryable === true;
|
|
16893
19085
|
}
|
|
16894
19086
|
/**
|
|
@@ -16906,14 +19098,14 @@ function isTransientLLMError(error) {
|
|
|
16906
19098
|
if (isAbortError(error)) return false;
|
|
16907
19099
|
const visited = /* @__PURE__ */ new WeakSet();
|
|
16908
19100
|
function visit(candidate) {
|
|
16909
|
-
if (isRecord$
|
|
19101
|
+
if (isRecord$2(candidate)) {
|
|
16910
19102
|
if (visited.has(candidate)) return false;
|
|
16911
19103
|
visited.add(candidate);
|
|
16912
19104
|
}
|
|
16913
19105
|
if (hasTransientMessage(candidate)) return true;
|
|
16914
19106
|
if (hasRetryableHttpStatus(candidate)) return true;
|
|
16915
19107
|
if (hasIsRetryableFlag(candidate)) return true;
|
|
16916
|
-
if (isRecord$
|
|
19108
|
+
if (isRecord$2(candidate)) {
|
|
16917
19109
|
if (visit(candidate.cause)) return true;
|
|
16918
19110
|
if (visit(candidate.error)) return true;
|
|
16919
19111
|
}
|
|
@@ -20152,40 +22344,6 @@ function createWorkingMemoryTool(config, options = {}) {
|
|
|
20152
22344
|
};
|
|
20153
22345
|
}
|
|
20154
22346
|
//#endregion
|
|
20155
|
-
//#region src/processors/observational-memory/model-by-input-tokens.ts
|
|
20156
|
-
function isTieredModelTarget(model) {
|
|
20157
|
-
if (typeof model === "string") return true;
|
|
20158
|
-
if (!model || typeof model !== "object") return false;
|
|
20159
|
-
return "modelId" in model || "id" in model || "providerId" in model || "provider" in model || "doGenerate" in model && "doStream" in model;
|
|
20160
|
-
}
|
|
20161
|
-
function normalizeThresholds(config) {
|
|
20162
|
-
const entries = Object.entries(config.upTo);
|
|
20163
|
-
if (entries.length === 0) throw new Error("ModelByInputTokens requires at least one threshold in \"upTo\"");
|
|
20164
|
-
for (const [limitStr, model] of entries) {
|
|
20165
|
-
const limit = Number(limitStr);
|
|
20166
|
-
if (!Number.isFinite(limit) || limit <= 0) throw new Error(`ModelByInputTokens threshold keys must be positive numbers. Got: ${limitStr}`);
|
|
20167
|
-
if (!isTieredModelTarget(model)) throw new Error(`ModelByInputTokens requires a valid model target for threshold ${limitStr}`);
|
|
20168
|
-
}
|
|
20169
|
-
return entries.map(([limitStr, model]) => ({
|
|
20170
|
-
limit: Number(limitStr),
|
|
20171
|
-
model
|
|
20172
|
-
})).sort((a, b) => a.limit - b.limit);
|
|
20173
|
-
}
|
|
20174
|
-
var ModelByInputTokens = class {
|
|
20175
|
-
thresholds;
|
|
20176
|
-
constructor(config) {
|
|
20177
|
-
this.thresholds = normalizeThresholds(config);
|
|
20178
|
-
}
|
|
20179
|
-
resolve(inputTokens) {
|
|
20180
|
-
for (const { limit, model } of this.thresholds) if (inputTokens <= limit) return model;
|
|
20181
|
-
const maxLimit = this.thresholds[this.thresholds.length - 1].limit;
|
|
20182
|
-
throw new Error(`ModelByInputTokens: input token count (${inputTokens}) exceeds the largest configured threshold (${maxLimit}). Please configure a higher threshold or use a larger model.`);
|
|
20183
|
-
}
|
|
20184
|
-
getThresholds() {
|
|
20185
|
-
return this.thresholds.map((t) => t.limit);
|
|
20186
|
-
}
|
|
20187
|
-
};
|
|
20188
|
-
//#endregion
|
|
20189
22347
|
//#region src/processors/observational-memory/activation-ttl.ts
|
|
20190
22348
|
const MINUTE = 6e4;
|
|
20191
22349
|
const HOUR = 60 * MINUTE;
|
|
@@ -20993,6 +23151,39 @@ function reconcileObservationGroupsFromReflection(content, sourceObservations) {
|
|
|
20993
23151
|
if (derivedGroups.length > 0) return derivedGroups.map((group) => wrapInObservationGroup(group.content, group.range, group.id, void 0, group.kind)).join("\n\n");
|
|
20994
23152
|
return wrapInObservationGroup(normalizedContent, combineObservationGroupRanges(sourceGroups), generateAnchorId(), void 0, "reflection");
|
|
20995
23153
|
}
|
|
23154
|
+
function isRecord$1(value) {
|
|
23155
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
23156
|
+
}
|
|
23157
|
+
function isSubconsciousSignalMessage(message) {
|
|
23158
|
+
if (message.role !== "signal" || !isRecord$1(message.content)) return false;
|
|
23159
|
+
const contentMetadata = message.content.metadata;
|
|
23160
|
+
if (!isRecord$1(contentMetadata) || !isRecord$1(contentMetadata.signal)) return false;
|
|
23161
|
+
const signal = contentMetadata.signal;
|
|
23162
|
+
const metadata = signal.metadata;
|
|
23163
|
+
const attributes = signal.attributes;
|
|
23164
|
+
return signal.origin === "subconscious" || isRecord$1(metadata) && metadata.origin === "subconscious" || isRecord$1(attributes) && attributes.source === "subconscious";
|
|
23165
|
+
}
|
|
23166
|
+
function isSubconsciousSignalPart(part) {
|
|
23167
|
+
if (!part || typeof part !== "object") return false;
|
|
23168
|
+
const candidate = part;
|
|
23169
|
+
return candidate.type === "data-signal" && (candidate.data?.metadata?.origin === "subconscious" || candidate.data?.attributes?.source === "subconscious");
|
|
23170
|
+
}
|
|
23171
|
+
function stripSubconsciousSignals(messages) {
|
|
23172
|
+
return messages.flatMap((message) => {
|
|
23173
|
+
if (isSubconsciousSignalMessage(message)) return [];
|
|
23174
|
+
if (typeof message.content === "string") return [message];
|
|
23175
|
+
const parts = message.content.parts.filter((part) => !isSubconsciousSignalPart(part));
|
|
23176
|
+
if (parts.length === message.content.parts.length) return [message];
|
|
23177
|
+
if (parts.length === 0) return [];
|
|
23178
|
+
return [{
|
|
23179
|
+
...message,
|
|
23180
|
+
content: {
|
|
23181
|
+
...message.content,
|
|
23182
|
+
parts
|
|
23183
|
+
}
|
|
23184
|
+
}];
|
|
23185
|
+
});
|
|
23186
|
+
}
|
|
20996
23187
|
//#endregion
|
|
20997
23188
|
//#region src/processors/observational-memory/thresholds.ts
|
|
20998
23189
|
/**
|
|
@@ -21150,8 +23341,9 @@ var ObservationStrategy = class ObservationStrategy {
|
|
|
21150
23341
|
if (fresh?.lastObservedAt && record.lastObservedAt && fresh.lastObservedAt > record.lastObservedAt) return { observed: false };
|
|
21151
23342
|
}
|
|
21152
23343
|
const { messages, existingObservations } = await this.prepare();
|
|
23344
|
+
const observationMessages = stripSubconsciousSignals(messages);
|
|
21153
23345
|
await this.emitStartMarkers(cycleId);
|
|
21154
|
-
const output = await this.observe(existingObservations,
|
|
23346
|
+
const output = await this.observe(existingObservations, observationMessages);
|
|
21155
23347
|
const processed = await this.process(output, existingObservations);
|
|
21156
23348
|
await this.persist(processed);
|
|
21157
23349
|
await this.emitEndMarkers(cycleId, processed);
|
|
@@ -21166,6 +23358,7 @@ var ObservationStrategy = class ObservationStrategy {
|
|
|
21166
23358
|
abortSignal,
|
|
21167
23359
|
mainAgent: this.opts.agent,
|
|
21168
23360
|
sendSignal: this.opts.sendSignal,
|
|
23361
|
+
sendStateSignal: this.opts.sendStateSignal,
|
|
21169
23362
|
reflectionHooks,
|
|
21170
23363
|
requestContext,
|
|
21171
23364
|
observabilityContext: this.opts.observabilityContext
|
|
@@ -21453,11 +23646,16 @@ var SyncObservationStrategy = class extends ObservationStrategy {
|
|
|
21453
23646
|
values: result.extractedValues,
|
|
21454
23647
|
failures: result.extractionFailures,
|
|
21455
23648
|
previousValues: this.priorExtractedValues,
|
|
23649
|
+
rawObservations: result.observations,
|
|
23650
|
+
recentMessages: formatMessagesForObserver(this.opts.messages, { maxPartLength: 500 }),
|
|
21456
23651
|
threadId: this.opts.threadId,
|
|
21457
23652
|
resourceId: this.opts.resourceId,
|
|
21458
23653
|
mainAgent: this.opts.agent,
|
|
21459
23654
|
memory: this.deps.memory,
|
|
21460
23655
|
sendSignal: this.opts.sendSignal,
|
|
23656
|
+
sendStateSignal: this.opts.sendStateSignal,
|
|
23657
|
+
writer: this.opts.writer,
|
|
23658
|
+
abortSignal: this.opts.abortSignal,
|
|
21461
23659
|
requestContext: this.opts.requestContext
|
|
21462
23660
|
});
|
|
21463
23661
|
const output = {
|
|
@@ -21633,11 +23831,16 @@ var AsyncBufferObservationStrategy = class extends ObservationStrategy {
|
|
|
21633
23831
|
values: result.extractedValues,
|
|
21634
23832
|
failures: result.extractionFailures,
|
|
21635
23833
|
previousValues: this.priorExtractedValues,
|
|
23834
|
+
rawObservations: result.observations,
|
|
23835
|
+
recentMessages: formatMessagesForObserver(messages, { maxPartLength: 500 }),
|
|
21636
23836
|
threadId: this.opts.threadId,
|
|
21637
23837
|
resourceId: this.opts.resourceId,
|
|
21638
23838
|
mainAgent: this.opts.agent,
|
|
21639
23839
|
memory: this.deps.memory,
|
|
21640
23840
|
sendSignal: this.opts.sendSignal,
|
|
23841
|
+
sendStateSignal: this.opts.sendStateSignal,
|
|
23842
|
+
writer: this.opts.writer,
|
|
23843
|
+
abortSignal: this.opts.abortSignal,
|
|
21641
23844
|
requestContext: this.opts.requestContext
|
|
21642
23845
|
});
|
|
21643
23846
|
return {
|
|
@@ -21972,11 +24175,25 @@ var ResourceScopedObservationStrategy = class extends ObservationStrategy {
|
|
|
21972
24175
|
values: result.extractedValues,
|
|
21973
24176
|
failures: result.extractionFailures,
|
|
21974
24177
|
previousValues,
|
|
24178
|
+
rawObservations: result.observations,
|
|
24179
|
+
recentMessages: formatMessagesForObserver(threadMessages, { maxPartLength: 500 }),
|
|
21975
24180
|
threadId,
|
|
21976
24181
|
resourceId: this.resourceId,
|
|
21977
24182
|
mainAgent: this.opts.agent,
|
|
21978
24183
|
memory: this.deps.memory,
|
|
21979
|
-
sendSignal: this.opts.
|
|
24184
|
+
sendSignal: this.opts.agent ? async (signal) => {
|
|
24185
|
+
const delivery = this.opts.agent.sendSignal(signal, {
|
|
24186
|
+
resourceId: this.resourceId,
|
|
24187
|
+
threadId,
|
|
24188
|
+
ifActive: { behavior: "deliver" },
|
|
24189
|
+
ifIdle: { behavior: "persist" }
|
|
24190
|
+
});
|
|
24191
|
+
await delivery.accepted;
|
|
24192
|
+
return delivery.signal;
|
|
24193
|
+
} : void 0,
|
|
24194
|
+
sendStateSignal: this.opts.sendStateSignal,
|
|
24195
|
+
writer: this.opts.writer,
|
|
24196
|
+
abortSignal: this.opts.abortSignal,
|
|
21980
24197
|
requestContext: this.opts.requestContext
|
|
21981
24198
|
});
|
|
21982
24199
|
this.observationResults.push({
|
|
@@ -22295,18 +24512,21 @@ var ObservationStep = class {
|
|
|
22295
24512
|
messageList.removeByIds(candidates.map((msg) => msg.id));
|
|
22296
24513
|
for (const msg of candidates) messageList.add(msg, "memory");
|
|
22297
24514
|
}
|
|
22298
|
-
om.buffer({
|
|
24515
|
+
om.trackBackgroundWork(om.buffer({
|
|
22299
24516
|
threadId,
|
|
22300
24517
|
resourceId,
|
|
22301
24518
|
messages: unobservedMessages,
|
|
22302
24519
|
pendingTokens: statusSnapshot.pendingTokens,
|
|
22303
24520
|
record: statusSnapshot.record,
|
|
22304
24521
|
writer: this.turn.writer,
|
|
24522
|
+
agent: this.turn.agent,
|
|
24523
|
+
sendSignal: this.turn.sendSignal,
|
|
24524
|
+
sendStateSignal: this.turn.sendStateSignal,
|
|
22305
24525
|
requestContext: this.turn.requestContext,
|
|
22306
24526
|
observabilityContext: this.turn.observabilityContext
|
|
22307
24527
|
}).catch((err) => {
|
|
22308
24528
|
omDebug(`[OM:buffer] fire-and-forget buffer failed: ${err?.message}`);
|
|
22309
|
-
});
|
|
24529
|
+
}));
|
|
22310
24530
|
buffered = true;
|
|
22311
24531
|
}
|
|
22312
24532
|
const willObserveNow = statusSnapshot.shouldObserve && !hasIncompleteToolCalls;
|
|
@@ -22478,6 +24698,9 @@ var ObservationStep = class {
|
|
|
22478
24698
|
messages: observableMessages,
|
|
22479
24699
|
messageList,
|
|
22480
24700
|
trigger: "turn-sync",
|
|
24701
|
+
agent: this.turn.agent,
|
|
24702
|
+
sendSignal: this.turn.sendSignal,
|
|
24703
|
+
sendStateSignal: this.turn.sendStateSignal,
|
|
22481
24704
|
requestContext: this.turn.requestContext,
|
|
22482
24705
|
writer: this.turn.writer,
|
|
22483
24706
|
observabilityContext: this.turn.observabilityContext
|
|
@@ -22557,6 +24780,8 @@ var ObservationTurn = class {
|
|
|
22557
24780
|
agent;
|
|
22558
24781
|
/** Optional signal sender for processor-originated notifications. */
|
|
22559
24782
|
sendSignal;
|
|
24783
|
+
/** Optional state signal sender for processor-originated snapshots. */
|
|
24784
|
+
sendStateSignal;
|
|
22560
24785
|
/** Current actor model for this step. Updated by the processor before prepare(). */
|
|
22561
24786
|
actorModelContext;
|
|
22562
24787
|
/** The active assistant response message ID for this step. Updated by the processor before prepare(). */
|
|
@@ -22570,6 +24795,7 @@ var ObservationTurn = class {
|
|
|
22570
24795
|
this.messageList = opts.messageList;
|
|
22571
24796
|
this.agent = opts.agent;
|
|
22572
24797
|
this.sendSignal = opts.sendSignal;
|
|
24798
|
+
this.sendStateSignal = opts.sendStateSignal;
|
|
22573
24799
|
this.requestContext = opts.requestContext;
|
|
22574
24800
|
this.observabilityContext = opts.observabilityContext;
|
|
22575
24801
|
this.hooks = opts.hooks ?? {};
|
|
@@ -22674,7 +24900,7 @@ var ObservationTurn = class {
|
|
|
22674
24900
|
const allMessages = getObservableMessages(this.messageList);
|
|
22675
24901
|
const record = this._record;
|
|
22676
24902
|
const unobservedMessages = this.om.getUnobservedMessages(allMessages, record);
|
|
22677
|
-
if (unobservedMessages.length > 0) this.om.buffer({
|
|
24903
|
+
if (unobservedMessages.length > 0) this.om.trackBackgroundWork(this.om.buffer({
|
|
22678
24904
|
threadId: this.threadId,
|
|
22679
24905
|
resourceId: this.resourceId,
|
|
22680
24906
|
messages: unobservedMessages,
|
|
@@ -22682,13 +24908,14 @@ var ObservationTurn = class {
|
|
|
22682
24908
|
writer: this.writer,
|
|
22683
24909
|
agent: this.agent,
|
|
22684
24910
|
sendSignal: this.sendSignal,
|
|
24911
|
+
sendStateSignal: this.sendStateSignal,
|
|
22685
24912
|
requestContext: this.requestContext,
|
|
22686
24913
|
currentModel: this.actorModelContext,
|
|
22687
24914
|
observabilityContext: this.observabilityContext,
|
|
22688
24915
|
skipMinimumTokenCheck: true
|
|
22689
24916
|
}).catch((err) => {
|
|
22690
24917
|
omDebug(`[OM:turn.end] idle buffer failed: ${err?.message}`);
|
|
22691
|
-
});
|
|
24918
|
+
}));
|
|
22692
24919
|
}
|
|
22693
24920
|
return { record: this._record };
|
|
22694
24921
|
}
|
|
@@ -23090,6 +25317,7 @@ var ReflectorRunner = class {
|
|
|
23090
25317
|
persistMarkerToMessage;
|
|
23091
25318
|
getCompressionStartLevel;
|
|
23092
25319
|
memory;
|
|
25320
|
+
onReflectionCommitted;
|
|
23093
25321
|
mastra;
|
|
23094
25322
|
constructor(opts) {
|
|
23095
25323
|
this.reflectionConfig = opts.reflectionConfig;
|
|
@@ -23105,6 +25333,7 @@ var ReflectorRunner = class {
|
|
|
23105
25333
|
this.getCompressionStartLevel = opts.getCompressionStartLevel;
|
|
23106
25334
|
this.mastra = opts.mastra;
|
|
23107
25335
|
this.memory = opts.memory;
|
|
25336
|
+
this.onReflectionCommitted = opts.onReflectionCommitted;
|
|
23108
25337
|
}
|
|
23109
25338
|
__registerMastra(mastra) {
|
|
23110
25339
|
this.mastra = mastra;
|
|
@@ -23172,6 +25401,8 @@ var ReflectorRunner = class {
|
|
|
23172
25401
|
};
|
|
23173
25402
|
let reflectedTokens = 0;
|
|
23174
25403
|
let attemptNumber = 0;
|
|
25404
|
+
/** Observations from the previous attempt, used to detect a no-progress ladder. */
|
|
25405
|
+
let previousObservations;
|
|
23175
25406
|
while (currentLevel <= maxLevel) {
|
|
23176
25407
|
attemptNumber++;
|
|
23177
25408
|
const isRetry = attemptNumber > 1;
|
|
@@ -23245,6 +25476,11 @@ var ReflectorRunner = class {
|
|
|
23245
25476
|
omDebug(`[OM:callReflector] degenerate output persists at maxLevel=${maxLevel}, breaking`);
|
|
23246
25477
|
break;
|
|
23247
25478
|
}
|
|
25479
|
+
if (!parsed.degenerate && previousObservations !== void 0 && parsed.observations === previousObservations) {
|
|
25480
|
+
omDebug(`[OM:callReflector] attempt #${attemptNumber} returned output identical to the previous attempt; escalating cannot help, stopping the ladder`);
|
|
25481
|
+
break;
|
|
25482
|
+
}
|
|
25483
|
+
previousObservations = parsed.observations;
|
|
23248
25484
|
if (streamContext?.writer) {
|
|
23249
25485
|
const failedMarker = createObservationFailedMarker({
|
|
23250
25486
|
cycleId: streamContext.cycleId,
|
|
@@ -23301,6 +25537,8 @@ var ReflectorRunner = class {
|
|
|
23301
25537
|
mainAgent,
|
|
23302
25538
|
memory: this.memory,
|
|
23303
25539
|
sendSignal,
|
|
25540
|
+
writer: streamContext?.writer,
|
|
25541
|
+
abortSignal,
|
|
23304
25542
|
requestContext
|
|
23305
25543
|
});
|
|
23306
25544
|
const extractedValues = hookedValues.values;
|
|
@@ -23456,7 +25694,7 @@ var ReflectorRunner = class {
|
|
|
23456
25694
|
* "activated", "no buffer present", and "suppressed by overshoot guard"
|
|
23457
25695
|
* without re-deriving that state.
|
|
23458
25696
|
*/
|
|
23459
|
-
async tryActivateBufferedReflection(record, lockKey, writer, messageList, activationMetadata) {
|
|
25697
|
+
async tryActivateBufferedReflection(record, lockKey, writer, messageList, activationMetadata, committedContext) {
|
|
23460
25698
|
const bufferKey = this.buffering.getReflectionBufferKey(lockKey);
|
|
23461
25699
|
const asyncOp = BufferingCoordinator.asyncBufferingOps.get(bufferKey);
|
|
23462
25700
|
if (asyncOp) if (activationMetadata?.triggeredBy === "ttl" || activationMetadata?.triggeredBy === "provider_change") omDebug(`[OM:reflect] tryActivateBufferedReflection: async op in progress, not blocking for ${activationMetadata.triggeredBy} trigger`);
|
|
@@ -23475,7 +25713,8 @@ var ReflectorRunner = class {
|
|
|
23475
25713
|
}
|
|
23476
25714
|
const beforeTokens = freshRecord.observationTokenCount ?? 0;
|
|
23477
25715
|
const reflectedLineCount = freshRecord.reflectedObservationLineCount ?? 0;
|
|
23478
|
-
const
|
|
25716
|
+
const allLines = (freshRecord.activeObservations ?? "").split("\n");
|
|
25717
|
+
const unreflectedLines = allLines.slice(reflectedLineCount);
|
|
23479
25718
|
const unreflectedContent = unreflectedLines.join("\n").trim();
|
|
23480
25719
|
const combinedObservations = unreflectedContent ? `${freshRecord.bufferedReflection}\n\n${unreflectedContent}` : freshRecord.bufferedReflection;
|
|
23481
25720
|
const combinedTokenCount = this.tokenCounter.countObservations(combinedObservations);
|
|
@@ -23506,6 +25745,11 @@ var ReflectorRunner = class {
|
|
|
23506
25745
|
currentRecord: freshRecord,
|
|
23507
25746
|
tokenCount: combinedTokenCount
|
|
23508
25747
|
});
|
|
25748
|
+
if (committedContext) await this.notifyReflectionCommitted({
|
|
25749
|
+
...committedContext,
|
|
25750
|
+
observations: allLines.slice(0, reflectedLineCount).join("\n").trim(),
|
|
25751
|
+
writer
|
|
25752
|
+
});
|
|
23509
25753
|
BufferingCoordinator.lastBufferedBoundary.delete(bufferKey);
|
|
23510
25754
|
const afterRecord = await this.storage.getObservationalMemory(record.threadId, record.resourceId);
|
|
23511
25755
|
const afterTokens = afterRecord?.observationTokenCount ?? 0;
|
|
@@ -23541,13 +25785,21 @@ var ReflectorRunner = class {
|
|
|
23541
25785
|
BufferingCoordinator.reflectionBufferCycleIds.delete(bufferKey);
|
|
23542
25786
|
return { status: "activated" };
|
|
23543
25787
|
}
|
|
25788
|
+
async notifyReflectionCommitted(context) {
|
|
25789
|
+
if (!this.onReflectionCommitted || !context.parentThreadId || !context.resourceId) return;
|
|
25790
|
+
try {
|
|
25791
|
+
await this.onReflectionCommitted(context);
|
|
25792
|
+
} catch (error) {
|
|
25793
|
+
omDebug(`[OM:reflect] post-commit reflection agent failed: ${error}`);
|
|
25794
|
+
}
|
|
25795
|
+
}
|
|
23544
25796
|
/**
|
|
23545
25797
|
* Check if reflection needed and trigger if so.
|
|
23546
25798
|
* Supports both synchronous reflection and async buffered reflection.
|
|
23547
25799
|
* @internal Used by observation strategies. Do not call directly.
|
|
23548
25800
|
*/
|
|
23549
25801
|
async maybeReflect(opts) {
|
|
23550
|
-
const { record, observationTokens, writer, abortSignal, mainAgent, sendSignal, messageList, currentModel, reflectionHooks, requestContext, observabilityContext, lastActivityAt, threadId: requestedThreadId } = opts;
|
|
25802
|
+
const { record, observationTokens, writer, abortSignal, mainAgent, sendSignal, sendStateSignal, messageList, currentModel, reflectionHooks, requestContext, observabilityContext, lastActivityAt, threadId: requestedThreadId } = opts;
|
|
23551
25803
|
const lockKey = this.buffering.getLockKey(record.threadId, record.resourceId);
|
|
23552
25804
|
const reflectThreshold = getMaxThreshold(this.getEffectiveReflectionTokens(record));
|
|
23553
25805
|
const priorExtractedValues = await getThreadExtractedValues(this.storage, this.reflectionConfig.extractors, requestedThreadId ?? record.threadId);
|
|
@@ -23592,7 +25844,15 @@ var ReflectorRunner = class {
|
|
|
23592
25844
|
await this.storage.setReflectingFlag(record.id, false);
|
|
23593
25845
|
}
|
|
23594
25846
|
if (this.buffering.isAsyncReflectionEnabled()) {
|
|
23595
|
-
const activationResult = await this.tryActivateBufferedReflection(record, lockKey, writer, messageList, activationMetadata
|
|
25847
|
+
const activationResult = await this.tryActivateBufferedReflection(record, lockKey, writer, messageList, activationMetadata, {
|
|
25848
|
+
parentThreadId: requestedThreadId ?? record.threadId ?? "",
|
|
25849
|
+
resourceId: record.resourceId ?? "",
|
|
25850
|
+
requestContext,
|
|
25851
|
+
mainAgent,
|
|
25852
|
+
sendStateSignal,
|
|
25853
|
+
abortSignal,
|
|
25854
|
+
observabilityContext
|
|
25855
|
+
});
|
|
23596
25856
|
if (activationResult.status === "activated") return;
|
|
23597
25857
|
if (activationResult.status === "suppressed") {
|
|
23598
25858
|
omDebug(`[OM:reflect] skipping sync fallback / re-buffer after suppressed early ${activationMetadata.triggeredBy} activation (reason=${activationResult.reason})`);
|
|
@@ -23663,6 +25923,17 @@ var ReflectorRunner = class {
|
|
|
23663
25923
|
reflection: reflectResult.observations,
|
|
23664
25924
|
tokenCount: reflectionTokenCount
|
|
23665
25925
|
});
|
|
25926
|
+
await this.notifyReflectionCommitted({
|
|
25927
|
+
parentThreadId: requestedThreadId ?? record.threadId ?? "",
|
|
25928
|
+
resourceId: record.resourceId ?? "",
|
|
25929
|
+
observations: record.activeObservations ?? "",
|
|
25930
|
+
requestContext,
|
|
25931
|
+
mainAgent,
|
|
25932
|
+
sendStateSignal,
|
|
25933
|
+
writer,
|
|
25934
|
+
abortSignal,
|
|
25935
|
+
observabilityContext
|
|
25936
|
+
});
|
|
23666
25937
|
if (writer && streamContext) {
|
|
23667
25938
|
const endMarker = createObservationEndMarker({
|
|
23668
25939
|
cycleId: streamContext.cycleId,
|
|
@@ -24192,6 +26463,7 @@ var ObservationalMemory = class ObservationalMemory {
|
|
|
24192
26463
|
hasher = (0, xxhash_wasm.default)();
|
|
24193
26464
|
mastra;
|
|
24194
26465
|
memory;
|
|
26466
|
+
curationCadence;
|
|
24195
26467
|
/**
|
|
24196
26468
|
* Track message IDs observed during this instance's lifetime.
|
|
24197
26469
|
* Prevents re-observing messages when per-thread lastObservedAt cursors
|
|
@@ -24223,6 +26495,39 @@ var ObservationalMemory = class ObservationalMemory {
|
|
|
24223
26495
|
*/
|
|
24224
26496
|
recordInitializations = /* @__PURE__ */ new Map();
|
|
24225
26497
|
/**
|
|
26498
|
+
* In-flight fire-and-forget background cycles (buffered observation, reflection).
|
|
26499
|
+
* Tracked so callers can join them via `settled()` before closing a storage
|
|
26500
|
+
* connection they own — otherwise a background cycle's tail statements race the close.
|
|
26501
|
+
*/
|
|
26502
|
+
pendingBackgroundWork = /* @__PURE__ */ new Set();
|
|
26503
|
+
/**
|
|
26504
|
+
* Register fire-and-forget background work so `settled()` can join it.
|
|
26505
|
+
* Returns the original promise so callers keep their own rejection handling.
|
|
26506
|
+
*/
|
|
26507
|
+
trackBackgroundWork(work) {
|
|
26508
|
+
const tracked = work.catch(() => {}).finally(() => {
|
|
26509
|
+
this.pendingBackgroundWork.delete(tracked);
|
|
26510
|
+
});
|
|
26511
|
+
this.pendingBackgroundWork.add(tracked);
|
|
26512
|
+
return work;
|
|
26513
|
+
}
|
|
26514
|
+
/**
|
|
26515
|
+
* Resolve once all background observational-memory work started so far has finished.
|
|
26516
|
+
*
|
|
26517
|
+
* Background cycles enqueue further background work (a buffered observation can
|
|
26518
|
+
* trigger a reflection, which runs nested agent streams), so this drains repeatedly
|
|
26519
|
+
* until nothing is left rather than joining a single snapshot.
|
|
26520
|
+
*/
|
|
26521
|
+
async settled() {
|
|
26522
|
+
const maxDrainRounds = 100;
|
|
26523
|
+
for (let round = 0; round < maxDrainRounds; round++) {
|
|
26524
|
+
if (this.pendingBackgroundWork.size === 0) return;
|
|
26525
|
+
await Promise.allSettled([...this.pendingBackgroundWork]);
|
|
26526
|
+
}
|
|
26527
|
+
if (this.pendingBackgroundWork.size === 0) return;
|
|
26528
|
+
omError(`[OM:settled] background work still pending after ${maxDrainRounds} drain rounds; giving up waiting`);
|
|
26529
|
+
}
|
|
26530
|
+
/**
|
|
24226
26531
|
* Acquire a lock for the given key, execute the callback, then release.
|
|
24227
26532
|
* If a lock is already held, waits for it to be released before acquiring.
|
|
24228
26533
|
*/
|
|
@@ -24256,6 +26561,7 @@ var ObservationalMemory = class ObservationalMemory {
|
|
|
24256
26561
|
this.hooks = config.hooks;
|
|
24257
26562
|
this.mastra = config.mastra;
|
|
24258
26563
|
this.memory = config.memory;
|
|
26564
|
+
this.curationCadence = config.curationCadence;
|
|
24259
26565
|
const resolveModel = (model, defaultModel) => model === "default" ? defaultModel : model;
|
|
24260
26566
|
const observationModel = resolveModel(config.model, OBSERVATIONAL_MEMORY_DEFAULTS.observation.model) ?? resolveModel(config.observation?.model, OBSERVATIONAL_MEMORY_DEFAULTS.observation.model) ?? resolveModel(config.reflection?.model, OBSERVATIONAL_MEMORY_DEFAULTS.observation.model) ?? OBSERVATIONAL_MEMORY_DEFAULTS.observation.model;
|
|
24261
26567
|
const reflectionModel = resolveModel(config.model, OBSERVATIONAL_MEMORY_DEFAULTS.reflection.model) ?? resolveModel(config.reflection?.model, OBSERVATIONAL_MEMORY_DEFAULTS.reflection.model) ?? resolveModel(config.observation?.model, OBSERVATIONAL_MEMORY_DEFAULTS.reflection.model) ?? OBSERVATIONAL_MEMORY_DEFAULTS.reflection.model;
|
|
@@ -24351,7 +26657,8 @@ var ObservationalMemory = class ObservationalMemory {
|
|
|
24351
26657
|
getCompressionStartLevel: (rc) => this.getCompressionStartLevel(rc),
|
|
24352
26658
|
resolveModel: (inputTokens) => this.resolveReflectionModel(inputTokens),
|
|
24353
26659
|
mastra: config.mastra,
|
|
24354
|
-
memory: this.memory
|
|
26660
|
+
memory: this.memory,
|
|
26661
|
+
onReflectionCommitted: config.onReflectionCommitted
|
|
24355
26662
|
});
|
|
24356
26663
|
this.validateBufferConfig();
|
|
24357
26664
|
omDebug(`[OM:init] new ObservationalMemory instance created — scope=${this.scope}, messageTokens=${JSON.stringify(this.observationConfig.messageTokens)}, obsAsyncEnabled=${this.buffering.isAsyncObservationEnabled()}, bufferTokens=${this.observationConfig.bufferTokens}, bufferActivation=${this.observationConfig.bufferActivation}, blockAfter=${this.observationConfig.blockAfter}, reflectionTokens=${this.reflectionConfig.observationTokens}, refAsyncEnabled=${this.buffering.isAsyncReflectionEnabled()}, refAsyncActivation=${this.reflectionConfig.bufferActivation}, refBlockAfter=${this.reflectionConfig.blockAfter}`);
|
|
@@ -25223,7 +27530,7 @@ ${formattedMessages}
|
|
|
25223
27530
|
if (!this.buffering.isAsyncObservationEnabled()) return false;
|
|
25224
27531
|
const lockKey = this.buffering.getLockKey(opts.threadId, opts.resourceId);
|
|
25225
27532
|
const shouldTrigger = this.buffering.shouldTriggerAsyncObservation(opts.pendingTokens, lockKey, opts.record, this.storage, opts.threshold);
|
|
25226
|
-
if (shouldTrigger) this.startAsyncBufferedObservation(opts.record, opts.threadId, opts.unobservedMessages, lockKey, opts.writer, opts.unbufferedPendingTokens, opts.requestContext);
|
|
27533
|
+
if (shouldTrigger) this.trackBackgroundWork(this.startAsyncBufferedObservation(opts.record, opts.threadId, opts.unobservedMessages, lockKey, opts.writer, opts.unbufferedPendingTokens, opts.requestContext));
|
|
25227
27534
|
return shouldTrigger;
|
|
25228
27535
|
}
|
|
25229
27536
|
isMessageList(value) {
|
|
@@ -25825,6 +28132,7 @@ ${formattedMessages}
|
|
|
25825
28132
|
writer,
|
|
25826
28133
|
agent: opts.agent,
|
|
25827
28134
|
sendSignal: opts.sendSignal,
|
|
28135
|
+
sendStateSignal: opts.sendStateSignal,
|
|
25828
28136
|
requestContext,
|
|
25829
28137
|
currentModel: opts.currentModel,
|
|
25830
28138
|
observabilityContext
|
|
@@ -26169,6 +28477,8 @@ ${formattedMessages}
|
|
|
26169
28477
|
messageList: opts.messageList,
|
|
26170
28478
|
reflectionHooks,
|
|
26171
28479
|
agent: opts.agent,
|
|
28480
|
+
sendSignal: opts.sendSignal,
|
|
28481
|
+
sendStateSignal: opts.sendStateSignal,
|
|
26172
28482
|
requestContext,
|
|
26173
28483
|
writer: opts.writer,
|
|
26174
28484
|
observabilityContext: opts.observabilityContext
|
|
@@ -26189,6 +28499,9 @@ ${formattedMessages}
|
|
|
26189
28499
|
});
|
|
26190
28500
|
const record = await this.getOrCreateRecord(threadId, resourceId);
|
|
26191
28501
|
const reflected = record.generationCount > generationBefore && generationBefore >= 0;
|
|
28502
|
+
if (observed) this.maybeTriggerCadenceCuration(threadId, resourceId, record, requestContext).catch((error) => {
|
|
28503
|
+
omDebug(`[OM:observe] cadence curation failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
28504
|
+
});
|
|
26192
28505
|
return {
|
|
26193
28506
|
observed,
|
|
26194
28507
|
reflected,
|
|
@@ -26196,6 +28509,31 @@ ${formattedMessages}
|
|
|
26196
28509
|
};
|
|
26197
28510
|
}
|
|
26198
28511
|
/**
|
|
28512
|
+
* Count committed observation runs on the sync observe path and run the curator every
|
|
28513
|
+
* `curationCadence` runs. The counter is persisted at `record.config.subconscious.observationRuns`
|
|
28514
|
+
* (the OM record's config jsonb, deep-merged; threshold resolution only reads `_overrides`, so the
|
|
28515
|
+
* sibling key is inert). Concurrent commits can miscount — the curator run is advisory, and the
|
|
28516
|
+
* curation cursor is the real serializer. The async-buffer lane bypasses observe(); deployments
|
|
28517
|
+
* that gate on this cadence (factory's resource scope) disable async buffering.
|
|
28518
|
+
*/
|
|
28519
|
+
async maybeTriggerCadenceCuration(threadId, resourceId, record, requestContext) {
|
|
28520
|
+
const cadence = this.curationCadence;
|
|
28521
|
+
const memory = this.memory;
|
|
28522
|
+
if (!cadence || cadence < 1 || !memory) return;
|
|
28523
|
+
const runs = ((record.config ?? {}).subconscious?.observationRuns ?? 0) + 1;
|
|
28524
|
+
const fire = runs >= cadence;
|
|
28525
|
+
await this.storage.updateObservationalMemoryConfig({
|
|
28526
|
+
id: record.id,
|
|
28527
|
+
config: { subconscious: { observationRuns: fire ? 0 : runs } }
|
|
28528
|
+
});
|
|
28529
|
+
if (!fire) return;
|
|
28530
|
+
omDebug(`[OM:observe] cadence curation outcome=${(await memory.runCuration({
|
|
28531
|
+
threadId,
|
|
28532
|
+
resourceId: resourceId ?? threadId,
|
|
28533
|
+
requestContext
|
|
28534
|
+
})).outcome} thread=${threadId}`);
|
|
28535
|
+
}
|
|
28536
|
+
/**
|
|
26199
28537
|
* Manually trigger reflection with optional guidance prompt.
|
|
26200
28538
|
*
|
|
26201
28539
|
* @example
|
|
@@ -26213,6 +28551,18 @@ ${formattedMessages}
|
|
|
26213
28551
|
record,
|
|
26214
28552
|
usage: void 0
|
|
26215
28553
|
};
|
|
28554
|
+
if (record.isReflecting) {
|
|
28555
|
+
if (isOpActiveInProcess(record.id, "reflecting")) {
|
|
28556
|
+
omDebug(`[OM:reflect] isReflecting=true and active in this process, skipping`);
|
|
28557
|
+
return {
|
|
28558
|
+
reflected: false,
|
|
28559
|
+
record,
|
|
28560
|
+
usage: void 0
|
|
28561
|
+
};
|
|
28562
|
+
}
|
|
28563
|
+
omDebug(`[OM:reflect] isReflecting=true but NOT active in this process — stale flag from dead process, clearing`);
|
|
28564
|
+
await this.storage.setReflectingFlag(record.id, false);
|
|
28565
|
+
}
|
|
26216
28566
|
await this.storage.setReflectingFlag(record.id, true);
|
|
26217
28567
|
registerOp(record.id, "reflecting");
|
|
26218
28568
|
const hooks = this.composeHooks(void 0, {
|
|
@@ -26398,6 +28748,8 @@ ${formattedMessages}
|
|
|
26398
28748
|
resourceId: opts.resourceId,
|
|
26399
28749
|
messageList: opts.messageList,
|
|
26400
28750
|
agent: opts.agent,
|
|
28751
|
+
sendSignal: opts.sendSignal,
|
|
28752
|
+
sendStateSignal: opts.sendStateSignal,
|
|
26401
28753
|
observabilityContext: opts.observabilityContext,
|
|
26402
28754
|
hooks: opts.hooks
|
|
26403
28755
|
});
|
|
@@ -26624,6 +28976,7 @@ var ObservationalMemoryProcessor = class {
|
|
|
26624
28976
|
});
|
|
26625
28977
|
this.turn.writer = writer;
|
|
26626
28978
|
this.turn.sendSignal = args.sendSignal;
|
|
28979
|
+
this.turn.sendStateSignal = args.sendStateSignal;
|
|
26627
28980
|
this.turn.agent = args.agent;
|
|
26628
28981
|
this.turn.requestContext = requestContext;
|
|
26629
28982
|
await this.turn.start(this.memory);
|
|
@@ -26768,6 +29121,7 @@ function getObservationsAsOf(activeObservations, asOf) {
|
|
|
26768
29121
|
//#region src/processors/observational-memory/index.ts
|
|
26769
29122
|
var observational_memory_exports = /* @__PURE__ */ __exportAll({
|
|
26770
29123
|
Extractor: () => Extractor,
|
|
29124
|
+
KnowledgeSemanticIndexCoordinator: () => KnowledgeSemanticIndexCoordinator,
|
|
26771
29125
|
ModelByInputTokens: () => ModelByInputTokens,
|
|
26772
29126
|
OBSERVATIONAL_MEMORY_DEFAULTS: () => OBSERVATIONAL_MEMORY_DEFAULTS,
|
|
26773
29127
|
OBSERVATION_CONTEXT_INSTRUCTIONS: () => OBSERVATION_CONTEXT_INSTRUCTIONS,
|
|
@@ -26776,10 +29130,16 @@ var observational_memory_exports = /* @__PURE__ */ __exportAll({
|
|
|
26776
29130
|
OBSERVER_SYSTEM_PROMPT: () => OBSERVER_SYSTEM_PROMPT,
|
|
26777
29131
|
ObservationalMemory: () => ObservationalMemory,
|
|
26778
29132
|
ObservationalMemoryProcessor: () => ObservationalMemoryProcessor,
|
|
29133
|
+
SUBCONSCIOUS_ACTIVITY_STATE_ID: () => SUBCONSCIOUS_ACTIVITY_STATE_ID,
|
|
29134
|
+
StaleKnowledgeSemanticIndexError: () => StaleKnowledgeSemanticIndexError,
|
|
29135
|
+
Subconscious: () => Subconscious,
|
|
29136
|
+
SubconsciousCaptureExtractor: () => SubconsciousCaptureExtractor,
|
|
29137
|
+
SubconsciousRemindExtractor: () => SubconsciousRemindExtractor,
|
|
26779
29138
|
TokenCounter: () => TokenCounter,
|
|
26780
29139
|
WorkingMemoryExtractor: () => WorkingMemoryExtractor,
|
|
26781
29140
|
buildObserverPrompt: () => buildObserverPrompt,
|
|
26782
29141
|
buildObserverSystemPrompt: () => buildObserverSystemPrompt,
|
|
29142
|
+
buildSubconsciousActivitySnapshot: () => buildSubconsciousActivitySnapshot,
|
|
26783
29143
|
combineObservationGroupRanges: () => combineObservationGroupRanges,
|
|
26784
29144
|
deriveObservationGroupProvenance: () => deriveObservationGroupProvenance,
|
|
26785
29145
|
extractCurrentTask: () => extractCurrentTask,
|
|
@@ -26791,10 +29151,13 @@ var observational_memory_exports = /* @__PURE__ */ __exportAll({
|
|
|
26791
29151
|
parseAnchorId: () => parseAnchorId,
|
|
26792
29152
|
parseObservationGroups: () => parseObservationGroups,
|
|
26793
29153
|
parseObserverOutput: () => parseObserverOutput,
|
|
29154
|
+
publishSubconsciousActivity: () => publishSubconsciousActivity,
|
|
26794
29155
|
reconcileObservationGroupsFromReflection: () => reconcileObservationGroupsFromReflection,
|
|
26795
29156
|
renderObservationGroupsForReflection: () => renderObservationGroupsForReflection,
|
|
29157
|
+
renderSubconsciousActivity: () => renderSubconsciousActivity,
|
|
26796
29158
|
stripEphemeralAnchorIds: () => stripEphemeralAnchorIds,
|
|
26797
29159
|
stripObservationGroups: () => stripObservationGroups,
|
|
29160
|
+
subconsciousCaptureSchema: () => subconsciousCaptureSchema,
|
|
26798
29161
|
summarizeConversation: () => summarizeConversation,
|
|
26799
29162
|
wrapInObservationGroup: () => wrapInObservationGroup
|
|
26800
29163
|
});
|
|
@@ -27037,10 +29400,11 @@ const DEFAULT_EMBEDDING_CACHE_MAX_SIZE = 1e3;
|
|
|
27037
29400
|
* Concrete implementation of MastraMemory that adds support for thread configuration
|
|
27038
29401
|
* and message injection.
|
|
27039
29402
|
*/
|
|
27040
|
-
var Memory = class extends _mastra_core_memory.MastraMemory {
|
|
29403
|
+
var Memory = class Memory extends _mastra_core_memory.MastraMemory {
|
|
27041
29404
|
_omEngine;
|
|
27042
29405
|
_omEngineInstance;
|
|
27043
29406
|
_mastraInstance;
|
|
29407
|
+
_knowledgeSemanticIndex;
|
|
27044
29408
|
/**
|
|
27045
29409
|
* Every vector cleanup that deleteThread or deleteMessages started in the background.
|
|
27046
29410
|
* Callers do not wait for the cleanup, so this handle is the only join point.
|
|
@@ -27053,6 +29417,25 @@ var Memory = class extends _mastra_core_memory.MastraMemory {
|
|
|
27053
29417
|
trackVectorCleanup(cleanup) {
|
|
27054
29418
|
this.pendingVectorCleanup = Promise.allSettled([this.pendingVectorCleanup, cleanup]).then(() => void 0);
|
|
27055
29419
|
}
|
|
29420
|
+
/**
|
|
29421
|
+
* Resolve once all background work this Memory started has finished: observational-memory
|
|
29422
|
+
* cycles (buffered observation and reflection, including the nested agent runs they spawn)
|
|
29423
|
+
* and vector cleanup from `deleteThread` / `deleteMessages`.
|
|
29424
|
+
*
|
|
29425
|
+
* Callers that own the storage connection should await this before closing it, otherwise
|
|
29426
|
+
* background statements can race the close.
|
|
29427
|
+
*
|
|
29428
|
+
* ```ts
|
|
29429
|
+
* await agent.generate('hello', { memory: { thread, resource } });
|
|
29430
|
+
* await memory.settled();
|
|
29431
|
+
* await store.close();
|
|
29432
|
+
* ```
|
|
29433
|
+
*/
|
|
29434
|
+
async settled() {
|
|
29435
|
+
await this.pendingVectorCleanup;
|
|
29436
|
+
await (this._omEngine ? await this._omEngine : this._omEngineInstance)?.settled();
|
|
29437
|
+
await this.pendingVectorCleanup;
|
|
29438
|
+
}
|
|
27056
29439
|
/** The shared ObservationalMemory engine. Lazily created on first access. */
|
|
27057
29440
|
get omEngine() {
|
|
27058
29441
|
if (!this._omEngine) this._omEngine = this._initOMEngine().then((engine) => {
|
|
@@ -27069,7 +29452,60 @@ var Memory = class extends _mastra_core_memory.MastraMemory {
|
|
|
27069
29452
|
else this._omEngine?.then((engine) => engine?.__registerMastra(mastra));
|
|
27070
29453
|
}
|
|
27071
29454
|
getMergedThreadConfig(config) {
|
|
27072
|
-
|
|
29455
|
+
const merged = super.getMergedThreadConfig(config);
|
|
29456
|
+
return this.applyManagedWorkingMemoryDefaults(this.applySubconsciousDefaults(merged));
|
|
29457
|
+
}
|
|
29458
|
+
applySubconsciousDefaults(config) {
|
|
29459
|
+
const omConfig = normalizeObservationalMemoryConfig(config.observationalMemory);
|
|
29460
|
+
if (!omConfig?.experimental_subconscious) return config;
|
|
29461
|
+
if (!(omConfig.experimental_subconscious instanceof Subconscious)) throw new Error("observationalMemory.experimental_subconscious must be a Subconscious instance.");
|
|
29462
|
+
const observation = omConfig.observation ?? {};
|
|
29463
|
+
const extract = observation.extract ?? [];
|
|
29464
|
+
const existingSlugs = new Set(extract.map((extractor) => extractor.slug));
|
|
29465
|
+
const subconsciousExtractors = omConfig.experimental_subconscious.createObservationExtractors(observation.model ?? omConfig.model).filter((extractor) => !existingSlugs.has(extractor.slug));
|
|
29466
|
+
return {
|
|
29467
|
+
...config,
|
|
29468
|
+
observationalMemory: {
|
|
29469
|
+
...omConfig,
|
|
29470
|
+
observation: {
|
|
29471
|
+
...observation,
|
|
29472
|
+
extract: [...extract, ...subconsciousExtractors]
|
|
29473
|
+
}
|
|
29474
|
+
}
|
|
29475
|
+
};
|
|
29476
|
+
}
|
|
29477
|
+
/** Threads with a curation currently in flight in this process; guards same-process double-fire only. */
|
|
29478
|
+
_curationsInFlight = /* @__PURE__ */ new Set();
|
|
29479
|
+
/**
|
|
29480
|
+
* Run the subconscious curator directly over the pending fact worklist, without a reflection.
|
|
29481
|
+
* Cross-process serialization is the curation cursor's job; a lost race wastes one advisory run.
|
|
29482
|
+
*
|
|
29483
|
+
* Outcomes: `ran` (curator executed), `no-op` (empty worklist and no prompt, or no curate agent
|
|
29484
|
+
* configured), `skipped` (a curation for this thread is already in flight in this process), and
|
|
29485
|
+
* `no-model` (no per-agent, observational memory, or main-agent model could be resolved).
|
|
29486
|
+
*/
|
|
29487
|
+
async runCuration(options) {
|
|
29488
|
+
const omConfig = normalizeObservationalMemoryConfig(this.threadConfig.observationalMemory);
|
|
29489
|
+
const subconscious = omConfig?.experimental_subconscious;
|
|
29490
|
+
if (!omConfig || !(subconscious instanceof Subconscious)) return { outcome: "no-op" };
|
|
29491
|
+
if (this._curationsInFlight.has(options.threadId)) return { outcome: "skipped" };
|
|
29492
|
+
this._curationsInFlight.add(options.threadId);
|
|
29493
|
+
try {
|
|
29494
|
+
return { outcome: await createCuratorHandler(this, subconscious.resolved, new Memory({
|
|
29495
|
+
storage: this.storage,
|
|
29496
|
+
options: { observationalMemory: false }
|
|
29497
|
+
}), { omModel: omConfig.observation?.model ?? omConfig.model })({
|
|
29498
|
+
parentThreadId: options.threadId,
|
|
29499
|
+
resourceId: options.resourceId,
|
|
29500
|
+
observations: options.prompt ?? "",
|
|
29501
|
+
requestContext: options.requestContext
|
|
29502
|
+
}) === "ran" ? "ran" : "no-op" };
|
|
29503
|
+
} catch (error) {
|
|
29504
|
+
if (error instanceof Error && error.message.includes("requires the main agent to resolve its model")) return { outcome: "no-model" };
|
|
29505
|
+
throw error;
|
|
29506
|
+
} finally {
|
|
29507
|
+
this._curationsInFlight.delete(options.threadId);
|
|
29508
|
+
}
|
|
27073
29509
|
}
|
|
27074
29510
|
applyManagedWorkingMemoryDefaults(config) {
|
|
27075
29511
|
const omConfig = normalizeObservationalMemoryConfig(config.observationalMemory);
|
|
@@ -27113,6 +29549,28 @@ var Memory = class extends _mastra_core_memory.MastraMemory {
|
|
|
27113
29549
|
if (!this.vector) throw new Error("`retrieval: { vector: true }` requires a vector store. Pass a `vector` option to your Memory instance.");
|
|
27114
29550
|
if (!this.embedder) throw new Error("`retrieval: { vector: true }` requires an embedder. Pass an `embedder` option to your Memory instance.");
|
|
27115
29551
|
}
|
|
29552
|
+
if (omConfig?.experimental_subconscious) {
|
|
29553
|
+
if (!this.vector) throw new Error("Subconscious semantic knowledge requires a vector store. Pass a `vector` option to Memory.");
|
|
29554
|
+
if (!this.embedder) throw new Error("Subconscious semantic knowledge requires an embedder. Pass an `embedder` option to Memory.");
|
|
29555
|
+
}
|
|
29556
|
+
}
|
|
29557
|
+
async getKnowledgeStore() {
|
|
29558
|
+
const store = await this.storage.getStore("knowledge");
|
|
29559
|
+
if (!store) throw new Error(`Knowledge storage domain is not available on ${this.storage.constructor.name}`);
|
|
29560
|
+
return store;
|
|
29561
|
+
}
|
|
29562
|
+
async getKnowledgeSemanticIndex() {
|
|
29563
|
+
if (!this.vector || !this.embedder) throw new Error("Subconscious semantic knowledge requires both a vector store and an embedder.");
|
|
29564
|
+
this._knowledgeSemanticIndex ??= this.getKnowledgeStore().then((knowledge) => new KnowledgeSemanticIndexCoordinator({
|
|
29565
|
+
knowledge,
|
|
29566
|
+
vector: this.vector,
|
|
29567
|
+
embedder: this.embedder,
|
|
29568
|
+
embedderOptions: this.embedderOptions
|
|
29569
|
+
}));
|
|
29570
|
+
return this._knowledgeSemanticIndex;
|
|
29571
|
+
}
|
|
29572
|
+
async drainKnowledgeSemanticIndex(scope) {
|
|
29573
|
+
return (await this.getKnowledgeSemanticIndex()).drain(scope);
|
|
27116
29574
|
}
|
|
27117
29575
|
/**
|
|
27118
29576
|
* Gets the memory storage domain, throwing if not available.
|
|
@@ -27881,6 +30339,7 @@ ${workingMemory}`;
|
|
|
27881
30339
|
if (!_mastra_core_features.coreFeatures.has("observationalMemory")) throw new Error("Observational memory is enabled but the installed version of @mastra/core does not support it. Please upgrade @mastra/core to a version that includes observational memory support.");
|
|
27882
30340
|
if (omConfig.observation?.bufferTokens !== false && !_mastra_core_features.coreFeatures.has("asyncBuffering")) throw new Error("Observational memory async buffering is enabled by default but the installed version of @mastra/core does not support it. Either upgrade @mastra/core, @mastra/memory, and your storage adapter (@mastra/libsql, @mastra/pg, @mastra/mongodb, or @mastra/convex) to the latest version, or explicitly disable async buffering by setting `observation: { bufferTokens: false }` in your observationalMemory config.");
|
|
27883
30341
|
if (!_mastra_core_features.coreFeatures.has("request-response-id-rotation")) throw new Error("Observational memory requires @mastra/core support for request-response-id-rotation. Please bump @mastra/core to a newer version.");
|
|
30342
|
+
if (omConfig.experimental_subconscious) await this.getKnowledgeStore();
|
|
27884
30343
|
const { ObservationalMemory: OMClass } = await Promise.resolve().then(() => observational_memory_exports);
|
|
27885
30344
|
const onIndexObservations = this.hasRetrievalSearch(omConfig.retrieval) ? async (observation) => {
|
|
27886
30345
|
await this.indexObservation(observation);
|
|
@@ -27894,9 +30353,21 @@ ${workingMemory}`;
|
|
|
27894
30353
|
activateOnProviderChange: omConfig.activateOnProviderChange,
|
|
27895
30354
|
shareTokenBudget: omConfig.shareTokenBudget,
|
|
27896
30355
|
model: omConfig.model,
|
|
30356
|
+
curationCadence: omConfig.experimental_subconscious instanceof Subconscious ? omConfig.experimental_subconscious.resolved.curationCadence : void 0,
|
|
27897
30357
|
mastra: this._mastraInstance,
|
|
27898
30358
|
onIndexObservations,
|
|
27899
30359
|
hooks: omConfig.hooks,
|
|
30360
|
+
onReflectionCommitted: omConfig.experimental_subconscious instanceof Subconscious ? (() => {
|
|
30361
|
+
const resolved = omConfig.experimental_subconscious.resolved;
|
|
30362
|
+
const omModel = omConfig.observation?.model ?? omConfig.model;
|
|
30363
|
+
return composeReflectionAgentHandlers([createCuratorHandler(this, resolved, new Memory({
|
|
30364
|
+
storage: this.storage,
|
|
30365
|
+
options: { observationalMemory: false }
|
|
30366
|
+
}), { omModel }), createLearnerHandler(this, resolved, new Memory({
|
|
30367
|
+
storage: this.storage,
|
|
30368
|
+
options: { observationalMemory: false }
|
|
30369
|
+
}), { omModel })]);
|
|
30370
|
+
})() : void 0,
|
|
27900
30371
|
observation: omConfig.observation ? {
|
|
27901
30372
|
model: omConfig.observation.model,
|
|
27902
30373
|
messageTokens: omConfig.observation.messageTokens,
|
|
@@ -28286,6 +30757,7 @@ Notes:
|
|
|
28286
30757
|
retrievalScope: typeof omConfig.retrieval === "object" ? omConfig.retrieval.scope ?? "resource" : "resource",
|
|
28287
30758
|
searchEnabled: this.hasRetrievalSearch(omConfig.retrieval)
|
|
28288
30759
|
});
|
|
30760
|
+
if (omConfig?.experimental_subconscious instanceof Subconscious && omConfig.experimental_subconscious.resolved.tools) Object.assign(tools, createKnowledgeTools(this));
|
|
28289
30761
|
return tools;
|
|
28290
30762
|
}
|
|
28291
30763
|
/**
|
|
@@ -28766,6 +31238,8 @@ Notes:
|
|
|
28766
31238
|
if (om) processors.push(om);
|
|
28767
31239
|
const wm = await this.createWorkingMemoryStateProcessor(configuredProcessors, context);
|
|
28768
31240
|
if (wm) processors.push(wm);
|
|
31241
|
+
const pins = await this.createPinnedStateProcessor(configuredProcessors, context);
|
|
31242
|
+
if (pins) processors.push(pins);
|
|
28769
31243
|
return processors;
|
|
28770
31244
|
}
|
|
28771
31245
|
/**
|
|
@@ -28813,6 +31287,21 @@ Notes:
|
|
|
28813
31287
|
if (configuredProcessors.some((p) => !("workflow" in p) && p.id === WORKING_MEMORY_STATE_PROCESSOR_ID)) return null;
|
|
28814
31288
|
return new WorkingMemoryStateProcessor(this, runtimeMemory?.memoryConfig);
|
|
28815
31289
|
}
|
|
31290
|
+
/**
|
|
31291
|
+
* Creates a PinnedStateProcessor when Subconscious pins are enabled on the
|
|
31292
|
+
* merged thread config. The gate is the validated `resolved.pins` on the
|
|
31293
|
+
* Subconscious instance, never the raw user object. Returns null when pins
|
|
31294
|
+
* are off or the processor is already present in the user's configured
|
|
31295
|
+
* processors.
|
|
31296
|
+
*/
|
|
31297
|
+
async createPinnedStateProcessor(configuredProcessors = [], context) {
|
|
31298
|
+
const runtimeMemory = context?.get("MastraMemory");
|
|
31299
|
+
const subconscious = normalizeObservationalMemoryConfig(this.getMergedThreadConfig(runtimeMemory?.memoryConfig).observationalMemory)?.experimental_subconscious;
|
|
31300
|
+
if (!(subconscious instanceof Subconscious) || subconscious.resolved.pins === false) return null;
|
|
31301
|
+
const { PinnedStateProcessor, SUBCONSCIOUS_PINS_STATE_ID } = await Promise.resolve().then(() => subconscious_exports);
|
|
31302
|
+
if (configuredProcessors.some((p) => !("workflow" in p) && p.id === SUBCONSCIOUS_PINS_STATE_ID)) return null;
|
|
31303
|
+
return new PinnedStateProcessor({ getKnowledgeStore: () => this.storage.getStore("knowledge") });
|
|
31304
|
+
}
|
|
28816
31305
|
};
|
|
28817
31306
|
//#endregion
|
|
28818
31307
|
Object.defineProperty(exports, "Extractor", {
|
|
@@ -28821,6 +31310,12 @@ Object.defineProperty(exports, "Extractor", {
|
|
|
28821
31310
|
return Extractor;
|
|
28822
31311
|
}
|
|
28823
31312
|
});
|
|
31313
|
+
Object.defineProperty(exports, "KnowledgeSemanticIndexCoordinator", {
|
|
31314
|
+
enumerable: true,
|
|
31315
|
+
get: function() {
|
|
31316
|
+
return KnowledgeSemanticIndexCoordinator;
|
|
31317
|
+
}
|
|
31318
|
+
});
|
|
28824
31319
|
Object.defineProperty(exports, "Memory", {
|
|
28825
31320
|
enumerable: true,
|
|
28826
31321
|
get: function() {
|
|
@@ -28875,12 +31370,42 @@ Object.defineProperty(exports, "ObservationalMemoryProcessor", {
|
|
|
28875
31370
|
return ObservationalMemoryProcessor;
|
|
28876
31371
|
}
|
|
28877
31372
|
});
|
|
31373
|
+
Object.defineProperty(exports, "SUBCONSCIOUS_ACTIVITY_STATE_ID", {
|
|
31374
|
+
enumerable: true,
|
|
31375
|
+
get: function() {
|
|
31376
|
+
return SUBCONSCIOUS_ACTIVITY_STATE_ID;
|
|
31377
|
+
}
|
|
31378
|
+
});
|
|
28878
31379
|
Object.defineProperty(exports, "SUMMARIZE_THREAD_DEFAULTS", {
|
|
28879
31380
|
enumerable: true,
|
|
28880
31381
|
get: function() {
|
|
28881
31382
|
return SUMMARIZE_THREAD_DEFAULTS;
|
|
28882
31383
|
}
|
|
28883
31384
|
});
|
|
31385
|
+
Object.defineProperty(exports, "StaleKnowledgeSemanticIndexError", {
|
|
31386
|
+
enumerable: true,
|
|
31387
|
+
get: function() {
|
|
31388
|
+
return StaleKnowledgeSemanticIndexError;
|
|
31389
|
+
}
|
|
31390
|
+
});
|
|
31391
|
+
Object.defineProperty(exports, "Subconscious", {
|
|
31392
|
+
enumerable: true,
|
|
31393
|
+
get: function() {
|
|
31394
|
+
return Subconscious;
|
|
31395
|
+
}
|
|
31396
|
+
});
|
|
31397
|
+
Object.defineProperty(exports, "SubconsciousCaptureExtractor", {
|
|
31398
|
+
enumerable: true,
|
|
31399
|
+
get: function() {
|
|
31400
|
+
return SubconsciousCaptureExtractor;
|
|
31401
|
+
}
|
|
31402
|
+
});
|
|
31403
|
+
Object.defineProperty(exports, "SubconsciousRemindExtractor", {
|
|
31404
|
+
enumerable: true,
|
|
31405
|
+
get: function() {
|
|
31406
|
+
return SubconsciousRemindExtractor;
|
|
31407
|
+
}
|
|
31408
|
+
});
|
|
28884
31409
|
Object.defineProperty(exports, "TokenCounter", {
|
|
28885
31410
|
enumerable: true,
|
|
28886
31411
|
get: function() {
|
|
@@ -28923,6 +31448,12 @@ Object.defineProperty(exports, "buildObserverSystemPrompt", {
|
|
|
28923
31448
|
return buildObserverSystemPrompt;
|
|
28924
31449
|
}
|
|
28925
31450
|
});
|
|
31451
|
+
Object.defineProperty(exports, "buildSubconsciousActivitySnapshot", {
|
|
31452
|
+
enumerable: true,
|
|
31453
|
+
get: function() {
|
|
31454
|
+
return buildSubconsciousActivitySnapshot;
|
|
31455
|
+
}
|
|
31456
|
+
});
|
|
28926
31457
|
Object.defineProperty(exports, "combineObservationGroupRanges", {
|
|
28927
31458
|
enumerable: true,
|
|
28928
31459
|
get: function() {
|
|
@@ -29007,6 +31538,12 @@ Object.defineProperty(exports, "parseObserverOutput", {
|
|
|
29007
31538
|
return parseObserverOutput;
|
|
29008
31539
|
}
|
|
29009
31540
|
});
|
|
31541
|
+
Object.defineProperty(exports, "publishSubconsciousActivity", {
|
|
31542
|
+
enumerable: true,
|
|
31543
|
+
get: function() {
|
|
31544
|
+
return publishSubconsciousActivity;
|
|
31545
|
+
}
|
|
31546
|
+
});
|
|
29010
31547
|
Object.defineProperty(exports, "reconcileObservationGroupsFromReflection", {
|
|
29011
31548
|
enumerable: true,
|
|
29012
31549
|
get: function() {
|
|
@@ -29025,6 +31562,12 @@ Object.defineProperty(exports, "renderObservationGroupsForReflection", {
|
|
|
29025
31562
|
return renderObservationGroupsForReflection;
|
|
29026
31563
|
}
|
|
29027
31564
|
});
|
|
31565
|
+
Object.defineProperty(exports, "renderSubconsciousActivity", {
|
|
31566
|
+
enumerable: true,
|
|
31567
|
+
get: function() {
|
|
31568
|
+
return renderSubconsciousActivity;
|
|
31569
|
+
}
|
|
31570
|
+
});
|
|
29028
31571
|
Object.defineProperty(exports, "stripEphemeralAnchorIds", {
|
|
29029
31572
|
enumerable: true,
|
|
29030
31573
|
get: function() {
|
|
@@ -29037,6 +31580,12 @@ Object.defineProperty(exports, "stripObservationGroups", {
|
|
|
29037
31580
|
return stripObservationGroups;
|
|
29038
31581
|
}
|
|
29039
31582
|
});
|
|
31583
|
+
Object.defineProperty(exports, "subconsciousCaptureSchema", {
|
|
31584
|
+
enumerable: true,
|
|
31585
|
+
get: function() {
|
|
31586
|
+
return subconsciousCaptureSchema;
|
|
31587
|
+
}
|
|
31588
|
+
});
|
|
29040
31589
|
Object.defineProperty(exports, "summarizeConversation", {
|
|
29041
31590
|
enumerable: true,
|
|
29042
31591
|
get: function() {
|
|
@@ -29050,4 +31599,4 @@ Object.defineProperty(exports, "wrapInObservationGroup", {
|
|
|
29050
31599
|
}
|
|
29051
31600
|
});
|
|
29052
31601
|
|
|
29053
|
-
//# sourceMappingURL=src-
|
|
31602
|
+
//# sourceMappingURL=src-Ik0ksTOt.cjs.map
|