@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.
Files changed (72) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/docs/SKILL.md +2 -1
  3. package/dist/docs/assets/SOURCE_MAP.json +1 -1
  4. package/dist/docs/references/docs-storage.md +1 -0
  5. package/dist/docs/references/reference-memory-memory-class.md +1 -0
  6. package/dist/docs/references/reference-memory-settled.md +57 -0
  7. package/dist/docs/references/reference-processors-token-limiter-processor.md +4 -0
  8. package/dist/index.cjs +4 -1
  9. package/dist/index.d.ts +52 -1
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +2 -2
  12. package/dist/processors/index.cjs +11 -1
  13. package/dist/processors/index.js +2 -2
  14. package/dist/processors/observational-memory/extracted-values.d.ts +6 -1
  15. package/dist/processors/observational-memory/extracted-values.d.ts.map +1 -1
  16. package/dist/processors/observational-memory/extractor.d.ts +12 -4
  17. package/dist/processors/observational-memory/extractor.d.ts.map +1 -1
  18. package/dist/processors/observational-memory/index.d.ts +2 -0
  19. package/dist/processors/observational-memory/index.d.ts.map +1 -1
  20. package/dist/processors/observational-memory/observation-strategies/async-buffer.d.ts.map +1 -1
  21. package/dist/processors/observational-memory/observation-strategies/base.d.ts.map +1 -1
  22. package/dist/processors/observational-memory/observation-strategies/resource-scoped.d.ts.map +1 -1
  23. package/dist/processors/observational-memory/observation-strategies/sync.d.ts.map +1 -1
  24. package/dist/processors/observational-memory/observation-strategies/types.d.ts +1 -0
  25. package/dist/processors/observational-memory/observation-strategies/types.d.ts.map +1 -1
  26. package/dist/processors/observational-memory/observation-turn/step.d.ts.map +1 -1
  27. package/dist/processors/observational-memory/observation-turn/turn.d.ts +3 -0
  28. package/dist/processors/observational-memory/observation-turn/turn.d.ts.map +1 -1
  29. package/dist/processors/observational-memory/observational-memory.d.ts +34 -0
  30. package/dist/processors/observational-memory/observational-memory.d.ts.map +1 -1
  31. package/dist/processors/observational-memory/processor.d.ts.map +1 -1
  32. package/dist/processors/observational-memory/reflector-runner.d.ts +5 -1
  33. package/dist/processors/observational-memory/reflector-runner.d.ts.map +1 -1
  34. package/dist/processors/observational-memory/subconscious/activity.d.ts +39 -0
  35. package/dist/processors/observational-memory/subconscious/activity.d.ts.map +1 -0
  36. package/dist/processors/observational-memory/subconscious/capture.d.ts +44 -0
  37. package/dist/processors/observational-memory/subconscious/capture.d.ts.map +1 -0
  38. package/dist/processors/observational-memory/subconscious/curate.d.ts +8 -0
  39. package/dist/processors/observational-memory/subconscious/curate.d.ts.map +1 -0
  40. package/dist/processors/observational-memory/subconscious/index.d.ts +30 -0
  41. package/dist/processors/observational-memory/subconscious/index.d.ts.map +1 -0
  42. package/dist/processors/observational-memory/subconscious/knowledge-tools.d.ts +12 -0
  43. package/dist/processors/observational-memory/subconscious/knowledge-tools.d.ts.map +1 -0
  44. package/dist/processors/observational-memory/subconscious/knowledge-write-tools.d.ts +16 -0
  45. package/dist/processors/observational-memory/subconscious/knowledge-write-tools.d.ts.map +1 -0
  46. package/dist/processors/observational-memory/subconscious/learn.d.ts +23 -0
  47. package/dist/processors/observational-memory/subconscious/learn.d.ts.map +1 -0
  48. package/dist/processors/observational-memory/subconscious/model.d.ts +21 -0
  49. package/dist/processors/observational-memory/subconscious/model.d.ts.map +1 -0
  50. package/dist/processors/observational-memory/subconscious/origin.d.ts +4 -0
  51. package/dist/processors/observational-memory/subconscious/origin.d.ts.map +1 -0
  52. package/dist/processors/observational-memory/subconscious/pinned-state-processor.d.ts +47 -0
  53. package/dist/processors/observational-memory/subconscious/pinned-state-processor.d.ts.map +1 -0
  54. package/dist/processors/observational-memory/subconscious/pinned.d.ts +54 -0
  55. package/dist/processors/observational-memory/subconscious/pinned.d.ts.map +1 -0
  56. package/dist/processors/observational-memory/subconscious/remind.d.ts +7 -0
  57. package/dist/processors/observational-memory/subconscious/remind.d.ts.map +1 -0
  58. package/dist/processors/observational-memory/subconscious/scope.d.ts +14 -0
  59. package/dist/processors/observational-memory/subconscious/scope.d.ts.map +1 -0
  60. package/dist/processors/observational-memory/subconscious/semantic-index.d.ts +20 -0
  61. package/dist/processors/observational-memory/subconscious/semantic-index.d.ts.map +1 -0
  62. package/dist/processors/observational-memory/subconscious/types.d.ts +118 -0
  63. package/dist/processors/observational-memory/subconscious/types.d.ts.map +1 -0
  64. package/dist/processors/observational-memory/types.d.ts +21 -0
  65. package/dist/processors/observational-memory/types.d.ts.map +1 -1
  66. package/dist/{src-_7ADtEwc.cjs → src-Ik0ksTOt.cjs} +2846 -297
  67. package/dist/src-Ik0ksTOt.cjs.map +1 -0
  68. package/dist/{src-BKRU-0Fx.js → src-t_4yoTX9.js} +2789 -300
  69. package/dist/src-t_4yoTX9.js.map +1 -0
  70. package/package.json +5 -5
  71. package/dist/src-BKRU-0Fx.js.map +0 -1
  72. package/dist/src-_7ADtEwc.cjs.map +0 -1
@@ -11,16 +11,16 @@ import { isStandardSchemaWithJSON, standardSchemaToJSONSchema, toStandardSchema
11
11
  import { Mutex } from "async-mutex";
12
12
  import { LRUCache } from "lru-cache";
13
13
  import xxhash from "xxhash-wasm";
14
+ import { InMemoryStore, assertKnowledgeScopeWithinCeiling, canonicalizeKnowledgeScope, createKnowledgeNodeCursor, expandKnowledgeScope, isKnowledgeScopeVisible, knowledgeScopeKey } from "@mastra/core/storage";
15
+ import { createHash, randomBytes, randomUUID } from "crypto";
16
+ import { createTool } from "@mastra/core/tools";
14
17
  import { modelSupportsAttachments, resolveModelConfig } from "@mastra/core/llm";
15
18
  import { appendFileSync, mkdirSync, writeFileSync } from "fs";
16
19
  import { join } from "path";
17
20
  import { MASTRA_THREAD_ID_KEY, RequestContext } from "@mastra/core/request-context";
18
21
  import { estimateTokenCount } from "tokenx";
19
- import { createHash, randomBytes, randomUUID } from "crypto";
20
- import { InMemoryStore } from "@mastra/core/storage";
21
22
  import { AsyncLocalStorage } from "async_hooks";
22
23
  import probeImageSizeSync from "probe-image-size/sync.js";
23
- import { createTool } from "@mastra/core/tools";
24
24
  import { isStandardSchemaWithJSON as isStandardSchemaWithJSON$1, toStandardSchema as toStandardSchema$1 } from "@mastra/core/schema";
25
25
  import { ErrorCategory, ErrorDomain, MastraError } from "@mastra/core/error";
26
26
  import { MessageHistory, MessageHistory as MessageHistory$1, SemanticRecall, WorkingMemory } from "@mastra/core/processors";
@@ -14616,162 +14616,6 @@ createIdGenerator({
14616
14616
  size: 24
14617
14617
  });
14618
14618
  //#endregion
14619
- //#region src/processors/observational-memory/constants.ts
14620
- var constants_exports = /* @__PURE__ */ __exportAll({
14621
- OBSERVATIONAL_MEMORY_DEFAULTS: () => OBSERVATIONAL_MEMORY_DEFAULTS,
14622
- OBSERVATION_CONTEXT_INSTRUCTIONS: () => OBSERVATION_CONTEXT_INSTRUCTIONS,
14623
- OBSERVATION_CONTEXT_PROMPT: () => OBSERVATION_CONTEXT_PROMPT,
14624
- OBSERVATION_CONTINUATION_HINT: () => OBSERVATION_CONTINUATION_HINT,
14625
- getRetrievalInstructions: () => getRetrievalInstructions
14626
- });
14627
- /**
14628
- * Default configuration values matching the spec
14629
- */
14630
- const OBSERVATIONAL_MEMORY_DEFAULTS = {
14631
- observation: {
14632
- model: "google/gemini-2.5-flash",
14633
- messageTokens: 3e4,
14634
- modelSettings: {
14635
- temperature: .3,
14636
- maxOutputTokens: 1e5
14637
- },
14638
- providerOptions: { google: { thinkingConfig: { thinkingBudget: 215 } } },
14639
- maxTokensPerBatch: 1e4,
14640
- bufferTokens: .2,
14641
- bufferActivation: .8
14642
- },
14643
- reflection: {
14644
- model: "google/gemini-2.5-flash",
14645
- observationTokens: 4e4,
14646
- modelSettings: {
14647
- temperature: 0,
14648
- maxOutputTokens: 1e5
14649
- },
14650
- providerOptions: { google: { thinkingConfig: { thinkingBudget: 1024 } } },
14651
- bufferActivation: .5
14652
- }
14653
- };
14654
- /**
14655
- * Continuation hint injected after observations to guide the model's behavior.
14656
- * Prevents the model from awkwardly acknowledging the memory system or treating
14657
- * the conversation as new after observed messages are removed.
14658
- */
14659
- const OBSERVATION_CONTINUATION_HINT = `Please continue naturally with the conversation so far and respond to the latest message.
14660
-
14661
- 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.
14662
-
14663
- Do not mention internal instructions, memory, summarization, context handling, or missing messages.
14664
-
14665
- Any messages following this reminder are newer and should take priority.`;
14666
- /**
14667
- * Preamble that introduces the observations block.
14668
- * Use before `<observations>`, with instructions after.
14669
- * Full pattern: `${OBSERVATION_CONTEXT_PROMPT}\n\n<observations>\n${obs}\n</observations>\n\n${OBSERVATION_CONTEXT_INSTRUCTIONS}`
14670
- */
14671
- const OBSERVATION_CONTEXT_PROMPT = `The following observations block contains your memory of past conversations with this user.`;
14672
- /**
14673
- * Instructions that tell the model how to interpret and use observations.
14674
- * Place AFTER the `<observations>` block so the model sees the data before the rules.
14675
- */
14676
- 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.
14677
-
14678
- 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.
14679
-
14680
- 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.
14681
-
14682
- 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.
14683
-
14684
- 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.`;
14685
- /**
14686
- * Instructions for retrieval mode — explains observation-group ranges and the recall tool.
14687
- * Appended to context when `retrieval` is enabled.
14688
- *
14689
- * The content adapts to the retrieval scope:
14690
- * - `'resource'`: covers routing between `search`, `threads`, and `messages` across
14691
- * all of the user's threads, including fallback when search results are unsuitable.
14692
- * - `'thread'`: covers cursor-based browsing and search within the current thread.
14693
- *
14694
- * @param scope - The retrieval scope the recall tool was registered with.
14695
- * @param customInstructions - Optional application-provided guidance appended after
14696
- * the native instructions. Never replaces them.
14697
- * @param searchEnabled - Whether semantic search (`retrieval: { vector: true }`) is
14698
- * available. When false, the guidance only covers \`threads\`/\`messages\` browsing so
14699
- * the agent is not steered toward a mode that cannot work.
14700
- */
14701
- function getRetrievalInstructions(scope = "resource", customInstructions, searchEnabled = true) {
14702
- const isResource = scope === "resource";
14703
- const base = `## Recall — looking up source messages
14704
-
14705
- 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.
14706
-
14707
- ### When to use recall
14708
- - The user asks you to **repeat, show, or reproduce** something from a past conversation
14709
- - The user asks for **exact content** — code, text, quotes, error messages, URLs, file paths, specific numbers
14710
- - 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)
14711
- - You want to **verify or expand on** an observation before responding${isResource ? `
14712
- - The user references another conversation that your observations don't cover — even if you have no observations yet, their other threads may contain it` : ""}
14713
-
14714
- **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.
14715
-
14716
- ${isResource ? searchEnabled ? `### Choosing a mode
14717
- The recall tool works across ALL of this user's conversation threads, not just the current one.
14718
-
14719
- - 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\`.
14720
- - 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.
14721
- - 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.
14722
-
14723
- **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
14724
- The recall tool works across ALL of this user's conversation threads, not just the current one.
14725
-
14726
- - 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.
14727
- - Use \`mode: "messages"\` when you know the thread — pass \`threadId\` to read another thread, or a \`cursor\` from an observation-group range.
14728
-
14729
- 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
14730
- The recall tool is limited to the current conversation thread.
14731
-
14732
- - Use \`mode: "messages"\` (default) to page through this thread's message history near a cursor.${searchEnabled ? `
14733
- - Use \`mode: "search"\` with a \`query\` to find messages by content within this thread.` : ""}
14734
- - Use \`mode: "threads"\` to get the current thread's ID, title, and dates.`}
14735
-
14736
- ### How to use recall with a cursor
14737
- Each range has the format \`startId:endId\` where both are message IDs separated by a colon.
14738
-
14739
- 1. Find the observation group relevant to the user's question and extract the start or end ID from its range.
14740
- 2. Call \`recall\` with that ID as the \`cursor\`.
14741
- 3. Use \`page: 1\` (or omit) to read forward from the cursor, \`page: -1\` to read backward.
14742
- 4. If the first page doesn't have what you need, increment the page number to keep paginating.
14743
- 5. Check \`hasNextPage\`/\`hasPrevPage\` in the result to know if more pages exist in each direction.
14744
-
14745
- ### Detail levels
14746
- 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.
14747
-
14748
- - 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.
14749
- - 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.
14750
-
14751
- 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.
14752
-
14753
- ### Following up on truncated parts
14754
- Low-detail results may include truncation hints like:
14755
- \`[truncated — call recall cursor="..." partIndex=N detail="high" for full content]\`
14756
-
14757
- **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.
14758
-
14759
- 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.
14760
-
14761
- ### When recall is NOT needed
14762
- - The user is asking for a high-level summary and your observations already cover it
14763
- - The question is about general preferences or facts that don't require source text
14764
- ${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`}
14765
-
14766
- Observation groups with range IDs and your recall tool allows you to think back and remember details you're fuzzy on.`;
14767
- const custom = customInstructions?.trim();
14768
- if (!custom) return base;
14769
- return `${base}
14770
-
14771
- ### Additional recall guidance
14772
- ${custom}`;
14773
- }
14774
- //#endregion
14775
14619
  //#region src/processors/observational-memory/extractor.ts
14776
14620
  const BUILT_IN_SLUGS = /* @__PURE__ */ new Set([
14777
14621
  "current-task",
@@ -14846,24 +14690,29 @@ var Extractor = class Extractor {
14846
14690
  const name = config.name.trim();
14847
14691
  const instructions = typeof config.instructions === "string" ? config.instructions.trim() : void 0;
14848
14692
  const slug = slugifyExtractorName(name);
14693
+ const isHook = config.mode === "hook";
14849
14694
  if (!name) throw new Error("Extractor name is required.");
14695
+ if (!isHook && !config.instructions) throw new Error(`Extractor "${name}" must include instructions.`);
14850
14696
  if (instructions !== void 0 && !instructions) throw new Error(`Extractor "${name}" must include instructions.`);
14697
+ if (isHook && !config.onExtracted) throw new Error(`Hook extractor "${name}" must include an onExtracted handler.`);
14698
+ if (isHook && (config.instructions || config.schema)) throw new Error(`Hook extractor "${name}" cannot include instructions or a schema.`);
14851
14699
  assertValidSlug(slug, name);
14852
14700
  if (!internal && RESERVED_XML_TAGS.has(slug)) throw new Error(`Extractor slug "${slug}" is reserved by Observational Memory.`);
14853
14701
  this.name = name;
14854
14702
  this.slug = slug;
14855
- this.instructionsConfig = config.instructions;
14703
+ this.instructionsConfig = config.instructions ?? "";
14856
14704
  this.schemaConfig = config.schema;
14857
14705
  this.instructions = instructions ?? "";
14858
- this.schema = typeof config.schema === "function" ? z.string() : config.schema ?? z.string();
14859
- this.mode = internal || !config.schema ? "inline" : "structured";
14860
- this.includePreviousExtraction = config.includePreviousExtraction ?? true;
14861
- this.metadataKeyPath = config.metadataKeyPath ?? `extracted.${slug}`;
14706
+ this.schema = isHook ? z.unknown() : typeof config.schema === "function" ? z.string() : config.schema ?? z.string();
14707
+ this.mode = isHook ? "hook" : internal || !config.schema ? "inline" : "structured";
14708
+ this.includePreviousExtraction = isHook ? false : config.includePreviousExtraction ?? true;
14709
+ this.metadataKeyPath = isHook ? false : config.metadataKeyPath ?? `extracted.${slug}`;
14862
14710
  this.onExtracted = config.onExtracted;
14863
14711
  this.retryStructuredExtractionOnEmptyObject = config.retryStructuredExtractionOnEmptyObject ?? false;
14864
14712
  this.internal = internal;
14865
14713
  }
14866
14714
  async resolve(context) {
14715
+ if (this.mode === "hook") return this;
14867
14716
  const instructions = typeof this.instructionsConfig === "function" ? (await this.instructionsConfig(context)).trim() : this.instructionsConfig.trim();
14868
14717
  if (!instructions) throw new Error(`Extractor "${this.name}" must include instructions.`);
14869
14718
  const schema = typeof this.schemaConfig === "function" ? await this.schemaConfig(context) : this.schemaConfig;
@@ -15000,84 +14849,2419 @@ function buildExtractorPriorLines(extractors, priorExtractedValues) {
15000
14849
  return lines;
15001
14850
  }
15002
14851
  //#endregion
15003
- //#region src/processors/observational-memory/extracted-values.ts
15004
- const BUILT_IN_METADATA_FIELDS = {
15005
- "current-task": {
15006
- metadataField: "currentTask",
15007
- builtInField: "currentTask"
15008
- },
15009
- "suggested-response": {
15010
- metadataField: "suggestedResponse",
15011
- builtInField: "suggestedContinuation"
15012
- },
15013
- "thread-title": {
15014
- metadataField: "threadTitle",
15015
- builtInField: "threadTitle"
14852
+ //#region src/processors/observational-memory/subconscious/activity.ts
14853
+ const SUBCONSCIOUS_ACTIVITY_STATE_ID = "subconscious-activity";
14854
+ async function getActivityTarget(store, event, scope) {
14855
+ if (event.recordType === "node") {
14856
+ const node = await store.getNode(event.recordId);
14857
+ if (!node || !isKnowledgeScopeVisible(node.scope, scope)) return {
14858
+ id: event.recordId,
14859
+ type: "node"
14860
+ };
14861
+ return {
14862
+ id: node.id,
14863
+ name: node.name,
14864
+ type: "node"
14865
+ };
15016
14866
  }
15017
- };
15018
- function isPresentExtractedValue(value) {
15019
- return value !== void 0 && value !== null && value !== "";
14867
+ const record = await store.getKnowledge({
14868
+ id: event.recordId,
14869
+ includeDeleted: true
14870
+ });
14871
+ const node = record ? await store.getNode(record.node) : void 0;
14872
+ if (!node || !isKnowledgeScopeVisible(node.scope, scope)) return {
14873
+ id: event.recordId,
14874
+ type: "node"
14875
+ };
14876
+ return {
14877
+ id: node.id,
14878
+ name: node.name,
14879
+ type: "node"
14880
+ };
15020
14881
  }
15021
- function normalizeExtractedValues(values) {
15022
- if (!values) return;
15023
- const normalized = Object.fromEntries(Object.entries(values).filter(([, value]) => isPresentExtractedValue(value)));
15024
- return Object.keys(normalized).length > 0 ? normalized : void 0;
14882
+ async function buildSubconsciousActivitySnapshot(input) {
14883
+ const events = await input.store.listActivity({
14884
+ scope: input.scope,
14885
+ limit: input.recentUpdates
14886
+ });
14887
+ const resolvedUpdates = await Promise.all(events.map(async (event) => {
14888
+ const target = await getActivityTarget(input.store, event, input.scope);
14889
+ return {
14890
+ action: event.action,
14891
+ type: event.recordType,
14892
+ name: target.name,
14893
+ targetId: target.id,
14894
+ targetType: target.type,
14895
+ createdAt: event.createdAt.toISOString()
14896
+ };
14897
+ }));
14898
+ const hotByRecord = /* @__PURE__ */ new Map();
14899
+ for (const update of resolvedUpdates) {
14900
+ if (!update.name) continue;
14901
+ const key = `${update.targetType}:${update.targetId}`;
14902
+ const existing = hotByRecord.get(key);
14903
+ if (existing) existing.updates += 1;
14904
+ else hotByRecord.set(key, {
14905
+ type: update.targetType,
14906
+ name: update.name,
14907
+ updates: 1
14908
+ });
14909
+ }
14910
+ const hot = [...hotByRecord.values()].sort((a, b) => b.updates - a.updates || a.name.localeCompare(b.name)).slice(0, input.recentUpdates);
14911
+ const updates = resolvedUpdates.map((update) => ({
14912
+ action: update.action,
14913
+ type: update.type,
14914
+ ...update.name ? { name: update.name } : {},
14915
+ createdAt: update.createdAt
14916
+ }));
14917
+ const errors = input.errors?.filter(Boolean).slice(0, input.recentUpdates);
14918
+ return {
14919
+ updates,
14920
+ hot,
14921
+ ...errors?.length ? { errors } : {}
14922
+ };
15025
14923
  }
15026
- function mergeExtractedValues(...valueSets) {
15027
- const merged = {};
15028
- for (const values of valueSets) {
15029
- const normalized = normalizeExtractedValues(values);
15030
- if (normalized) Object.assign(merged, normalized);
15031
- }
15032
- return Object.keys(merged).length > 0 ? merged : void 0;
14924
+ function renderSubconsciousActivity(snapshot) {
14925
+ const lines = snapshot.updates.map((update) => {
14926
+ const target = update.name ? `${update.type} [[${update.name}]]` : update.type;
14927
+ return `- ${update.action}: ${target}`;
14928
+ });
14929
+ const hot = snapshot.hot.map((record) => `[[${record.name}]] (${record.updates})`).join(", ");
14930
+ return [
14931
+ hot ? `Hot: ${hot}` : "Hot: none",
14932
+ "Recent updates:",
14933
+ ...lines.length ? lines : ["- none"],
14934
+ ...snapshot.errors?.length ? ["Errors:", ...snapshot.errors.map((error) => `- ${error}`)] : []
14935
+ ].join("\n");
14936
+ }
14937
+ async function publishSubconsciousError(input) {
14938
+ await input.writer?.custom({
14939
+ type: "data-subconscious-error",
14940
+ data: {
14941
+ error: input.error,
14942
+ agent: input.agent
14943
+ }
14944
+ });
14945
+ if (!input.sendStateSignal) return;
14946
+ const snapshot = {
14947
+ updates: [],
14948
+ hot: [],
14949
+ errors: [input.error]
14950
+ };
14951
+ const contents = renderSubconsciousActivity(snapshot);
14952
+ await input.sendStateSignal({
14953
+ id: SUBCONSCIOUS_ACTIVITY_STATE_ID,
14954
+ mode: "snapshot",
14955
+ cacheKey: createHash("sha256").update(contents).digest("hex"),
14956
+ tagName: "state",
14957
+ attributes: { id: SUBCONSCIOUS_ACTIVITY_STATE_ID },
14958
+ metadata: { origin: "subconscious" },
14959
+ contents,
14960
+ value: snapshot
14961
+ });
15033
14962
  }
15034
- function mergeExtractionFailures(...failureSets) {
15035
- const failures = failureSets.flatMap((set) => set ?? []);
15036
- return failures.length > 0 ? failures : void 0;
14963
+ async function publishSubconsciousActivity(input) {
14964
+ if (!input.sendStateSignal) return void 0;
14965
+ const snapshot = await buildSubconsciousActivitySnapshot(input);
14966
+ const contents = renderSubconsciousActivity(snapshot);
14967
+ const cacheKey = createHash("sha256").update(contents).digest("hex");
14968
+ await input.sendStateSignal({
14969
+ id: SUBCONSCIOUS_ACTIVITY_STATE_ID,
14970
+ mode: "snapshot",
14971
+ cacheKey,
14972
+ tagName: "state",
14973
+ attributes: { id: SUBCONSCIOUS_ACTIVITY_STATE_ID },
14974
+ metadata: { origin: "subconscious" },
14975
+ contents,
14976
+ value: snapshot
14977
+ });
14978
+ return snapshot;
15037
14979
  }
15038
- const UNSAFE_METADATA_PATH_SEGMENTS = /* @__PURE__ */ new Set([
15039
- "__proto__",
15040
- "constructor",
15041
- "prototype"
15042
- ]);
15043
- function getMetadataPathSegments(keyPath) {
15044
- if (keyPath === false) return;
15045
- const segments = keyPath.split(".").filter(Boolean);
15046
- if (segments.some((segment) => UNSAFE_METADATA_PATH_SEGMENTS.has(segment))) throw new Error(`Extractor metadataKeyPath "${keyPath}" contains an unsafe path segment.`);
15047
- return segments.length > 0 ? segments : void 0;
14980
+ //#endregion
14981
+ //#region src/processors/observational-memory/subconscious/pinned.ts
14982
+ /** Processor id and state-signal id for the pinned-knowledge lane. */
14983
+ const SUBCONSCIOUS_PINS_STATE_ID = "subconscious-pins";
14984
+ /** Snapshot tag the model sees; the delta tag appends `-update`. */
14985
+ const PINNED_SNAPSHOT_TAG = "pinned-knowledge";
14986
+ const PINNED_DELTA_TAG = "pinned-knowledge-update";
14987
+ /** Reserved node holding the pin set. One record, at one fixed scope level. */
14988
+ const PINNED_NODE_NAME = "pinned";
14989
+ const PINNED_NODE_KIND = "system";
14990
+ const PINNED_NODE_SCOPE_LEVEL = "resource";
14991
+ const DEFAULT_PINNED_MAX_CHARACTERS = 2e3;
14992
+ const MAX_PINNED_MAX_CHARACTERS = 8e3;
14993
+ const PIN_IDENTITY = "subconscious:pin";
14994
+ function pinnedNodeScope(scope, maxScope) {
14995
+ return expandKnowledgeScope(scope, maxScope === "thread" ? "thread" : PINNED_NODE_SCOPE_LEVEL);
14996
+ }
14997
+ async function resolvePinnedNodeId(store, scope) {
14998
+ return (await store.resolveNode({
14999
+ name: PINNED_NODE_NAME,
15000
+ scope
15001
+ }))?.id;
15002
+ }
15003
+ /** Reuse the node wherever it is visible; otherwise create it. `createNode` is an idempotent upsert on (name, scope). */
15004
+ async function ensurePinnedNodeId(store, scope, maxScope) {
15005
+ const existing = await resolvePinnedNodeId(store, scope);
15006
+ if (existing) return existing;
15007
+ return (await store.createNode({
15008
+ name: PINNED_NODE_NAME,
15009
+ kind: PINNED_NODE_KIND,
15010
+ scope: pinnedNodeScope(scope, maxScope)
15011
+ })).id;
15048
15012
  }
15049
- function getValueAtPath(metadata, keyPath) {
15050
- if (!metadata) return;
15051
- const segments = getMetadataPathSegments(keyPath);
15052
- if (!segments) return;
15053
- let current = metadata;
15054
- for (const segment of segments) {
15055
- if (!current || typeof current !== "object" || Array.isArray(current)) return;
15056
- current = current[segment];
15057
- }
15058
- return current;
15013
+ /**
15014
+ * Assembles the current pin set.
15015
+ *
15016
+ * Reads use the FULL visible scope context, never a level-narrowed write scope: visibility is
15017
+ * subset containment, so querying at the node's level would drop pins written at narrower
15018
+ * levels. Deleted records are excluded explicitly.
15019
+ */
15020
+ async function listPinnedKnowledge(input) {
15021
+ const nodeId = await resolvePinnedNodeId(input.store, input.scope);
15022
+ if (!nodeId) return { pins: [] };
15023
+ const pins = [];
15024
+ let after;
15025
+ do {
15026
+ const page = await input.store.listKnowledgeAbout({
15027
+ node: nodeId,
15028
+ scope: input.scope,
15029
+ after,
15030
+ includeDeleted: false
15031
+ });
15032
+ pins.push(...page.records);
15033
+ after = page.nextCursor;
15034
+ } while (after);
15035
+ return {
15036
+ nodeId,
15037
+ pins
15038
+ };
15059
15039
  }
15060
- function setValueAtPath(metadata, keyPath, value) {
15061
- if (!isPresentExtractedValue(value)) return;
15062
- const segments = getMetadataPathSegments(keyPath);
15063
- if (!segments) return;
15064
- let current = metadata;
15065
- for (const segment of segments.slice(0, -1)) {
15066
- const next = current[segment];
15067
- if (!next || typeof next !== "object" || Array.isArray(next)) current[segment] = {};
15068
- current = current[segment];
15069
- }
15070
- current[segments[segments.length - 1]] = value;
15040
+ function totalCharacters(pins) {
15041
+ return pins.reduce((sum, pin) => sum + pin.text.length, 0);
15071
15042
  }
15072
- function readBuiltInMetadataValues(metadata) {
15073
- const values = {};
15074
- for (const slug of BUILT_IN_EXTRACTOR_SLUGS) {
15075
- const { metadataField } = BUILT_IN_METADATA_FIELDS[slug];
15076
- values[slug] = metadata[metadataField];
15077
- }
15078
- return values;
15043
+ function assertBudget(options, pins, incomingText, replacing) {
15044
+ const kept = replacing ? pins.filter((pin) => pin.id !== replacing.id) : pins;
15045
+ if (!replacing && kept.length >= options.maxPins) throw new Error(`Pin limit reached: the set holds at most ${options.maxPins}. Unpin something first.`);
15046
+ if (totalCharacters(kept) + incomingText.length > options.maxCharacters) throw new Error(`Pin budget exceeded: the pin set is limited to ${options.maxCharacters} characters in total.`);
15079
15047
  }
15080
- function getPriorExtractedValues(metadata, extractors) {
15048
+ function clampPinLevel(level) {
15049
+ return level === "org" ? "resource" : level;
15050
+ }
15051
+ function resolveWriteScope$1(options, level) {
15052
+ let effective = clampPinLevel(level ?? options.defaultScope);
15053
+ if (!level && options.maxScope === "thread") effective = "thread";
15054
+ const scope = expandKnowledgeScope(options.scope, effective);
15055
+ assertKnowledgeScopeWithinCeiling(scope, options.maxScope);
15056
+ return scope;
15057
+ }
15058
+ const scopeLevelSchema$1 = {
15059
+ type: "string",
15060
+ enum: ["resource", "thread"]
15061
+ };
15062
+ /** Shared pin write path used by the tool and capture-time pinning. */
15063
+ async function writePinnedKnowledge(store, options, text, level, metadata) {
15064
+ const { pins } = await listPinnedKnowledge({
15065
+ store,
15066
+ scope: options.scope
15067
+ });
15068
+ assertBudget(options, pins, text);
15069
+ const nodeId = await ensurePinnedNodeId(store, options.scope, options.maxScope);
15070
+ return store.appendKnowledge({
15071
+ node: nodeId,
15072
+ text,
15073
+ scope: resolveWriteScope$1(options, level),
15074
+ sourceThreadId: options.sourceThreadId,
15075
+ maxScope: options.maxScope,
15076
+ metadata,
15077
+ resolutionScope: options.scope,
15078
+ defaultScope: expandKnowledgeScope(options.scope, options.defaultScope)
15079
+ });
15080
+ }
15081
+ async function getStore$1(memory) {
15082
+ const store = await memory.storage.getStore("knowledge");
15083
+ if (!store) throw new Error("Pinned knowledge requires a configured knowledge storage domain.");
15084
+ return store;
15085
+ }
15086
+ async function requirePin(store, recordId, options) {
15087
+ const record = await store.getKnowledge({
15088
+ id: recordId,
15089
+ includeDeleted: false
15090
+ });
15091
+ if (!record) throw new Error(`Pin not found: ${recordId}`);
15092
+ const nodeId = await resolvePinnedNodeId(store, options.scope);
15093
+ if (!nodeId || record.node !== nodeId) throw new Error(`Record is not a pin: ${recordId}`);
15094
+ if (!isKnowledgeScopeVisible(record.scope, options.scope)) throw new Error("Pin is outside the visible scope.");
15095
+ return record;
15096
+ }
15097
+ /**
15098
+ * Pin lifecycle tools. Pin appends a record on the reserved node; unpin soft-deletes it
15099
+ * (auditable, restorable); edit is remove plus append because knowledge records are immutable,
15100
+ * so an edited pin carries a new record id.
15101
+ */
15102
+ function createPinnedTools(memory, options) {
15103
+ return {
15104
+ knowledge_pin: createTool({
15105
+ id: "knowledge_pin",
15106
+ description: "Pin knowledge that must stay in context every turn without being asked for. Pins cost context permanently; pin only what is unconditionally relevant.",
15107
+ inputSchema: {
15108
+ type: "object",
15109
+ properties: {
15110
+ text: {
15111
+ type: "string",
15112
+ minLength: 1
15113
+ },
15114
+ scope: scopeLevelSchema$1,
15115
+ reason: {
15116
+ type: "string",
15117
+ minLength: 1,
15118
+ description: "One short sentence: why this must stay in context permanently."
15119
+ }
15120
+ },
15121
+ required: ["text"],
15122
+ additionalProperties: false
15123
+ },
15124
+ execute: async (input) => {
15125
+ const value = input;
15126
+ return writePinnedKnowledge(await getStore$1(memory), options, value.text, value.scope, value.reason ? { reason: value.reason } : void 0);
15127
+ }
15128
+ }),
15129
+ knowledge_unpin: createTool({
15130
+ id: "knowledge_unpin",
15131
+ description: "Remove a pin. The underlying knowledge record is soft-deleted and drops out of the pinned context.",
15132
+ inputSchema: {
15133
+ type: "object",
15134
+ properties: { recordId: {
15135
+ type: "string",
15136
+ minLength: 1
15137
+ } },
15138
+ required: ["recordId"],
15139
+ additionalProperties: false
15140
+ },
15141
+ execute: async (input) => {
15142
+ const store = await getStore$1(memory);
15143
+ const record = await requirePin(store, input.recordId, options);
15144
+ return store.removeKnowledge({
15145
+ id: record.id,
15146
+ deletedBy: PIN_IDENTITY
15147
+ });
15148
+ }
15149
+ }),
15150
+ knowledge_edit_pin: createTool({
15151
+ id: "knowledge_edit_pin",
15152
+ description: "Replace the text of an existing pin. The replacement carries a new knowledge record id.",
15153
+ inputSchema: {
15154
+ type: "object",
15155
+ properties: {
15156
+ recordId: {
15157
+ type: "string",
15158
+ minLength: 1
15159
+ },
15160
+ text: {
15161
+ type: "string",
15162
+ minLength: 1
15163
+ },
15164
+ reason: {
15165
+ type: "string",
15166
+ minLength: 1,
15167
+ description: "One short sentence: why this must stay in context permanently."
15168
+ }
15169
+ },
15170
+ required: ["recordId", "text"],
15171
+ additionalProperties: false
15172
+ },
15173
+ execute: async (input) => {
15174
+ const value = input;
15175
+ const store = await getStore$1(memory);
15176
+ const record = await requirePin(store, value.recordId, options);
15177
+ const { pins } = await listPinnedKnowledge({
15178
+ store,
15179
+ scope: options.scope
15180
+ });
15181
+ assertBudget(options, pins, value.text, record);
15182
+ await store.removeKnowledge({
15183
+ id: record.id,
15184
+ deletedBy: PIN_IDENTITY
15185
+ });
15186
+ return store.appendKnowledge({
15187
+ node: record.node,
15188
+ text: value.text,
15189
+ scope: record.scope,
15190
+ sourceThreadId: options.sourceThreadId,
15191
+ maxScope: record.maxScope,
15192
+ metadata: value.reason ? {
15193
+ ...record.metadata,
15194
+ reason: value.reason
15195
+ } : record.metadata,
15196
+ resolutionScope: options.scope,
15197
+ defaultScope: expandKnowledgeScope(options.scope, options.defaultScope)
15198
+ });
15199
+ }
15200
+ })
15201
+ };
15202
+ }
15203
+ //#endregion
15204
+ //#region src/processors/observational-memory/subconscious/scope.ts
15205
+ /**
15206
+ * Resolves the resource rung of a subconscious knowledge scope.
15207
+ *
15208
+ * When the request context carries a `knowledgeResourceId` override, every
15209
+ * scope resolver anchors the resource rung on it instead of the run's
15210
+ * resourceId. This lets a host (for example, the factory) share one knowledge
15211
+ * graph across many runs of the same project while leaving thread identity and
15212
+ * observational memory untouched. Without the override, behavior is unchanged:
15213
+ * the fallback (the run's resourceId) is used as-is.
15214
+ */
15215
+ function resolveKnowledgeResourceId(requestContext, fallback) {
15216
+ const override = requestContext?.get?.("knowledgeResourceId");
15217
+ if (typeof override === "string" && override.trim()) return override;
15218
+ return fallback;
15219
+ }
15220
+ //#endregion
15221
+ //#region src/processors/observational-memory/subconscious/capture.ts
15222
+ const CAPTURE_GUIDANCE_PAGE = "capture-guidance";
15223
+ const MAX_CAPTURE_GUIDANCE_LENGTH = 4e3;
15224
+ const SCOPE_ORDER = {
15225
+ org: 0,
15226
+ resource: 1,
15227
+ thread: 2
15228
+ };
15229
+ const subconsciousCaptureSchema = z.object({ nodes: z.array(z.object({
15230
+ name: z.string().trim().min(1),
15231
+ kind: z.string().trim().min(1),
15232
+ scope: z.enum([
15233
+ "org",
15234
+ "resource",
15235
+ "thread"
15236
+ ]).optional(),
15237
+ records: z.array(z.object({
15238
+ text: z.string().trim().min(1),
15239
+ scope: z.enum([
15240
+ "org",
15241
+ "resource",
15242
+ "thread"
15243
+ ]).optional(),
15244
+ when: z.string().trim().min(1).optional(),
15245
+ reason: z.string().trim().min(1)
15246
+ }))
15247
+ })) });
15248
+ const subconsciousCapturePinningSchema = z.object({ nodes: z.array(z.object({
15249
+ name: z.string().trim().min(1),
15250
+ kind: z.string().trim().min(1),
15251
+ scope: z.enum([
15252
+ "org",
15253
+ "resource",
15254
+ "thread"
15255
+ ]).optional(),
15256
+ records: z.array(z.object({
15257
+ text: z.string().trim().min(1),
15258
+ scope: z.enum([
15259
+ "org",
15260
+ "resource",
15261
+ "thread"
15262
+ ]).optional(),
15263
+ when: z.string().trim().min(1).optional(),
15264
+ reason: z.string().trim().min(1),
15265
+ pin: z.boolean().optional()
15266
+ }))
15267
+ })) });
15268
+ 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.`;
15269
+ const CAPTURE_INSTRUCTIONS = `Extract durable, explicitly stated knowledge from the observations.
15270
+ Return nodes with short stable names, a freeform kind, and knowledge records nested under the node each record is about.
15271
+ Use common kinds such as person, task, event, project, organization, or document when they fit.
15272
+ Set node scope to the narrowest level where that identity and content should be shared. Omit it to use the configured default scope.
15273
+ Knowledge records must be grounded in the conversation, concise, and written as prose. Do not infer unstated information.
15274
+ Wrap every named node mentioned in record text in [[wikilinks]].
15275
+ 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.
15276
+ Omit scope when uncertain; omitted record scopes stay private to the current thread.
15277
+ Emit when only when the conversation anchors the referred time. Resolve relative dates against the current date and use ISO 8601.
15278
+ 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.`;
15279
+ 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".`;
15280
+ function clampScope(level, ceiling) {
15281
+ return ceiling && SCOPE_ORDER[level] < SCOPE_ORDER[ceiling] ? ceiling : level;
15282
+ }
15283
+ function requireScopeContext(context) {
15284
+ const organizationId = context.requestContext?.get("organizationId");
15285
+ 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.");
15286
+ const resourceId = resolveKnowledgeResourceId(context.requestContext, context.resourceId);
15287
+ if (!resourceId) throw new Error("Subconscious requires resourceId to derive scoped knowledge.");
15288
+ if (!context.threadId) throw new Error("Subconscious requires threadId to derive scoped knowledge.");
15289
+ return [
15290
+ `org:${organizationId}`,
15291
+ `resource:${resourceId}`,
15292
+ `thread:${context.threadId}`
15293
+ ];
15294
+ }
15295
+ async function getKnowledgeStore$1(context) {
15296
+ if (!context.memory) throw new Error("Subconscious capture requires an active Memory instance.");
15297
+ const store = await context.memory.storage.getStore("knowledge");
15298
+ if (!store) throw new Error("Subconscious requires a knowledge storage domain. Configure a storage adapter that provides stores.knowledge.");
15299
+ return store;
15300
+ }
15301
+ function parseWhen(value) {
15302
+ if (!value) return void 0;
15303
+ const when = new Date(value);
15304
+ if (Number.isNaN(when.getTime())) throw new Error(`Invalid Subconscious record time: ${value}`);
15305
+ return when;
15306
+ }
15307
+ var SubconsciousCaptureExtractor = class extends Extractor {
15308
+ constructor(options) {
15309
+ const capturePinning = options.pins !== false && options.pins !== void 0 && options.pins.capturePinning;
15310
+ const pinNotes = /* @__PURE__ */ new WeakMap();
15311
+ const defaultImplementation = async (context) => {
15312
+ const scopeContext = requireScopeContext(context);
15313
+ const store = await getKnowledgeStore$1(context);
15314
+ const droppedPins = [];
15315
+ for (const extractedNode of context.current.nodes) {
15316
+ const nodeScope = expandKnowledgeScope(scopeContext, clampScope(extractedNode.scope ?? options.defaultScope, options.maxScope));
15317
+ const node = await store.createNode({
15318
+ name: extractedNode.name,
15319
+ kind: extractedNode.kind,
15320
+ scope: nodeScope
15321
+ });
15322
+ for (const extractedKnowledge of extractedNode.records) {
15323
+ if (capturePinning && extractedKnowledge.pin === true && options.pins) {
15324
+ try {
15325
+ await writePinnedKnowledge(store, {
15326
+ scope: scopeContext,
15327
+ sourceThreadId: context.threadId,
15328
+ defaultScope: options.defaultScope,
15329
+ maxScope: options.maxScope,
15330
+ maxPins: options.pins.maxPins,
15331
+ maxCharacters: options.pins.maxCharacters
15332
+ }, extractedKnowledge.text, extractedKnowledge.scope, extractedKnowledge.reason ? { reason: extractedKnowledge.reason } : void 0);
15333
+ } catch (error) {
15334
+ droppedPins.push(`Capture-time pin dropped: ${error instanceof Error ? error.message : String(error)}`);
15335
+ }
15336
+ continue;
15337
+ }
15338
+ const knowledgeLevel = clampScope(extractedKnowledge.scope ?? "thread", options.maxScope);
15339
+ await store.appendKnowledge({
15340
+ node,
15341
+ text: extractedKnowledge.text,
15342
+ scope: expandKnowledgeScope(scopeContext, knowledgeLevel),
15343
+ sourceThreadId: context.threadId,
15344
+ when: parseWhen(extractedKnowledge.when),
15345
+ maxScope: options.maxScope,
15346
+ metadata: extractedKnowledge.reason ? { reason: extractedKnowledge.reason } : void 0,
15347
+ resolutionScope: scopeContext,
15348
+ defaultScope: nodeScope
15349
+ });
15350
+ }
15351
+ }
15352
+ if (droppedPins.length) pinNotes.set(context.current, droppedPins);
15353
+ };
15354
+ super({
15355
+ name: "Capture",
15356
+ includePreviousExtraction: false,
15357
+ metadataKeyPath: false,
15358
+ schema: options.config?.schema ?? (capturePinning ? subconsciousCapturePinningSchema : subconsciousCaptureSchema),
15359
+ instructions: async (context) => {
15360
+ const sections = [
15361
+ CAPTURE_INSTRUCTIONS,
15362
+ !options.config?.schema ? CAPTURE_REASON_INSTRUCTIONS : void 0,
15363
+ capturePinning && !options.config?.schema ? CAPTURE_PINNING_INSTRUCTIONS : void 0,
15364
+ options.config?.instructions?.trim()
15365
+ ];
15366
+ if (options.learnedGuidance) {
15367
+ const scopeContext = requireScopeContext(context);
15368
+ const store = await getKnowledgeStore$1(context);
15369
+ const guidanceScope = expandKnowledgeScope(scopeContext, clampScope(options.defaultScope, options.maxScope));
15370
+ const guidance = await store.getNodeByName({
15371
+ name: CAPTURE_GUIDANCE_PAGE,
15372
+ scope: guidanceScope
15373
+ });
15374
+ 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)}`);
15375
+ }
15376
+ return sections.filter(Boolean).join("\n\n");
15377
+ },
15378
+ onExtracted: async (context) => {
15379
+ const publishActivity = async (errors) => {
15380
+ if (!options.activityRecentUpdates) return;
15381
+ const scope = requireScopeContext(context);
15382
+ await publishSubconsciousActivity({
15383
+ store: await getKnowledgeStore$1(context),
15384
+ scope,
15385
+ recentUpdates: options.activityRecentUpdates,
15386
+ sendStateSignal: context.sendStateSignal,
15387
+ errors
15388
+ });
15389
+ };
15390
+ try {
15391
+ const result = options.config?.onExtracted ? await options.config.onExtracted({
15392
+ ...context,
15393
+ defaultImplementation
15394
+ }) : await defaultImplementation(context);
15395
+ const droppedPinNotes = pinNotes.get(context.current);
15396
+ pinNotes.delete(context.current);
15397
+ await publishActivity(droppedPinNotes);
15398
+ return result ?? context.current;
15399
+ } catch (error) {
15400
+ await publishActivity([error instanceof Error ? error.message : String(error)]).catch(() => {});
15401
+ throw error;
15402
+ }
15403
+ }
15404
+ });
15405
+ }
15406
+ };
15407
+ //#endregion
15408
+ //#region src/processors/observational-memory/subconscious/knowledge-tools.ts
15409
+ const DEFAULT_LIMIT = 10;
15410
+ const MAX_LIMIT = 50;
15411
+ function resolveScope$3(context) {
15412
+ const organizationId = context?.requestContext?.get("organizationId");
15413
+ const resourceId = resolveKnowledgeResourceId(context?.requestContext, context?.agent?.resourceId);
15414
+ const threadId = context?.agent?.threadId;
15415
+ if (typeof organizationId !== "string" || !organizationId.trim()) throw new Error("Knowledge tools require requestContext.organizationId.");
15416
+ if (!resourceId) throw new Error("Knowledge tools require an active resourceId.");
15417
+ if (!threadId) throw new Error("Knowledge tools require an active threadId.");
15418
+ return [
15419
+ `org:${organizationId}`,
15420
+ `resource:${resourceId}`,
15421
+ `thread:${threadId}`
15422
+ ];
15423
+ }
15424
+ async function getKnowledgeStore(memory) {
15425
+ const store = await memory.storage.getStore("knowledge");
15426
+ if (!store) throw new Error("Knowledge tools require a configured knowledge storage domain.");
15427
+ return store;
15428
+ }
15429
+ function normalizeLimit(limit) {
15430
+ return Math.min(Math.max(limit ?? DEFAULT_LIMIT, 1), MAX_LIMIT);
15431
+ }
15432
+ function serializeRecord(record) {
15433
+ return {
15434
+ id: record.id,
15435
+ text: record.text,
15436
+ scope: record.scope,
15437
+ sourceThreadId: record.sourceThreadId,
15438
+ capturedAt: record.capturedAt.toISOString(),
15439
+ when: record.when?.toISOString()
15440
+ };
15441
+ }
15442
+ function serializeNode(node) {
15443
+ return {
15444
+ id: node.id,
15445
+ type: node.type,
15446
+ name: node.name,
15447
+ kind: node.kind,
15448
+ content: node.content,
15449
+ scope: node.scope,
15450
+ version: node.version,
15451
+ updatedAt: node.updatedAt.toISOString()
15452
+ };
15453
+ }
15454
+ async function loadSemanticResult(store, scope, candidate) {
15455
+ const type = candidate.metadata?.document_type;
15456
+ if (type === "node") {
15457
+ const node = await store.getNode(candidate.id.slice(15));
15458
+ if (!node || node.mergedInto || !isKnowledgeScopeVisible(node.scope, scope)) return null;
15459
+ return {
15460
+ type: "node",
15461
+ id: node.id,
15462
+ recordId: node.id,
15463
+ name: node.name,
15464
+ text: `${node.name}\n${node.content ?? ""}`,
15465
+ scope: node.scope,
15466
+ semanticScore: candidate.score
15467
+ };
15468
+ }
15469
+ if (type === "record") {
15470
+ const record = await store.getKnowledge({ id: candidate.id.slice(17) });
15471
+ if (!record || !isKnowledgeScopeVisible(record.scope, scope)) return null;
15472
+ const node = await store.getNode(record.node);
15473
+ const parentVisible = Boolean(node && !node.mergedInto && isKnowledgeScopeVisible(node.scope, scope));
15474
+ return {
15475
+ type: "record",
15476
+ id: record.id,
15477
+ recordId: parentVisible ? node.id : record.id,
15478
+ name: parentVisible ? node.name : "(private node)",
15479
+ text: record.text,
15480
+ scope: record.scope,
15481
+ semanticScore: candidate.score
15482
+ };
15483
+ }
15484
+ return null;
15485
+ }
15486
+ function mergeHybridResults(lexical, semantic, limit) {
15487
+ const ranked = /* @__PURE__ */ new Map();
15488
+ lexical.forEach((result, index) => {
15489
+ ranked.set(`${result.type}:${result.id}`, {
15490
+ ...result,
15491
+ score: 1 / (60 + index + 1),
15492
+ sources: ["lexical"]
15493
+ });
15494
+ });
15495
+ semantic.forEach((result, index) => {
15496
+ const key = `${result.type}:${result.id}`;
15497
+ const existing = ranked.get(key);
15498
+ const reciprocalRank = 1 / (60 + index + 1);
15499
+ ranked.set(key, {
15500
+ ...result,
15501
+ score: (existing?.score ?? 0) + reciprocalRank,
15502
+ sources: existing ? ["lexical", "semantic"] : ["semantic"],
15503
+ semanticScore: result.semanticScore
15504
+ });
15505
+ });
15506
+ return [...ranked.values()].sort((left, right) => right.score - left.score || left.id.localeCompare(right.id)).slice(0, limit);
15507
+ }
15508
+ function createKnowledgeTools(memory, fixedScope) {
15509
+ return {
15510
+ knowledge_search: createTool({
15511
+ id: "knowledge_search",
15512
+ description: "Search durable scoped knowledge across nodes and knowledge records using lexical and semantic retrieval.",
15513
+ inputSchema: {
15514
+ type: "object",
15515
+ properties: {
15516
+ query: {
15517
+ type: "string",
15518
+ minLength: 1,
15519
+ description: "The knowledge to search for."
15520
+ },
15521
+ limit: {
15522
+ type: "integer",
15523
+ minimum: 1,
15524
+ maximum: MAX_LIMIT,
15525
+ description: "Maximum results. Defaults to 10."
15526
+ }
15527
+ },
15528
+ required: ["query"],
15529
+ additionalProperties: false
15530
+ },
15531
+ execute: async (input, context) => {
15532
+ const { query, limit: requestedLimit } = input;
15533
+ const scope = fixedScope ?? resolveScope$3(context);
15534
+ const limit = normalizeLimit(requestedLimit);
15535
+ const store = await getKnowledgeStore(memory);
15536
+ const semanticCandidates = await memory.getKnowledgeSemanticIndex().then((index) => index.search(query, scope, limit * 2));
15537
+ return {
15538
+ query,
15539
+ results: mergeHybridResults(await store.search({
15540
+ query,
15541
+ scope,
15542
+ limit: limit * 2
15543
+ }), (await Promise.all(semanticCandidates.map((candidate) => loadSemanticResult(store, scope, candidate)))).filter((result) => Boolean(result)), limit)
15544
+ };
15545
+ }
15546
+ }),
15547
+ knowledge_read: createTool({
15548
+ id: "knowledge_read",
15549
+ description: "Read a knowledge node, its content, and knowledge records about or linked to it by name or ID.",
15550
+ inputSchema: {
15551
+ type: "object",
15552
+ properties: {
15553
+ id: {
15554
+ type: "string",
15555
+ minLength: 1
15556
+ },
15557
+ name: {
15558
+ type: "string",
15559
+ minLength: 1
15560
+ },
15561
+ relationship: {
15562
+ type: "string",
15563
+ enum: [
15564
+ "about",
15565
+ "mentioning",
15566
+ "related"
15567
+ ],
15568
+ description: "Knowledge relationship to query. Defaults to about."
15569
+ },
15570
+ cursor: {
15571
+ type: "string",
15572
+ minLength: 1,
15573
+ description: "Return knowledge records after this record ULID."
15574
+ },
15575
+ limit: {
15576
+ type: "integer",
15577
+ minimum: 1,
15578
+ maximum: MAX_LIMIT
15579
+ }
15580
+ },
15581
+ additionalProperties: false
15582
+ },
15583
+ execute: async (input, context) => {
15584
+ const { id, name, relationship = "about", cursor, limit: requestedLimit } = input;
15585
+ if (!id && !name) throw new Error("knowledge_read requires id or name.");
15586
+ const scope = fixedScope ?? resolveScope$3(context);
15587
+ const store = await getKnowledgeStore(memory);
15588
+ const node = id ? await store.getNode(id) : await store.resolveNode({
15589
+ name,
15590
+ scope
15591
+ });
15592
+ if (!node || node.mergedInto || !isKnowledgeScopeVisible(node.scope, scope)) return { found: false };
15593
+ const result = await (relationship === "related" ? store.listKnowledgeRelatedTo : relationship === "mentioning" ? store.listKnowledgeMentioning : store.listKnowledgeAbout).call(store, {
15594
+ node,
15595
+ scope,
15596
+ after: cursor,
15597
+ limit: normalizeLimit(requestedLimit)
15598
+ });
15599
+ return {
15600
+ found: true,
15601
+ node: serializeNode(node),
15602
+ records: result.records.map(serializeRecord),
15603
+ nextCursor: result.nextCursor
15604
+ };
15605
+ }
15606
+ }),
15607
+ knowledge_browse: createTool({
15608
+ id: "knowledge_browse",
15609
+ description: "Browse visible knowledge nodes by scope and name prefix, or follow a node’s mentions and backlinks.",
15610
+ inputSchema: {
15611
+ type: "object",
15612
+ properties: {
15613
+ namePrefix: { type: "string" },
15614
+ kind: {
15615
+ type: "string",
15616
+ description: "Optional node kind filter."
15617
+ },
15618
+ hasContent: {
15619
+ type: "boolean",
15620
+ description: "Filter nodes by whether they have long-form content."
15621
+ },
15622
+ node: {
15623
+ type: "string",
15624
+ minLength: 1,
15625
+ description: "When set, return knowledge related to this node."
15626
+ },
15627
+ cursor: {
15628
+ type: "string",
15629
+ minLength: 1
15630
+ },
15631
+ limit: {
15632
+ type: "integer",
15633
+ minimum: 1,
15634
+ maximum: MAX_LIMIT
15635
+ }
15636
+ },
15637
+ additionalProperties: false
15638
+ },
15639
+ execute: async (input, context) => {
15640
+ const { namePrefix, kind, hasContent, node: nodeReference, cursor, limit: requestedLimit } = input;
15641
+ const scope = fixedScope ?? resolveScope$3(context);
15642
+ const limit = normalizeLimit(requestedLimit);
15643
+ const store = await getKnowledgeStore(memory);
15644
+ if (nodeReference) {
15645
+ const node = await store.getNode(nodeReference);
15646
+ if (!node || node.mergedInto || !isKnowledgeScopeVisible(node.scope, scope)) return { found: false };
15647
+ const result = await store.listKnowledgeRelatedTo({
15648
+ node,
15649
+ scope,
15650
+ after: cursor,
15651
+ limit
15652
+ });
15653
+ return {
15654
+ found: true,
15655
+ node: serializeNode(node),
15656
+ records: result.records.map(serializeRecord),
15657
+ nextCursor: result.nextCursor
15658
+ };
15659
+ }
15660
+ const nodes = await store.listNodes({
15661
+ scope,
15662
+ namePrefix,
15663
+ kind,
15664
+ hasContent,
15665
+ cursor,
15666
+ limit: limit + 1
15667
+ });
15668
+ const hasMore = nodes.length > limit;
15669
+ const visibleNodes = nodes.slice(0, limit);
15670
+ return {
15671
+ nodes: visibleNodes.map(serializeNode),
15672
+ nextCursor: hasMore ? createKnowledgeNodeCursor(visibleNodes.at(-1), {
15673
+ namePrefix,
15674
+ kind,
15675
+ hasContent
15676
+ }) : void 0
15677
+ };
15678
+ }
15679
+ })
15680
+ };
15681
+ }
15682
+ //#endregion
15683
+ //#region src/processors/observational-memory/model-by-input-tokens.ts
15684
+ function isTieredModelTarget(model) {
15685
+ if (typeof model === "string") return true;
15686
+ if (!model || typeof model !== "object") return false;
15687
+ return "modelId" in model || "id" in model || "providerId" in model || "provider" in model || "doGenerate" in model && "doStream" in model;
15688
+ }
15689
+ function normalizeThresholds(config) {
15690
+ const entries = Object.entries(config.upTo);
15691
+ if (entries.length === 0) throw new Error("ModelByInputTokens requires at least one threshold in \"upTo\"");
15692
+ for (const [limitStr, model] of entries) {
15693
+ const limit = Number(limitStr);
15694
+ if (!Number.isFinite(limit) || limit <= 0) throw new Error(`ModelByInputTokens threshold keys must be positive numbers. Got: ${limitStr}`);
15695
+ if (!isTieredModelTarget(model)) throw new Error(`ModelByInputTokens requires a valid model target for threshold ${limitStr}`);
15696
+ }
15697
+ return entries.map(([limitStr, model]) => ({
15698
+ limit: Number(limitStr),
15699
+ model
15700
+ })).sort((a, b) => a.limit - b.limit);
15701
+ }
15702
+ var ModelByInputTokens = class {
15703
+ thresholds;
15704
+ constructor(config) {
15705
+ this.thresholds = normalizeThresholds(config);
15706
+ }
15707
+ resolve(inputTokens) {
15708
+ for (const { limit, model } of this.thresholds) if (inputTokens <= limit) return model;
15709
+ const maxLimit = this.thresholds[this.thresholds.length - 1].limit;
15710
+ throw new Error(`ModelByInputTokens: input token count (${inputTokens}) exceeds the largest configured threshold (${maxLimit}). Please configure a higher threshold or use a larger model.`);
15711
+ }
15712
+ getThresholds() {
15713
+ return this.thresholds.map((t) => t.limit);
15714
+ }
15715
+ };
15716
+ //#endregion
15717
+ //#region src/processors/observational-memory/subconscious/model.ts
15718
+ /**
15719
+ * Normalize an observational memory model config into something an Agent can consume directly.
15720
+ * Returns undefined when the OM model cannot stand alone: the 'default' sentinel and
15721
+ * token-routed models (ModelByInputTokens) both require engine context to resolve.
15722
+ */
15723
+ function usableObservationalMemoryModel(model) {
15724
+ if (!model || model === "default") return void 0;
15725
+ if (model instanceof ModelByInputTokens) return void 0;
15726
+ if (Array.isArray(model)) return model[0]?.model;
15727
+ if (typeof model === "function") return (async (ctx) => {
15728
+ const result = await model(ctx);
15729
+ return Array.isArray(result) ? result[0]?.model ?? result : result;
15730
+ });
15731
+ return model;
15732
+ }
15733
+ /**
15734
+ * Resolve the model a subconscious agent runs on. Precedence: the per-agent config model,
15735
+ * then the observational memory model, then the main agent's model. Returns undefined when
15736
+ * no source is available so callers keep their existing throw/silent-return behavior.
15737
+ */
15738
+ async function resolveSubconsciousAgentModel(options) {
15739
+ const { config, omModel, mainAgent, requestContext } = options;
15740
+ if (config.model) {
15741
+ if (mainAgent) return await mainAgent.getModel({
15742
+ requestContext,
15743
+ modelConfig: config.model
15744
+ });
15745
+ return config.model;
15746
+ }
15747
+ const fromOm = usableObservationalMemoryModel(omModel);
15748
+ if (fromOm) return fromOm;
15749
+ if (mainAgent) return await mainAgent.getModel({ requestContext });
15750
+ }
15751
+ //#endregion
15752
+ //#region src/processors/observational-memory/subconscious/remind.ts
15753
+ const DEFAULT_INSTRUCTIONS$2 = `Review the current observations and use the knowledge tools to find prior knowledge that is directly relevant now.
15754
+
15755
+ 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.
15756
+ 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.
15757
+ If nothing is relevant, respond with exactly <no-reminder /> and nothing else.
15758
+ 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.`;
15759
+ /** Own-thread records younger than this are treated as still-in-context and excluded from reminder candidates. */
15760
+ const FRESH_OWN_RECORD_WINDOW_MS = 1800 * 1e3;
15761
+ function resolveScope$2(context) {
15762
+ const organizationId = context.requestContext?.get("organizationId");
15763
+ if (typeof organizationId !== "string" || !organizationId.trim()) throw new Error("Subconscious remind requires organizationId in the request context.");
15764
+ const resourceId = resolveKnowledgeResourceId(context.requestContext, context.resourceId);
15765
+ if (!resourceId) throw new Error("Subconscious remind requires a resourceId.");
15766
+ return canonicalizeKnowledgeScope([
15767
+ `org:${organizationId}`,
15768
+ `resource:${resourceId}`,
15769
+ `thread:${context.threadId}`
15770
+ ]);
15771
+ }
15772
+ const REMINDER_QUERY_STOP_WORDS = /* @__PURE__ */ new Set([
15773
+ "about",
15774
+ "after",
15775
+ "before",
15776
+ "current",
15777
+ "from",
15778
+ "have",
15779
+ "observations",
15780
+ "that",
15781
+ "their",
15782
+ "there",
15783
+ "they",
15784
+ "this",
15785
+ "user",
15786
+ "what",
15787
+ "when",
15788
+ "where",
15789
+ "which",
15790
+ "with"
15791
+ ]);
15792
+ async function findReminderSources(store, scope, observations) {
15793
+ 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);
15794
+ const results = (await Promise.all(terms.map((query) => store.search({
15795
+ query,
15796
+ scope,
15797
+ limit: 5
15798
+ })))).flat();
15799
+ return [...new Map(results.map((result) => [`${result.type}:${result.id}`, result])).values()].slice(0, 10);
15800
+ }
15801
+ /**
15802
+ * Drop the current thread's own freshly captured KnowledgeRecords from the candidate list. They match the
15803
+ * current observations almost perfectly (they were just distilled from them), so without this
15804
+ * guard the reminder agent mostly echoes the session's own words back at it.
15805
+ */
15806
+ async function dropFreshOwnRecords(store, sources, threadId) {
15807
+ const checks = await Promise.all(sources.map(async (source) => {
15808
+ if (source.type !== "record") return true;
15809
+ const record = await store.getKnowledge({ id: source.id }).catch(() => null);
15810
+ if (!record) return true;
15811
+ const isOwnThread = record.sourceThreadId === threadId || record.sourceThreadId.startsWith(`subconscious:${threadId}:`);
15812
+ const isFresh = Date.now() - new Date(record.capturedAt).getTime() < FRESH_OWN_RECORD_WINDOW_MS;
15813
+ return !(isOwnThread && isFresh);
15814
+ }));
15815
+ return sources.filter((_, index) => checks[index]);
15816
+ }
15817
+ var SubconsciousRemindExtractor = class extends Extractor {
15818
+ constructor(config, omModel) {
15819
+ super({
15820
+ name: "Remind",
15821
+ mode: "hook",
15822
+ metadataKeyPath: false,
15823
+ onExtracted: async (context) => {
15824
+ if (!context.rawObservations?.trim() || !context.memory || !context.sendSignal) return;
15825
+ let scope;
15826
+ let store;
15827
+ try {
15828
+ scope = resolveScope$2(context);
15829
+ store = await context.memory.storage.getStore("knowledge");
15830
+ if (!store) throw new Error("Subconscious remind requires a configured knowledge storage domain.");
15831
+ const sources = await dropFreshOwnRecords(store, await findReminderSources(store, scope, context.rawObservations), context.threadId);
15832
+ if (sources.length === 0) return;
15833
+ const model = await resolveSubconsciousAgentModel({
15834
+ config,
15835
+ omModel,
15836
+ mainAgent: context.mainAgent,
15837
+ requestContext: context.requestContext
15838
+ });
15839
+ if (!model) return;
15840
+ const agent = new Agent({
15841
+ id: `subconscious-remind-${context.threadId}`,
15842
+ name: "Subconscious Remind",
15843
+ instructions: [DEFAULT_INSTRUCTIONS$2, config.instructions?.trim()].filter(Boolean).join("\n\n"),
15844
+ model,
15845
+ tools: createKnowledgeTools(context.memory, scope)
15846
+ });
15847
+ const recentMessagesSection = context.recentMessages?.trim() ? `\n\nRecent conversation messages (already visible to the agent — never remind about anything present here):\n${context.recentMessages}` : "";
15848
+ 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}`, {
15849
+ requestContext: context.requestContext,
15850
+ abortSignal: context.abortSignal,
15851
+ maxSteps: config.maxSteps
15852
+ })).text.trim();
15853
+ if (!reminder || /^<no-reminder\s*\/>$/i.test(reminder)) return;
15854
+ const sourceIds = [...new Set(sources.flatMap((source) => [source.id, source.recordId]))].filter((id) => reminder.includes(id)).slice(0, 5);
15855
+ if (sourceIds.length === 0) return;
15856
+ const contents = `${reminder}\n\nSources: ${sourceIds.join(", ")}`;
15857
+ await context.sendSignal({
15858
+ id: `__subconscious_remembered_${crypto.randomUUID()}`,
15859
+ type: "reactive",
15860
+ tagName: "remembered",
15861
+ contents,
15862
+ createdAt: /* @__PURE__ */ new Date(),
15863
+ metadata: { origin: "subconscious" },
15864
+ attributes: {
15865
+ source: "subconscious",
15866
+ sourceIds: sourceIds.join(","),
15867
+ agent: "remind",
15868
+ threadId: context.threadId
15869
+ }
15870
+ });
15871
+ } catch (error) {
15872
+ await context.writer?.custom({
15873
+ type: "data-subconscious-error",
15874
+ data: {
15875
+ agent: "remind",
15876
+ error: error instanceof Error ? error.message : String(error)
15877
+ }
15878
+ });
15879
+ if (store && scope) await publishSubconsciousActivity({
15880
+ store,
15881
+ scope,
15882
+ recentUpdates: 10,
15883
+ sendStateSignal: context.sendStateSignal,
15884
+ errors: [`remind: ${error instanceof Error ? error.message : String(error)}`]
15885
+ });
15886
+ throw error;
15887
+ }
15888
+ }
15889
+ });
15890
+ }
15891
+ };
15892
+ //#endregion
15893
+ //#region src/processors/observational-memory/subconscious/pinned-state-processor.ts
15894
+ function lp(value) {
15895
+ return `${value.length}:${value}`;
15896
+ }
15897
+ function pinFingerprint(pin) {
15898
+ return `${lp(pin.id)}${lp(pin.text)}`;
15899
+ }
15900
+ function stablePinsCacheKey(pins) {
15901
+ return `pins:${pins.map(pinFingerprint).join("|")}`;
15902
+ }
15903
+ function getPinsFromSnapshot(snapshot) {
15904
+ const value = (snapshot?.metadata)?.value?.pins;
15905
+ return Array.isArray(value) ? value : [];
15906
+ }
15907
+ function getOpsFromDelta(delta) {
15908
+ const ops = delta.metadata?.delta?.ops;
15909
+ return Array.isArray(ops) ? ops : [];
15910
+ }
15911
+ function applyPinOps(pins, ops) {
15912
+ const next = pins.slice();
15913
+ for (const op of ops) {
15914
+ if (op.op === "remove") {
15915
+ const idx = next.findIndex((pin) => pin.id === op.id);
15916
+ if (idx >= 0) next.splice(idx, 1);
15917
+ continue;
15918
+ }
15919
+ const idx = next.findIndex((pin) => pin.id === op.pin.id);
15920
+ if (idx >= 0) next[idx] = op.pin;
15921
+ else next.push(op.pin);
15922
+ }
15923
+ return next;
15924
+ }
15925
+ function effectivePriorPins(args) {
15926
+ let pins = getPinsFromSnapshot(args.lastSnapshot);
15927
+ for (const delta of args.deltasSinceSnapshot ?? []) pins = applyPinOps(pins, getOpsFromDelta(delta));
15928
+ return pins;
15929
+ }
15930
+ function diffPins(prior, current) {
15931
+ const ops = [];
15932
+ const priorIds = new Set(prior.map((pin) => pin.id));
15933
+ const currentIds = new Set(current.map((pin) => pin.id));
15934
+ for (const pin of prior) if (!currentIds.has(pin.id)) ops.push({
15935
+ op: "remove",
15936
+ id: pin.id
15937
+ });
15938
+ for (const pin of current) if (!priorIds.has(pin.id)) ops.push({
15939
+ op: "add",
15940
+ pin
15941
+ });
15942
+ return ops;
15943
+ }
15944
+ function renderPins(pins) {
15945
+ if (pins.length === 0) return "";
15946
+ return `\n${pins.map((pin) => ` • {id: ${pin.id}} ${pin.text}`).join("\n")}\n`;
15947
+ }
15948
+ function renderDelta(ops) {
15949
+ return `\n${ops.map((op) => op.op === "remove" ? ` - removed {id: ${op.id}}` : ` + {id: ${op.pin.id}} ${op.pin.text}`).join("\n")}\n`;
15950
+ }
15951
+ const MEMO_KEY = "__subconsciousPinnedRead";
15952
+ var PinnedStateProcessor = class {
15953
+ deps;
15954
+ id = SUBCONSCIOUS_PINS_STATE_ID;
15955
+ stateId = SUBCONSCIOUS_PINS_STATE_ID;
15956
+ constructor(deps) {
15957
+ this.deps = deps;
15958
+ }
15959
+ processInput(args) {
15960
+ return {
15961
+ messages: args.messages,
15962
+ systemMessages: [...args.systemMessages, {
15963
+ role: "system",
15964
+ 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.`
15965
+ }]
15966
+ };
15967
+ }
15968
+ resolveScope(args) {
15969
+ const organizationId = args.requestContext?.get?.("organizationId");
15970
+ if (typeof organizationId !== "string" || !organizationId.trim()) return void 0;
15971
+ const resourceId = resolveKnowledgeResourceId(args.requestContext, args.resourceId);
15972
+ if (!resourceId) return void 0;
15973
+ return canonicalizeKnowledgeScope([
15974
+ `org:${organizationId}`,
15975
+ `resource:${resourceId}`,
15976
+ `thread:${args.threadId}`
15977
+ ]);
15978
+ }
15979
+ async readPins(args, scope) {
15980
+ const stepNumber = typeof args.stepNumber === "number" ? args.stepNumber : 0;
15981
+ const scopeKey = scope.join("/");
15982
+ const memo = args.requestContext?.get?.(MEMO_KEY);
15983
+ if (memo && memo.scopeKey === scopeKey && stepNumber > memo.atStep) return memo.entries;
15984
+ const store = await this.deps.getKnowledgeStore();
15985
+ if (!store) return void 0;
15986
+ const { pins } = await listPinnedKnowledge({
15987
+ store,
15988
+ scope
15989
+ });
15990
+ const entries = pins.map((pin) => ({
15991
+ id: pin.id,
15992
+ text: pin.text
15993
+ }));
15994
+ args.requestContext?.set?.(MEMO_KEY, {
15995
+ atStep: stepNumber,
15996
+ scopeKey,
15997
+ entries
15998
+ });
15999
+ return entries;
16000
+ }
16001
+ async computeStateSignal(args) {
16002
+ const scope = this.resolveScope(args);
16003
+ if (!scope) return;
16004
+ const currentPins = await this.readPins(args, scope);
16005
+ if (!currentPins) return;
16006
+ const priorPins = effectivePriorPins(args);
16007
+ const hasBase = Boolean(args.lastSnapshot) && args.contextWindow.hasSnapshot;
16008
+ const ops = diffPins(priorPins, currentPins);
16009
+ if (ops.length === 0 && hasBase) return;
16010
+ if (currentPins.length === 0) {
16011
+ if (!hasBase) return;
16012
+ return {
16013
+ id: SUBCONSCIOUS_PINS_STATE_ID,
16014
+ mode: "snapshot",
16015
+ cacheKey: stablePinsCacheKey(currentPins),
16016
+ tagName: PINNED_SNAPSHOT_TAG,
16017
+ contents: "",
16018
+ value: { pins: currentPins },
16019
+ attributes: { count: 0 },
16020
+ metadata: { value: { pins: currentPins } }
16021
+ };
16022
+ }
16023
+ if (!hasBase) return {
16024
+ id: SUBCONSCIOUS_PINS_STATE_ID,
16025
+ mode: "snapshot",
16026
+ cacheKey: stablePinsCacheKey(currentPins),
16027
+ tagName: PINNED_SNAPSHOT_TAG,
16028
+ contents: renderPins(currentPins),
16029
+ value: { pins: currentPins },
16030
+ attributes: { count: currentPins.length },
16031
+ metadata: { value: { pins: currentPins } }
16032
+ };
16033
+ return {
16034
+ id: SUBCONSCIOUS_PINS_STATE_ID,
16035
+ mode: "delta",
16036
+ cacheKey: stablePinsCacheKey(currentPins),
16037
+ tagName: PINNED_DELTA_TAG,
16038
+ contents: renderDelta(ops),
16039
+ value: { pins: currentPins },
16040
+ delta: { ops },
16041
+ attributes: { changes: ops.length },
16042
+ metadata: {
16043
+ value: { pins: currentPins },
16044
+ delta: { ops }
16045
+ }
16046
+ };
16047
+ }
16048
+ };
16049
+ //#endregion
16050
+ //#region src/processors/observational-memory/subconscious/knowledge-write-tools.ts
16051
+ const CURATOR_IDENTITY = "subconscious:curate";
16052
+ const MAX_GUIDANCE_LENGTH = 4e3;
16053
+ const scopeLevelSchema = {
16054
+ type: "string",
16055
+ enum: [
16056
+ "org",
16057
+ "resource",
16058
+ "thread"
16059
+ ]
16060
+ };
16061
+ async function getStore(memory) {
16062
+ const store = await memory.storage.getStore("knowledge");
16063
+ if (!store) throw new Error("Knowledge write tools require a configured knowledge storage domain.");
16064
+ return store;
16065
+ }
16066
+ function resolveWriteScope(options, level) {
16067
+ const scope = expandKnowledgeScope(options.scope, level ?? options.defaultScope);
16068
+ assertKnowledgeScopeWithinCeiling(scope, options.maxScope);
16069
+ return scope;
16070
+ }
16071
+ function requireVisible(scope, options, label) {
16072
+ if (!isKnowledgeScopeVisible(scope, options.scope)) throw new Error(`${label} is outside the curator's visible scope.`);
16073
+ }
16074
+ function createKnowledgeWriteTools(memory, options) {
16075
+ return {
16076
+ knowledge_append: createTool({
16077
+ id: "knowledge_append",
16078
+ description: "Append a scoped record to an existing node. Provenance and capture time are stamped by code.",
16079
+ inputSchema: {
16080
+ type: "object",
16081
+ properties: {
16082
+ node: {
16083
+ type: "string",
16084
+ minLength: 1
16085
+ },
16086
+ text: {
16087
+ type: "string",
16088
+ minLength: 1
16089
+ },
16090
+ scope: scopeLevelSchema,
16091
+ when: { type: "string" }
16092
+ },
16093
+ required: ["node", "text"],
16094
+ additionalProperties: false
16095
+ },
16096
+ execute: async (input) => {
16097
+ const value = input;
16098
+ const store = await getStore(memory);
16099
+ const parent = await store.getNode(value.node);
16100
+ if (!parent || parent.mergedInto) throw new Error(`Knowledge node not found: ${value.node}`);
16101
+ requireVisible(parent.scope, options, "Knowledge node");
16102
+ const scope = resolveWriteScope(options, value.scope);
16103
+ const when = value.when ? new Date(value.when) : void 0;
16104
+ if (when && Number.isNaN(when.getTime())) throw new Error("KnowledgeRecord when must be a valid date.");
16105
+ return store.appendKnowledge({
16106
+ node: parent.id,
16107
+ text: value.text,
16108
+ scope,
16109
+ sourceThreadId: options.sourceThreadId,
16110
+ when,
16111
+ maxScope: options.maxScope,
16112
+ resolutionScope: options.scope,
16113
+ defaultScope: expandKnowledgeScope(options.scope, options.defaultScope)
16114
+ });
16115
+ }
16116
+ }),
16117
+ knowledge_remove: createTool({
16118
+ id: "knowledge_remove",
16119
+ description: "Soft-delete a visible record. Curators cannot restore or physically erase knowledge records.",
16120
+ inputSchema: {
16121
+ type: "object",
16122
+ properties: { recordId: {
16123
+ type: "string",
16124
+ minLength: 1
16125
+ } },
16126
+ required: ["recordId"],
16127
+ additionalProperties: false
16128
+ },
16129
+ execute: async (input) => {
16130
+ const store = await getStore(memory);
16131
+ const record = await store.getKnowledge({
16132
+ id: input.recordId,
16133
+ includeDeleted: true
16134
+ });
16135
+ if (!record) throw new Error(`KnowledgeRecord not found: ${input.recordId}`);
16136
+ requireVisible(record.scope, options, "KnowledgeRecord");
16137
+ return store.removeKnowledge({
16138
+ id: record.id,
16139
+ deletedBy: CURATOR_IDENTITY
16140
+ });
16141
+ }
16142
+ }),
16143
+ knowledge_update_node: createTool({
16144
+ id: "knowledge_update_node",
16145
+ description: "Update a visible node name or kind using optimistic concurrency.",
16146
+ inputSchema: {
16147
+ type: "object",
16148
+ properties: {
16149
+ node: {
16150
+ type: "string",
16151
+ minLength: 1
16152
+ },
16153
+ expectedVersion: {
16154
+ type: "integer",
16155
+ minimum: 1
16156
+ },
16157
+ name: {
16158
+ type: "string",
16159
+ minLength: 1
16160
+ },
16161
+ kind: {
16162
+ type: "string",
16163
+ minLength: 1
16164
+ }
16165
+ },
16166
+ required: ["node", "expectedVersion"],
16167
+ anyOf: [{ required: ["name"] }, { required: ["kind"] }],
16168
+ additionalProperties: false
16169
+ },
16170
+ execute: async (input) => {
16171
+ const value = input;
16172
+ const store = await getStore(memory);
16173
+ const node = await store.getNode(value.node);
16174
+ if (!node || node.mergedInto) throw new Error(`Knowledge node not found: ${value.node}`);
16175
+ requireVisible(node.scope, options, "Knowledge node");
16176
+ return store.updateNode({
16177
+ id: node.id,
16178
+ version: value.expectedVersion,
16179
+ name: value.name,
16180
+ kind: value.kind
16181
+ });
16182
+ }
16183
+ }),
16184
+ knowledge_merge_nodes: createTool({
16185
+ id: "knowledge_merge_nodes",
16186
+ description: "Merge a visible duplicate node into another visible node using source-version CAS.",
16187
+ inputSchema: {
16188
+ type: "object",
16189
+ properties: {
16190
+ sourceId: {
16191
+ type: "string",
16192
+ minLength: 1
16193
+ },
16194
+ targetId: {
16195
+ type: "string",
16196
+ minLength: 1
16197
+ },
16198
+ sourceVersion: {
16199
+ type: "integer",
16200
+ minimum: 1
16201
+ }
16202
+ },
16203
+ required: [
16204
+ "sourceId",
16205
+ "targetId",
16206
+ "sourceVersion"
16207
+ ],
16208
+ additionalProperties: false
16209
+ },
16210
+ execute: async (input) => {
16211
+ const value = input;
16212
+ const store = await getStore(memory);
16213
+ const [source, target] = await Promise.all([store.getNode(value.sourceId), store.getNode(value.targetId)]);
16214
+ if (!source || !target) throw new Error("Knowledge merge requires two existing nodes.");
16215
+ requireVisible(source.scope, options, "Knowledge merge source");
16216
+ requireVisible(target.scope, options, "Knowledge merge target");
16217
+ return store.mergeNodes(value);
16218
+ }
16219
+ }),
16220
+ knowledge_rescope: createTool({
16221
+ id: "knowledge_rescope",
16222
+ description: "Change a record visibility scope without exceeding its stamped ceiling.",
16223
+ inputSchema: {
16224
+ type: "object",
16225
+ properties: {
16226
+ recordId: {
16227
+ type: "string",
16228
+ minLength: 1
16229
+ },
16230
+ scope: scopeLevelSchema
16231
+ },
16232
+ required: ["recordId", "scope"],
16233
+ additionalProperties: false
16234
+ },
16235
+ execute: async (input) => {
16236
+ const value = input;
16237
+ const store = await getStore(memory);
16238
+ const record = await store.getKnowledge({ id: value.recordId });
16239
+ if (!record) throw new Error(`KnowledgeRecord not found: ${value.recordId}`);
16240
+ requireVisible(record.scope, options, "KnowledgeRecord");
16241
+ const scope = resolveWriteScope(options, value.scope);
16242
+ assertKnowledgeScopeWithinCeiling(scope, record.maxScope);
16243
+ return store.rescopeKnowledge({
16244
+ id: record.id,
16245
+ scope
16246
+ });
16247
+ }
16248
+ }),
16249
+ knowledge_write_node_content: createTool({
16250
+ id: "knowledge_write_node_content",
16251
+ description: "Create or replace long-form content on a scoped knowledge node. Existing nodes require expectedVersion.",
16252
+ inputSchema: {
16253
+ type: "object",
16254
+ properties: {
16255
+ name: {
16256
+ type: "string",
16257
+ minLength: 1
16258
+ },
16259
+ kind: {
16260
+ type: "string",
16261
+ minLength: 1
16262
+ },
16263
+ content: {
16264
+ type: "string",
16265
+ minLength: 1
16266
+ },
16267
+ scope: scopeLevelSchema,
16268
+ expectedVersion: {
16269
+ type: "integer",
16270
+ minimum: 1
16271
+ }
16272
+ },
16273
+ required: ["name", "content"],
16274
+ additionalProperties: false
16275
+ },
16276
+ execute: async (input) => {
16277
+ const value = input;
16278
+ const trimmedName = value.name.trim();
16279
+ const reservedName = trimmedName.toLowerCase();
16280
+ const name = reservedName === "capture-guidance" ? reservedName : trimmedName;
16281
+ if (reservedName === "capture-guidance" && value.content.length > MAX_GUIDANCE_LENGTH) throw new Error(`capture-guidance is limited to ${MAX_GUIDANCE_LENGTH} characters.`);
16282
+ const store = await getStore(memory);
16283
+ const scope = resolveWriteScope(options, value.scope);
16284
+ const resolvedNode = await store.resolveNode({
16285
+ name,
16286
+ scope
16287
+ });
16288
+ const existing = resolvedNode && knowledgeScopeKey(resolvedNode.scope) === knowledgeScopeKey(scope) ? resolvedNode : null;
16289
+ if (!existing) {
16290
+ if (value.expectedVersion !== void 0) throw new Error("expectedVersion is only valid for an existing node.");
16291
+ return store.createNode({
16292
+ name,
16293
+ kind: value.kind ?? "document",
16294
+ content: value.content,
16295
+ scope,
16296
+ resolutionScope: options.scope
16297
+ });
16298
+ }
16299
+ if (value.expectedVersion === void 0) throw new Error("Updating node content requires expectedVersion.");
16300
+ return store.updateNode({
16301
+ id: existing.id,
16302
+ version: value.expectedVersion,
16303
+ kind: value.kind,
16304
+ content: value.content,
16305
+ resolutionScope: options.scope
16306
+ });
16307
+ }
16308
+ })
16309
+ };
16310
+ }
16311
+ //#endregion
16312
+ //#region src/processors/observational-memory/subconscious/semantic-index.ts
16313
+ const DEFAULT_BATCH_SIZE = 50;
16314
+ const MAX_DRAIN_BATCHES = 100;
16315
+ var StaleKnowledgeSemanticIndexError = class extends Error {
16316
+ constructor(message, options) {
16317
+ super(message, options);
16318
+ this.name = "StaleKnowledgeSemanticIndexError";
16319
+ }
16320
+ };
16321
+ var KnowledgeSemanticIndexCoordinator = class {
16322
+ #knowledge;
16323
+ #vector;
16324
+ #embedder;
16325
+ #embedderOptions;
16326
+ #workerId;
16327
+ #batchSize;
16328
+ #draining = /* @__PURE__ */ new Map();
16329
+ constructor(config) {
16330
+ this.#knowledge = config.knowledge;
16331
+ this.#vector = config.vector;
16332
+ this.#embedder = config.embedder;
16333
+ this.#embedderOptions = config.embedderOptions;
16334
+ this.#workerId = config.workerId ?? `knowledge-index-${crypto.randomUUID()}`;
16335
+ this.#batchSize = config.batchSize ?? DEFAULT_BATCH_SIZE;
16336
+ }
16337
+ async drain(scope) {
16338
+ const key = scope?.join("") ?? "*";
16339
+ const active = this.#draining.get(key);
16340
+ if (active) return active;
16341
+ const draining = this.#drain(scope).finally(() => {
16342
+ this.#draining.delete(key);
16343
+ });
16344
+ this.#draining.set(key, draining);
16345
+ return draining;
16346
+ }
16347
+ async search(query, scope, limit = 10) {
16348
+ await this.drain(scope);
16349
+ const embedding = (await this.#embedder.doEmbed({
16350
+ values: [query],
16351
+ ...this.#embedderOptions ?? {}
16352
+ })).embeddings[0];
16353
+ if (!embedding?.length) throw new Error("Embedder returned no vector for knowledge search query.");
16354
+ const indexName = this.#indexName(embedding.length);
16355
+ if (!(await this.#knowledgeIndexes()).includes(indexName)) throw new StaleKnowledgeSemanticIndexError(`Knowledge semantic index ${indexName} is unavailable. Capture or index knowledge before searching.`);
16356
+ const visibleScopeKeys = scope.map((_, index) => scope.slice(0, index + 1).join(""));
16357
+ const batches = await Promise.all(visibleScopeKeys.map((scopeKey) => this.#vector.query({
16358
+ indexName,
16359
+ queryVector: embedding,
16360
+ topK: limit,
16361
+ filter: { scope_key: scopeKey }
16362
+ })));
16363
+ const deduped = /* @__PURE__ */ new Map();
16364
+ for (const candidate of batches.flat()) {
16365
+ const candidateScope = candidate.metadata?.scope;
16366
+ if (!Array.isArray(candidateScope)) continue;
16367
+ let visible = false;
16368
+ try {
16369
+ visible = isKnowledgeScopeVisible(canonicalizeKnowledgeScope(candidateScope.map(String)), scope);
16370
+ } catch {
16371
+ continue;
16372
+ }
16373
+ if (!visible) continue;
16374
+ const existing = deduped.get(candidate.id);
16375
+ if (!existing || candidate.score > existing.score) deduped.set(candidate.id, candidate);
16376
+ }
16377
+ return [...deduped.values()].sort((left, right) => right.score - left.score || left.id.localeCompare(right.id)).slice(0, limit);
16378
+ }
16379
+ async #drain(scope) {
16380
+ let processed = 0;
16381
+ for (let batch = 0; batch < MAX_DRAIN_BATCHES; batch++) {
16382
+ const entries = await this.#knowledge.claimSemanticOutbox({
16383
+ workerId: this.#workerId,
16384
+ limit: this.#batchSize,
16385
+ scope
16386
+ });
16387
+ if (entries.length === 0) {
16388
+ const [pending, processing] = await Promise.all([this.#knowledge.listSemanticOutbox({
16389
+ status: "pending",
16390
+ scope,
16391
+ limit: 1
16392
+ }), this.#knowledge.listSemanticOutbox({
16393
+ status: "processing",
16394
+ scope,
16395
+ limit: 1
16396
+ })]);
16397
+ 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.");
16398
+ return processed;
16399
+ }
16400
+ for (let index = 0; index < entries.length; index++) {
16401
+ const entry = entries[index];
16402
+ try {
16403
+ await this.#apply(entry);
16404
+ await this.#knowledge.completeSemanticOutbox({
16405
+ ids: [entry.id],
16406
+ workerId: this.#workerId
16407
+ });
16408
+ processed++;
16409
+ } catch (error) {
16410
+ await this.#knowledge.releaseSemanticOutbox({
16411
+ ids: entries.slice(index).map((pendingEntry) => pendingEntry.id),
16412
+ workerId: this.#workerId
16413
+ });
16414
+ throw new StaleKnowledgeSemanticIndexError(`Knowledge semantic index is stale because operation ${entry.id} could not be applied.`, { cause: error });
16415
+ }
16416
+ }
16417
+ }
16418
+ throw new StaleKnowledgeSemanticIndexError(`Knowledge semantic index remained stale after ${MAX_DRAIN_BATCHES} processing batches.`);
16419
+ }
16420
+ async #apply(entry) {
16421
+ if (entry.operation === "delete") {
16422
+ await this.#deleteDocument(entry.documentId);
16423
+ return;
16424
+ }
16425
+ const document = await this.#loadDocument(entry);
16426
+ if (!document) {
16427
+ await this.#deleteDocument(entry.documentId);
16428
+ return;
16429
+ }
16430
+ const embedding = (await this.#embedder.doEmbed({
16431
+ values: [document.text],
16432
+ ...this.#embedderOptions ?? {}
16433
+ })).embeddings[0];
16434
+ if (!embedding?.length) throw new Error(`Embedder returned no vector for ${entry.documentId}`);
16435
+ const indexName = this.#indexName(embedding.length);
16436
+ const indexes = await this.#knowledgeIndexes();
16437
+ if (!indexes.includes(indexName)) await this.#vector.createIndex({
16438
+ indexName,
16439
+ dimension: embedding.length
16440
+ });
16441
+ for (const existingIndex of indexes) if (existingIndex !== indexName) await this.#vector.deleteVectors({
16442
+ indexName: existingIndex,
16443
+ ids: [entry.documentId]
16444
+ });
16445
+ await this.#vector.upsert({
16446
+ indexName,
16447
+ ids: [entry.documentId],
16448
+ vectors: [embedding],
16449
+ metadata: [this.#metadata(document)]
16450
+ });
16451
+ }
16452
+ async #loadDocument(entry) {
16453
+ if (entry.documentType === "node") {
16454
+ const node = await this.#knowledge.getNode(entry.documentId.slice(15));
16455
+ if (!node || node.mergedInto) return null;
16456
+ return {
16457
+ text: `${node.name}\n${node.content ?? ""}`,
16458
+ name: node.name,
16459
+ scope: node.scope,
16460
+ recordId: node.id,
16461
+ type: "node"
16462
+ };
16463
+ }
16464
+ const record = await this.#knowledge.getKnowledge({
16465
+ id: entry.documentId.slice(17),
16466
+ includeDeleted: true
16467
+ });
16468
+ if (!record || record.deletedAt) return null;
16469
+ const node = await this.#knowledge.getNode(record.node);
16470
+ if (!node) return null;
16471
+ return {
16472
+ text: `${node.name}\n${record.text}`,
16473
+ name: node.name,
16474
+ scope: record.scope,
16475
+ recordId: node.id,
16476
+ type: "record"
16477
+ };
16478
+ }
16479
+ async #deleteDocument(documentId) {
16480
+ for (const indexName of await this.#knowledgeIndexes()) await this.#vector.deleteVectors({
16481
+ indexName,
16482
+ ids: [documentId]
16483
+ });
16484
+ }
16485
+ async #knowledgeIndexes() {
16486
+ const prefix = `knowledge${this.#vector.indexSeparator ?? "_"}documents`;
16487
+ return (await this.#vector.listIndexes()).filter((index) => index === prefix || index.startsWith(`${prefix}${this.#vector.indexSeparator ?? "_"}dimension`));
16488
+ }
16489
+ #indexName(dimension) {
16490
+ const separator = this.#vector.indexSeparator ?? "_";
16491
+ return `knowledge${separator}documents${separator}dimension${separator}${dimension}`;
16492
+ }
16493
+ #metadata(document) {
16494
+ const metadata = {
16495
+ document_type: document.type,
16496
+ record_id: document.recordId,
16497
+ name: document.name,
16498
+ scope: [...document.scope],
16499
+ scope_key: document.scope.join(""),
16500
+ text: document.text
16501
+ };
16502
+ for (const entry of document.scope) {
16503
+ const separator = entry.indexOf(":");
16504
+ metadata[`scope_${entry.slice(0, separator)}`] = entry.slice(separator + 1);
16505
+ }
16506
+ return metadata;
16507
+ }
16508
+ };
16509
+ //#endregion
16510
+ //#region src/processors/observational-memory/subconscious/index.ts
16511
+ var subconscious_exports = /* @__PURE__ */ __exportAll({
16512
+ DEFAULT_MAX_PINS: () => 20,
16513
+ DEFAULT_PINNED_MAX_CHARACTERS: () => DEFAULT_PINNED_MAX_CHARACTERS,
16514
+ MAX_PINNED_MAX_CHARACTERS: () => MAX_PINNED_MAX_CHARACTERS,
16515
+ PINNED_DELTA_TAG: () => PINNED_DELTA_TAG,
16516
+ PINNED_NODE_KIND: () => PINNED_NODE_KIND,
16517
+ PINNED_NODE_NAME: () => PINNED_NODE_NAME,
16518
+ PINNED_NODE_SCOPE_LEVEL: () => PINNED_NODE_SCOPE_LEVEL,
16519
+ PINNED_SNAPSHOT_TAG: () => PINNED_SNAPSHOT_TAG,
16520
+ PinnedStateProcessor: () => PinnedStateProcessor,
16521
+ SUBCONSCIOUS_ACTIVITY_STATE_ID: () => SUBCONSCIOUS_ACTIVITY_STATE_ID,
16522
+ SUBCONSCIOUS_PINS_STATE_ID: () => SUBCONSCIOUS_PINS_STATE_ID,
16523
+ Subconscious: () => Subconscious
16524
+ });
16525
+ const BUILT_IN_OBSERVATION = /* @__PURE__ */ new Set(["capture", "remind"]);
16526
+ const BUILT_IN_REFLECTION = /* @__PURE__ */ new Set(["curate", "learn"]);
16527
+ const DEFAULT_MAX_STEPS = 5;
16528
+ const DEFAULT_RECENT_UPDATES = 10;
16529
+ const MAX_RECENT_UPDATES = 100;
16530
+ function entryName(entry) {
16531
+ return typeof entry === "string" ? entry : entry.name.trim();
16532
+ }
16533
+ function assertUniqueNames(entries, phase) {
16534
+ const seen = /* @__PURE__ */ new Set();
16535
+ for (const entry of entries) {
16536
+ const name = entryName(entry);
16537
+ if (!name) throw new Error(`Subconscious ${phase} agent name is required.`);
16538
+ if (seen.has(name)) throw new Error(`Duplicate Subconscious ${phase} agent: ${name}`);
16539
+ seen.add(name);
16540
+ }
16541
+ }
16542
+ function boundedSteps(entry, fallback) {
16543
+ const steps = entry?.maxSteps ?? fallback;
16544
+ if (!Number.isInteger(steps) || steps < 1 || steps > 25) throw new Error("Subconscious maxSteps must be an integer between 1 and 25.");
16545
+ return steps;
16546
+ }
16547
+ function resolveExtractor(entry) {
16548
+ const config = typeof entry === "string" ? void 0 : entry;
16549
+ const name = entryName(entry);
16550
+ return {
16551
+ name,
16552
+ instructions: config?.instructions,
16553
+ builtIn: name === "capture"
16554
+ };
16555
+ }
16556
+ function resolveAgent(entry, builtIns, globalModel, globalMaxSteps) {
16557
+ const config = typeof entry === "string" ? void 0 : entry;
16558
+ const name = entryName(entry);
16559
+ return {
16560
+ name,
16561
+ instructions: config?.instructions,
16562
+ model: config?.model ?? globalModel,
16563
+ agent: config?.agent,
16564
+ maxSteps: boundedSteps(config, globalMaxSteps),
16565
+ builtIn: builtIns.has(name)
16566
+ };
16567
+ }
16568
+ /**
16569
+ * Configures experimental autonomous knowledge extraction and reflection.
16570
+ *
16571
+ * @experimental This API may change without notice.
16572
+ */
16573
+ var Subconscious = class {
16574
+ config;
16575
+ resolved;
16576
+ constructor(config = {}) {
16577
+ const observation = config.observation ?? ["capture", "remind"];
16578
+ const reflection = config.reflection ?? ["curate", "learn"];
16579
+ assertUniqueNames(observation, "observation");
16580
+ assertUniqueNames(reflection, "reflection");
16581
+ const maxSteps = boundedSteps(config, DEFAULT_MAX_STEPS);
16582
+ for (const entry of observation) this.#validateObservationEntry(entry);
16583
+ for (const entry of reflection) this.#validateReflectionEntry(entry);
16584
+ const recentUpdates = config.activity === false ? false : config.activity?.recentUpdates ?? DEFAULT_RECENT_UPDATES;
16585
+ 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}.`);
16586
+ const pins = config.pins === void 0 || config.pins === false ? false : {
16587
+ maxPins: (config.pins === true ? void 0 : config.pins.maxPins) ?? 20,
16588
+ maxCharacters: (config.pins === true ? void 0 : config.pins.maxCharacters) ?? 2e3,
16589
+ capturePinning: (config.pins === true ? void 0 : config.pins.capturePinning) ?? false
16590
+ };
16591
+ if (pins !== false) {
16592
+ if (!Number.isInteger(pins.maxPins) || pins.maxPins < 1) throw new Error("Subconscious pins.maxPins must be a positive integer.");
16593
+ 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}.`);
16594
+ }
16595
+ if (config.curationCadence !== void 0 && (!Number.isInteger(config.curationCadence) || config.curationCadence < 1)) throw new Error("Subconscious curationCadence must be a positive integer.");
16596
+ this.config = Object.freeze({
16597
+ ...config,
16598
+ observation: [...observation],
16599
+ reflection: [...reflection]
16600
+ });
16601
+ this.resolved = Object.freeze({
16602
+ observation: observation.map((entry) => entryName(entry) === "remind" ? resolveAgent(entry, BUILT_IN_OBSERVATION, config.model, maxSteps) : resolveExtractor(entry)),
16603
+ reflection: reflection.map((entry) => resolveAgent(entry, BUILT_IN_REFLECTION, config.model, maxSteps)),
16604
+ defaultScope: config.defaultScope ?? "resource",
16605
+ maxScope: config.maxScope,
16606
+ learnedGuidance: config.learnedGuidance !== false,
16607
+ tools: config.tools !== false,
16608
+ activity: recentUpdates === false ? false : { recentUpdates },
16609
+ pins,
16610
+ curationCadence: config.curationCadence
16611
+ });
16612
+ }
16613
+ createObservationExtractors(omModel) {
16614
+ const extractors = [];
16615
+ for (const entry of this.config.observation ?? []) {
16616
+ const name = entryName(entry);
16617
+ if (name === "capture") extractors.push(new SubconsciousCaptureExtractor({
16618
+ config: typeof entry === "string" ? void 0 : entry,
16619
+ defaultScope: this.resolved.defaultScope,
16620
+ maxScope: this.resolved.maxScope,
16621
+ learnedGuidance: this.resolved.learnedGuidance,
16622
+ activityRecentUpdates: this.resolved.activity === false ? void 0 : this.resolved.activity.recentUpdates,
16623
+ pins: this.resolved.pins
16624
+ }));
16625
+ else if (name === "remind") {
16626
+ const resolved = this.resolved.observation.find((agent) => agent.name === name);
16627
+ if (resolved) extractors.push(new SubconsciousRemindExtractor(resolved, omModel));
16628
+ } else if (!BUILT_IN_OBSERVATION.has(name)) {
16629
+ const custom = entry;
16630
+ extractors.push(new Extractor({
16631
+ name: custom.name,
16632
+ instructions: custom.instructions?.trim() || `Extract ${custom.name} from the current observations.`,
16633
+ schema: custom.schema,
16634
+ metadataKeyPath: false,
16635
+ includePreviousExtraction: false,
16636
+ onExtracted: custom.onExtracted
16637
+ }));
16638
+ }
16639
+ }
16640
+ return extractors;
16641
+ }
16642
+ #validateObservationEntry(entry) {
16643
+ const name = entryName(entry);
16644
+ if (typeof entry === "string") {
16645
+ if (!BUILT_IN_OBSERVATION.has(name)) throw new Error(`Unknown Subconscious observation agent: ${name}`);
16646
+ return;
16647
+ }
16648
+ if (BUILT_IN_OBSERVATION.has(name)) {
16649
+ if (name === "capture") {
16650
+ if ("model" in entry || "maxSteps" in entry) throw new Error("Subconscious capture shares the Observer model and does not accept model or maxSteps.");
16651
+ 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.");
16652
+ }
16653
+ return;
16654
+ }
16655
+ 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.`);
16656
+ 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.`);
16657
+ }
16658
+ #validateReflectionEntry(entry) {
16659
+ const name = entryName(entry);
16660
+ if (typeof entry === "string") {
16661
+ if (!BUILT_IN_REFLECTION.has(name)) throw new Error(`Unknown Subconscious reflection agent: ${name}`);
16662
+ return;
16663
+ }
16664
+ 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.`);
16665
+ 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.`);
16666
+ }
16667
+ };
16668
+ //#endregion
16669
+ //#region src/processors/observational-memory/subconscious/curate.ts
16670
+ const CURATION_AGENT = "curate";
16671
+ const DEFAULT_INSTRUCTIONS$1 = `Maintain durable scoped knowledge from the committed observation worklist.
16672
+
16673
+ 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.
16674
+
16675
+ 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.`;
16676
+ 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.`;
16677
+ function resolveScope$1(context) {
16678
+ const organizationId = context.requestContext?.get("organizationId");
16679
+ if (typeof organizationId !== "string" || !organizationId.trim()) throw new Error("Subconscious curate requires organizationId in the request context.");
16680
+ return canonicalizeKnowledgeScope([
16681
+ `org:${organizationId}`,
16682
+ `resource:${resolveKnowledgeResourceId(context.requestContext, context.resourceId)}`,
16683
+ `thread:${context.parentThreadId}`
16684
+ ]);
16685
+ }
16686
+ async function readWorklist$1(store, sourceThreadId, scope, after) {
16687
+ const records = [];
16688
+ let cursor = after;
16689
+ do {
16690
+ const page = await store.knowledgeBySource({
16691
+ sourceThreadId,
16692
+ scope,
16693
+ after: cursor,
16694
+ limit: 100,
16695
+ includeDeleted: true
16696
+ });
16697
+ records.push(...page.records);
16698
+ cursor = page.nextCursor;
16699
+ } while (cursor && records.length < 500);
16700
+ return {
16701
+ records,
16702
+ hasMore: Boolean(cursor)
16703
+ };
16704
+ }
16705
+ function createCuratorHandler(memory, subconscious, curatorMemory = memory, options) {
16706
+ const config = subconscious.reflection.find((agent) => agent.name === CURATION_AGENT);
16707
+ if (!config) return async () => "no-op";
16708
+ return async (context) => {
16709
+ let store;
16710
+ let scope;
16711
+ try {
16712
+ scope = resolveScope$1(context);
16713
+ store = await memory.storage.getStore("knowledge");
16714
+ if (!store) throw new Error("Subconscious curate requires a configured knowledge storage domain.");
16715
+ const cursor = await store.getCurationCursor({
16716
+ sourceThreadId: context.parentThreadId,
16717
+ agent: CURATION_AGENT
16718
+ });
16719
+ const worklist = await readWorklist$1(store, context.parentThreadId, scope, cursor?.lastKnowledgeId);
16720
+ if (!worklist.records.length && !context.observations.trim()) return "no-op";
16721
+ 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)}`, {
16722
+ requestContext: context.requestContext,
16723
+ abortSignal: context.abortSignal,
16724
+ maxSteps: config.maxSteps,
16725
+ memory: {
16726
+ thread: `subconscious:${context.parentThreadId}:curate`,
16727
+ resource: context.resourceId
16728
+ }
16729
+ });
16730
+ if (worklist.records.length) {
16731
+ const acknowledgedId = [...result.text.matchAll(/<curation-complete\s+through=["']([^"']+)["']\s*\/>/gi)].at(-1)?.[1];
16732
+ if (!acknowledgedId || !worklist.records.some((record) => record.id === acknowledgedId)) throw new Error("Curator did not acknowledge a valid processed KnowledgeRecord cursor.");
16733
+ await store.advanceCurationCursor({
16734
+ sourceThreadId: context.parentThreadId,
16735
+ agent: CURATION_AGENT,
16736
+ lastKnowledgeId: acknowledgedId
16737
+ });
16738
+ }
16739
+ return "ran";
16740
+ } catch (error) {
16741
+ const message = `curate: ${error instanceof Error ? error.message : String(error)}`;
16742
+ await context.writer?.custom({
16743
+ type: "data-subconscious-error",
16744
+ data: {
16745
+ agent: "curate",
16746
+ error: message
16747
+ }
16748
+ });
16749
+ if (store && scope) await publishSubconsciousActivity({
16750
+ store,
16751
+ scope,
16752
+ recentUpdates: subconscious.activity === false ? 10 : subconscious.activity.recentUpdates,
16753
+ sendStateSignal: context.sendStateSignal,
16754
+ errors: [message]
16755
+ });
16756
+ else await publishSubconsciousError({
16757
+ error: message,
16758
+ sendStateSignal: context.sendStateSignal
16759
+ });
16760
+ throw error;
16761
+ }
16762
+ };
16763
+ }
16764
+ async function createCuratorAgent(memory, curatorMemory, context, scope, config, subconscious, omModel) {
16765
+ const model = await resolveSubconsciousAgentModel({
16766
+ config,
16767
+ omModel,
16768
+ mainAgent: context.mainAgent,
16769
+ requestContext: context.requestContext
16770
+ });
16771
+ if (!model) throw new Error("Subconscious curate requires the main agent to resolve its model.");
16772
+ return new Agent({
16773
+ id: `subconscious-curate-${context.parentThreadId}`,
16774
+ name: "Subconscious Curate",
16775
+ instructions: [
16776
+ DEFAULT_INSTRUCTIONS$1,
16777
+ subconscious.pins ? PINNED_INSTRUCTIONS : void 0,
16778
+ config.instructions?.trim()
16779
+ ].filter(Boolean).join("\n\n"),
16780
+ model,
16781
+ memory: curatorMemory,
16782
+ tools: {
16783
+ ...createKnowledgeTools(memory),
16784
+ ...createKnowledgeWriteTools(memory, {
16785
+ scope,
16786
+ sourceThreadId: context.parentThreadId,
16787
+ defaultScope: subconscious.defaultScope,
16788
+ maxScope: subconscious.maxScope
16789
+ }),
16790
+ ...subconscious.pins ? createPinnedTools(memory, {
16791
+ scope,
16792
+ sourceThreadId: context.parentThreadId,
16793
+ defaultScope: subconscious.defaultScope,
16794
+ maxScope: subconscious.maxScope,
16795
+ maxPins: subconscious.pins.maxPins,
16796
+ maxCharacters: subconscious.pins.maxCharacters
16797
+ }) : {}
16798
+ }
16799
+ });
16800
+ }
16801
+ //#endregion
16802
+ //#region src/processors/observational-memory/subconscious/learn.ts
16803
+ const LEARN_AGENT = "learn";
16804
+ const ULID_ALPHABET = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
16805
+ const DEFAULT_INSTRUCTIONS = `Learn reusable skills from the full pre-reflection observations and pending knowledge records.
16806
+
16807
+ 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.
16808
+
16809
+ 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.
16810
+
16811
+ 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.`;
16812
+ function resolveScope(context) {
16813
+ const organizationId = context.requestContext?.get("organizationId");
16814
+ if (typeof organizationId !== "string" || !organizationId.trim()) throw new Error("Subconscious learn requires organizationId in the request context.");
16815
+ return canonicalizeKnowledgeScope([
16816
+ `org:${organizationId}`,
16817
+ `resource:${resolveKnowledgeResourceId(context.requestContext, context.resourceId)}`,
16818
+ `thread:${context.parentThreadId}`
16819
+ ]);
16820
+ }
16821
+ async function readWorklist(store, sourceThreadId, scope, after) {
16822
+ const records = [];
16823
+ let cursor = after;
16824
+ do {
16825
+ const page = await store.knowledgeBySource({
16826
+ sourceThreadId,
16827
+ scope,
16828
+ after: cursor,
16829
+ limit: 100
16830
+ });
16831
+ records.push(...page.records);
16832
+ cursor = page.nextCursor;
16833
+ } while (cursor && records.length < 500);
16834
+ return {
16835
+ records,
16836
+ hasMore: Boolean(cursor)
16837
+ };
16838
+ }
16839
+ function evidenceRecordId(sourceRecordId, skillName) {
16840
+ const hash = createHash("sha256").update(`${skillName.trim().toLocaleLowerCase()}\0${sourceRecordId}`).digest();
16841
+ let suffix = "";
16842
+ for (let index = 0; index < 16; index++) suffix += ULID_ALPHABET[hash[index] & 31];
16843
+ return `${sourceRecordId.slice(0, 10)}${suffix}`;
16844
+ }
16845
+ function createLearnerRecordSkillTool(input) {
16846
+ return createTool({
16847
+ id: "knowledge_record_skill",
16848
+ description: "Create or update one reusable skill using at least two distinct pending knowledge records. Evidence writes are idempotent across retries.",
16849
+ inputSchema: {
16850
+ type: "object",
16851
+ properties: {
16852
+ name: {
16853
+ type: "string",
16854
+ minLength: 1
16855
+ },
16856
+ procedure: {
16857
+ type: "string",
16858
+ minLength: 1
16859
+ },
16860
+ sourceRecordIds: {
16861
+ type: "array",
16862
+ items: {
16863
+ type: "string",
16864
+ minLength: 1
16865
+ },
16866
+ minItems: 2,
16867
+ uniqueItems: true
16868
+ }
16869
+ },
16870
+ required: [
16871
+ "name",
16872
+ "procedure",
16873
+ "sourceRecordIds"
16874
+ ],
16875
+ additionalProperties: false
16876
+ },
16877
+ execute: async (raw) => {
16878
+ const value = raw;
16879
+ const sourceIds = [...new Set(value.sourceRecordIds)];
16880
+ const pending = new Map(input.pendingRecords.map((record) => [record.id, record]));
16881
+ 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.");
16882
+ const normalizedName = value.name.trim();
16883
+ if (input.state.recordedName && input.state.recordedName.toLocaleLowerCase() !== normalizedName.toLocaleLowerCase()) throw new Error("The learner may record at most one skill per reflection.");
16884
+ input.state.recordedName = normalizedName;
16885
+ const nodeScope = expandKnowledgeScope(input.scope, input.defaultScope);
16886
+ let node = await input.store.resolveNode({
16887
+ name: normalizedName,
16888
+ scope: input.scope
16889
+ });
16890
+ if (node && node.kind !== "skill") throw new Error(`Knowledge node is not a skill: ${normalizedName}`);
16891
+ node ??= await input.store.createNode({
16892
+ name: normalizedName,
16893
+ kind: "skill",
16894
+ scope: nodeScope
16895
+ });
16896
+ const evidence = [];
16897
+ for (const sourceId of sourceIds) {
16898
+ const id = evidenceRecordId(sourceId, normalizedName);
16899
+ const existing = await input.store.getKnowledge({ id });
16900
+ if (existing) {
16901
+ evidence.push(existing);
16902
+ continue;
16903
+ }
16904
+ const source = pending.get(sourceId);
16905
+ try {
16906
+ evidence.push(await input.store.appendKnowledge({
16907
+ id,
16908
+ node: node.id,
16909
+ text: `Procedure: ${value.procedure.trim()} Evidence source: ${source.id}.`,
16910
+ scope: source.scope,
16911
+ sourceThreadId: `subconscious:${input.parentThreadId}:learn`,
16912
+ maxScope: source.maxScope ?? input.maxScope,
16913
+ resolutionScope: input.scope,
16914
+ defaultScope: nodeScope
16915
+ }));
16916
+ } catch (error) {
16917
+ const raced = await input.store.getKnowledge({ id });
16918
+ if (!raced) throw error;
16919
+ evidence.push(raced);
16920
+ }
16921
+ }
16922
+ return {
16923
+ node,
16924
+ evidence
16925
+ };
16926
+ }
16927
+ });
16928
+ }
16929
+ function composeReflectionAgentHandlers(handlers) {
16930
+ return async (context) => {
16931
+ for (const handler of handlers) try {
16932
+ await handler(context);
16933
+ } catch (error) {
16934
+ if (context.abortSignal?.aborted) throw error;
16935
+ }
16936
+ };
16937
+ }
16938
+ function createLearnerHandler(memory, subconscious, learnerMemory = memory, options) {
16939
+ const config = subconscious.reflection.find((agent) => agent.name === LEARN_AGENT);
16940
+ if (!config) return async () => {};
16941
+ return async (context) => {
16942
+ let store;
16943
+ let scope;
16944
+ try {
16945
+ scope = resolveScope(context);
16946
+ store = await memory.storage.getStore("knowledge");
16947
+ if (!store) throw new Error("Subconscious learn requires a configured knowledge storage domain.");
16948
+ const cursor = await store.getCurationCursor({
16949
+ sourceThreadId: context.parentThreadId,
16950
+ agent: LEARN_AGENT
16951
+ });
16952
+ const worklist = await readWorklist(store, context.parentThreadId, scope, cursor?.lastKnowledgeId);
16953
+ if (!worklist.records.length) return;
16954
+ 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)}`, {
16955
+ requestContext: context.requestContext,
16956
+ abortSignal: context.abortSignal,
16957
+ maxSteps: config.maxSteps,
16958
+ memory: {
16959
+ thread: `subconscious:${context.parentThreadId}:learn`,
16960
+ resource: context.resourceId
16961
+ }
16962
+ })).text.match(/<learning-complete\s+through=["']([^"']+)["']\s*\/>/i)?.[1];
16963
+ if (!acknowledgedId || !worklist.records.some((record) => record.id === acknowledgedId)) throw new Error("Learner did not acknowledge a valid reviewed record cursor.");
16964
+ await store.advanceCurationCursor({
16965
+ sourceThreadId: context.parentThreadId,
16966
+ agent: LEARN_AGENT,
16967
+ lastKnowledgeId: acknowledgedId
16968
+ });
16969
+ } catch (error) {
16970
+ const message = `learn: ${error instanceof Error ? error.message : String(error)}`;
16971
+ await context.writer?.custom({
16972
+ type: "data-subconscious-error",
16973
+ data: {
16974
+ agent: "learn",
16975
+ error: message
16976
+ }
16977
+ });
16978
+ if (store && scope) await publishSubconsciousActivity({
16979
+ store,
16980
+ scope,
16981
+ recentUpdates: subconscious.activity === false ? 10 : subconscious.activity.recentUpdates,
16982
+ sendStateSignal: context.sendStateSignal,
16983
+ errors: [message]
16984
+ });
16985
+ else await publishSubconsciousError({
16986
+ error: message,
16987
+ sendStateSignal: context.sendStateSignal
16988
+ });
16989
+ throw error;
16990
+ }
16991
+ };
16992
+ }
16993
+ async function createLearnerAgent(memory, learnerMemory, context, scope, pendingRecords, config, subconscious, omModel) {
16994
+ const model = await resolveSubconsciousAgentModel({
16995
+ config,
16996
+ omModel,
16997
+ mainAgent: context.mainAgent,
16998
+ requestContext: context.requestContext
16999
+ });
17000
+ if (!model) throw new Error("Subconscious learn requires the main agent to resolve its model.");
17001
+ const store = await memory.storage.getStore("knowledge");
17002
+ if (!store) throw new Error("Subconscious learn requires a configured knowledge storage domain.");
17003
+ const state = {};
17004
+ return new Agent({
17005
+ id: `subconscious-learn-${context.parentThreadId}`,
17006
+ name: "Subconscious Learn",
17007
+ instructions: [DEFAULT_INSTRUCTIONS, config.instructions?.trim()].filter(Boolean).join("\n\n"),
17008
+ model,
17009
+ memory: learnerMemory,
17010
+ tools: {
17011
+ ...createKnowledgeTools(memory, scope),
17012
+ ...createKnowledgeWriteTools(memory, {
17013
+ scope,
17014
+ sourceThreadId: context.parentThreadId,
17015
+ defaultScope: subconscious.defaultScope,
17016
+ maxScope: subconscious.maxScope
17017
+ }),
17018
+ knowledge_record_skill: createLearnerRecordSkillTool({
17019
+ store,
17020
+ scope,
17021
+ pendingRecords,
17022
+ parentThreadId: context.parentThreadId,
17023
+ defaultScope: subconscious.defaultScope,
17024
+ maxScope: subconscious.maxScope,
17025
+ state
17026
+ })
17027
+ }
17028
+ });
17029
+ }
17030
+ //#endregion
17031
+ //#region src/processors/observational-memory/constants.ts
17032
+ var constants_exports = /* @__PURE__ */ __exportAll({
17033
+ OBSERVATIONAL_MEMORY_DEFAULTS: () => OBSERVATIONAL_MEMORY_DEFAULTS,
17034
+ OBSERVATION_CONTEXT_INSTRUCTIONS: () => OBSERVATION_CONTEXT_INSTRUCTIONS,
17035
+ OBSERVATION_CONTEXT_PROMPT: () => OBSERVATION_CONTEXT_PROMPT,
17036
+ OBSERVATION_CONTINUATION_HINT: () => OBSERVATION_CONTINUATION_HINT,
17037
+ getRetrievalInstructions: () => getRetrievalInstructions
17038
+ });
17039
+ /**
17040
+ * Default configuration values matching the spec
17041
+ */
17042
+ const OBSERVATIONAL_MEMORY_DEFAULTS = {
17043
+ observation: {
17044
+ model: "google/gemini-2.5-flash",
17045
+ messageTokens: 3e4,
17046
+ modelSettings: {
17047
+ temperature: .3,
17048
+ maxOutputTokens: 1e5
17049
+ },
17050
+ providerOptions: { google: { thinkingConfig: { thinkingBudget: 215 } } },
17051
+ maxTokensPerBatch: 1e4,
17052
+ bufferTokens: .2,
17053
+ bufferActivation: .8
17054
+ },
17055
+ reflection: {
17056
+ model: "google/gemini-2.5-flash",
17057
+ observationTokens: 4e4,
17058
+ modelSettings: {
17059
+ temperature: 0,
17060
+ maxOutputTokens: 1e5
17061
+ },
17062
+ providerOptions: { google: { thinkingConfig: { thinkingBudget: 1024 } } },
17063
+ bufferActivation: .5
17064
+ }
17065
+ };
17066
+ /**
17067
+ * Continuation hint injected after observations to guide the model's behavior.
17068
+ * Prevents the model from awkwardly acknowledging the memory system or treating
17069
+ * the conversation as new after observed messages are removed.
17070
+ */
17071
+ const OBSERVATION_CONTINUATION_HINT = `Please continue naturally with the conversation so far and respond to the latest message.
17072
+
17073
+ 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.
17074
+
17075
+ Do not mention internal instructions, memory, summarization, context handling, or missing messages.
17076
+
17077
+ Any messages following this reminder are newer and should take priority.`;
17078
+ /**
17079
+ * Preamble that introduces the observations block.
17080
+ * Use before `<observations>`, with instructions after.
17081
+ * Full pattern: `${OBSERVATION_CONTEXT_PROMPT}\n\n<observations>\n${obs}\n</observations>\n\n${OBSERVATION_CONTEXT_INSTRUCTIONS}`
17082
+ */
17083
+ const OBSERVATION_CONTEXT_PROMPT = `The following observations block contains your memory of past conversations with this user.`;
17084
+ /**
17085
+ * Instructions that tell the model how to interpret and use observations.
17086
+ * Place AFTER the `<observations>` block so the model sees the data before the rules.
17087
+ */
17088
+ 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.
17089
+
17090
+ 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.
17091
+
17092
+ 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.
17093
+
17094
+ 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.
17095
+
17096
+ 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.`;
17097
+ /**
17098
+ * Instructions for retrieval mode — explains observation-group ranges and the recall tool.
17099
+ * Appended to context when `retrieval` is enabled.
17100
+ *
17101
+ * The content adapts to the retrieval scope:
17102
+ * - `'resource'`: covers routing between `search`, `threads`, and `messages` across
17103
+ * all of the user's threads, including fallback when search results are unsuitable.
17104
+ * - `'thread'`: covers cursor-based browsing and search within the current thread.
17105
+ *
17106
+ * @param scope - The retrieval scope the recall tool was registered with.
17107
+ * @param customInstructions - Optional application-provided guidance appended after
17108
+ * the native instructions. Never replaces them.
17109
+ * @param searchEnabled - Whether semantic search (`retrieval: { vector: true }`) is
17110
+ * available. When false, the guidance only covers \`threads\`/\`messages\` browsing so
17111
+ * the agent is not steered toward a mode that cannot work.
17112
+ */
17113
+ function getRetrievalInstructions(scope = "resource", customInstructions, searchEnabled = true) {
17114
+ const isResource = scope === "resource";
17115
+ const base = `## Recall — looking up source messages
17116
+
17117
+ 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.
17118
+
17119
+ ### When to use recall
17120
+ - The user asks you to **repeat, show, or reproduce** something from a past conversation
17121
+ - The user asks for **exact content** — code, text, quotes, error messages, URLs, file paths, specific numbers
17122
+ - 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)
17123
+ - You want to **verify or expand on** an observation before responding${isResource ? `
17124
+ - The user references another conversation that your observations don't cover — even if you have no observations yet, their other threads may contain it` : ""}
17125
+
17126
+ **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.
17127
+
17128
+ ${isResource ? searchEnabled ? `### Choosing a mode
17129
+ The recall tool works across ALL of this user's conversation threads, not just the current one.
17130
+
17131
+ - 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\`.
17132
+ - 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.
17133
+ - 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.
17134
+
17135
+ **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
17136
+ The recall tool works across ALL of this user's conversation threads, not just the current one.
17137
+
17138
+ - 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.
17139
+ - Use \`mode: "messages"\` when you know the thread — pass \`threadId\` to read another thread, or a \`cursor\` from an observation-group range.
17140
+
17141
+ 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
17142
+ The recall tool is limited to the current conversation thread.
17143
+
17144
+ - Use \`mode: "messages"\` (default) to page through this thread's message history near a cursor.${searchEnabled ? `
17145
+ - Use \`mode: "search"\` with a \`query\` to find messages by content within this thread.` : ""}
17146
+ - Use \`mode: "threads"\` to get the current thread's ID, title, and dates.`}
17147
+
17148
+ ### How to use recall with a cursor
17149
+ Each range has the format \`startId:endId\` where both are message IDs separated by a colon.
17150
+
17151
+ 1. Find the observation group relevant to the user's question and extract the start or end ID from its range.
17152
+ 2. Call \`recall\` with that ID as the \`cursor\`.
17153
+ 3. Use \`page: 1\` (or omit) to read forward from the cursor, \`page: -1\` to read backward.
17154
+ 4. If the first page doesn't have what you need, increment the page number to keep paginating.
17155
+ 5. Check \`hasNextPage\`/\`hasPrevPage\` in the result to know if more pages exist in each direction.
17156
+
17157
+ ### Detail levels
17158
+ 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.
17159
+
17160
+ - 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.
17161
+ - 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.
17162
+
17163
+ 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.
17164
+
17165
+ ### Following up on truncated parts
17166
+ Low-detail results may include truncation hints like:
17167
+ \`[truncated — call recall cursor="..." partIndex=N detail="high" for full content]\`
17168
+
17169
+ **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.
17170
+
17171
+ 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.
17172
+
17173
+ ### When recall is NOT needed
17174
+ - The user is asking for a high-level summary and your observations already cover it
17175
+ - The question is about general preferences or facts that don't require source text
17176
+ ${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`}
17177
+
17178
+ Observation groups with range IDs and your recall tool allows you to think back and remember details you're fuzzy on.`;
17179
+ const custom = customInstructions?.trim();
17180
+ if (!custom) return base;
17181
+ return `${base}
17182
+
17183
+ ### Additional recall guidance
17184
+ ${custom}`;
17185
+ }
17186
+ //#endregion
17187
+ //#region src/processors/observational-memory/extracted-values.ts
17188
+ const BUILT_IN_METADATA_FIELDS = {
17189
+ "current-task": {
17190
+ metadataField: "currentTask",
17191
+ builtInField: "currentTask"
17192
+ },
17193
+ "suggested-response": {
17194
+ metadataField: "suggestedResponse",
17195
+ builtInField: "suggestedContinuation"
17196
+ },
17197
+ "thread-title": {
17198
+ metadataField: "threadTitle",
17199
+ builtInField: "threadTitle"
17200
+ }
17201
+ };
17202
+ function isPresentExtractedValue(value) {
17203
+ return value !== void 0 && value !== null && value !== "";
17204
+ }
17205
+ function normalizeExtractedValues(values) {
17206
+ if (!values) return;
17207
+ const normalized = Object.fromEntries(Object.entries(values).filter(([, value]) => isPresentExtractedValue(value)));
17208
+ return Object.keys(normalized).length > 0 ? normalized : void 0;
17209
+ }
17210
+ function mergeExtractedValues(...valueSets) {
17211
+ const merged = {};
17212
+ for (const values of valueSets) {
17213
+ const normalized = normalizeExtractedValues(values);
17214
+ if (normalized) Object.assign(merged, normalized);
17215
+ }
17216
+ return Object.keys(merged).length > 0 ? merged : void 0;
17217
+ }
17218
+ function mergeExtractionFailures(...failureSets) {
17219
+ const failures = failureSets.flatMap((set) => set ?? []);
17220
+ return failures.length > 0 ? failures : void 0;
17221
+ }
17222
+ const UNSAFE_METADATA_PATH_SEGMENTS = /* @__PURE__ */ new Set([
17223
+ "__proto__",
17224
+ "constructor",
17225
+ "prototype"
17226
+ ]);
17227
+ function getMetadataPathSegments(keyPath) {
17228
+ if (keyPath === false) return;
17229
+ const segments = keyPath.split(".").filter(Boolean);
17230
+ if (segments.some((segment) => UNSAFE_METADATA_PATH_SEGMENTS.has(segment))) throw new Error(`Extractor metadataKeyPath "${keyPath}" contains an unsafe path segment.`);
17231
+ return segments.length > 0 ? segments : void 0;
17232
+ }
17233
+ function getValueAtPath(metadata, keyPath) {
17234
+ if (!metadata) return;
17235
+ const segments = getMetadataPathSegments(keyPath);
17236
+ if (!segments) return;
17237
+ let current = metadata;
17238
+ for (const segment of segments) {
17239
+ if (!current || typeof current !== "object" || Array.isArray(current)) return;
17240
+ current = current[segment];
17241
+ }
17242
+ return current;
17243
+ }
17244
+ function setValueAtPath(metadata, keyPath, value) {
17245
+ if (!isPresentExtractedValue(value)) return;
17246
+ const segments = getMetadataPathSegments(keyPath);
17247
+ if (!segments) return;
17248
+ let current = metadata;
17249
+ for (const segment of segments.slice(0, -1)) {
17250
+ const next = current[segment];
17251
+ if (!next || typeof next !== "object" || Array.isArray(next)) current[segment] = {};
17252
+ current = current[segment];
17253
+ }
17254
+ current[segments[segments.length - 1]] = value;
17255
+ }
17256
+ function readBuiltInMetadataValues(metadata) {
17257
+ const values = {};
17258
+ for (const slug of BUILT_IN_EXTRACTOR_SLUGS) {
17259
+ const { metadataField } = BUILT_IN_METADATA_FIELDS[slug];
17260
+ values[slug] = metadata[metadataField];
17261
+ }
17262
+ return values;
17263
+ }
17264
+ function getPriorExtractedValues(metadata, extractors) {
15081
17265
  if (!metadata) return;
15082
17266
  if (!extractors) return mergeExtractedValues(readBuiltInMetadataValues(metadata), metadata.extracted);
15083
17267
  const values = {};
@@ -15122,9 +17306,11 @@ async function applyExtractorHooks(opts) {
15122
17306
  const values = normalizeExtractedValues(opts.values) ?? {};
15123
17307
  const failures = [...opts.failures ?? []];
15124
17308
  for (const extractor of opts.extractors) {
15125
- if (!Object.prototype.hasOwnProperty.call(values, extractor.slug)) continue;
15126
- const current = values[extractor.slug];
15127
- if (!isPresentExtractedValue(current)) {
17309
+ const isHook = extractor.mode === "hook";
17310
+ const hasValue = Object.prototype.hasOwnProperty.call(values, extractor.slug);
17311
+ if (!isHook && !hasValue) continue;
17312
+ const current = isHook ? opts.rawObservations : values[extractor.slug];
17313
+ if (!isHook && !isPresentExtractedValue(current)) {
15128
17314
  delete values[extractor.slug];
15129
17315
  continue;
15130
17316
  }
@@ -15135,14 +17321,19 @@ async function applyExtractorHooks(opts) {
15135
17321
  extractor,
15136
17322
  threadId: opts.threadId,
15137
17323
  resourceId: opts.resourceId,
15138
- previous: opts.previousValues?.[extractor.slug],
17324
+ previous: isHook ? void 0 : opts.previousValues?.[extractor.slug],
15139
17325
  current,
17326
+ rawObservations: opts.rawObservations,
17327
+ recentMessages: opts.recentMessages,
15140
17328
  mainAgent: opts.mainAgent,
15141
17329
  memory: opts.memory,
15142
17330
  sendSignal: opts.sendSignal,
17331
+ sendStateSignal: opts.sendStateSignal,
17332
+ writer: opts.writer,
17333
+ abortSignal: opts.abortSignal,
15143
17334
  requestContext: opts.requestContext
15144
17335
  });
15145
- if (hookValue === void 0) continue;
17336
+ if (isHook || hookValue === void 0) continue;
15146
17337
  const parsed = extractor.schema.safeParse(hookValue);
15147
17338
  if (parsed.success) values[extractor.slug] = parsed.data;
15148
17339
  else {
@@ -15153,7 +17344,8 @@ async function applyExtractorHooks(opts) {
15153
17344
  });
15154
17345
  }
15155
17346
  } catch (error) {
15156
- delete values[extractor.slug];
17347
+ if (opts.abortSignal?.aborted) throw error;
17348
+ if (!isHook) delete values[extractor.slug];
15157
17349
  failures.push({
15158
17350
  slug: extractor.slug,
15159
17351
  error: error instanceof Error ? error.message : String(error)
@@ -16218,11 +18410,11 @@ function formatObserverAttachmentPlaceholder(part, counter) {
16218
18410
  const label = resolveObserverAttachmentLabel(part);
16219
18411
  return label ? `[${attachmentType} #${attachmentId}: ${label}]` : `[${attachmentType} #${attachmentId}]`;
16220
18412
  }
16221
- function isRecord$2(value) {
18413
+ function isRecord$3(value) {
16222
18414
  return !!value && typeof value === "object";
16223
18415
  }
16224
18416
  function mapToolResultBlockToAttachment(block) {
16225
- if (!isRecord$2(block) || typeof block.type !== "string") return;
18417
+ if (!isRecord$3(block) || typeof block.type !== "string") return;
16226
18418
  const mediaType = typeof block.mediaType === "string" ? block.mediaType : void 0;
16227
18419
  const filename = typeof block.filename === "string" ? block.filename : void 0;
16228
18420
  switch (block.type) {
@@ -16283,7 +18475,7 @@ function mapToolResultBlockToAttachment(block) {
16283
18475
  }
16284
18476
  }
16285
18477
  function extractToolResultAttachments(result, counter, attachmentFilter) {
16286
- if (!isRecord$2(result) || result.type !== "content" || !Array.isArray(result.value)) return {
18478
+ if (!isRecord$3(result) || result.type !== "content" || !Array.isArray(result.value)) return {
16287
18479
  resultWithoutAttachments: result,
16288
18480
  attachments: []
16289
18481
  };
@@ -16297,7 +18489,7 @@ function extractToolResultAttachments(result, counter, attachmentFilter) {
16297
18489
  if (shouldIncludeObserverAttachment(attachment, attachmentFilter)) attachments.push(toObserverInputAttachmentPart(attachment));
16298
18490
  const placeholder = formatObserverAttachmentPlaceholder(attachment, counter);
16299
18491
  return {
16300
- type: isRecord$2(block) ? block.type : void 0,
18492
+ type: isRecord$3(block) ? block.type : void 0,
16301
18493
  placeholder
16302
18494
  };
16303
18495
  });
@@ -16840,17 +19032,17 @@ const TRANSIENT_MESSAGE_SUBSTRINGS = [
16840
19032
  "connection reset",
16841
19033
  "connection closed"
16842
19034
  ];
16843
- function isRecord$1(value) {
19035
+ function isRecord$2(value) {
16844
19036
  return typeof value === "object" && value !== null;
16845
19037
  }
16846
19038
  function isAbortError(error) {
16847
- if (!isRecord$1(error)) return false;
19039
+ if (!isRecord$2(error)) return false;
16848
19040
  if (error.name === "AbortError") return true;
16849
19041
  if (typeof error.code === "string" && error.code === "ABORT_ERR") return true;
16850
19042
  return false;
16851
19043
  }
16852
19044
  function hasTransientMessage(value) {
16853
- if (!isRecord$1(value)) return false;
19045
+ if (!isRecord$2(value)) return false;
16854
19046
  const message = typeof value.message === "string" ? value.message.toLowerCase() : "";
16855
19047
  if (message && TRANSIENT_MESSAGE_SUBSTRINGS.some((sub) => message.includes(sub))) return true;
16856
19048
  if (typeof value.code === "string" && value.code.toUpperCase().startsWith("UND_ERR_")) return true;
@@ -16861,13 +19053,13 @@ function isRetryableHttpStatus(status) {
16861
19053
  return status >= 500 && status <= 599;
16862
19054
  }
16863
19055
  function hasRetryableHttpStatus(value) {
16864
- if (!isRecord$1(value)) return false;
19056
+ if (!isRecord$2(value)) return false;
16865
19057
  if (typeof value.statusCode === "number" && isRetryableHttpStatus(value.statusCode)) return true;
16866
19058
  if (typeof value.code === "number" && isRetryableHttpStatus(value.code)) return true;
16867
19059
  return false;
16868
19060
  }
16869
19061
  function hasIsRetryableFlag(value) {
16870
- if (!isRecord$1(value)) return false;
19062
+ if (!isRecord$2(value)) return false;
16871
19063
  return value.isRetryable === true;
16872
19064
  }
16873
19065
  /**
@@ -16885,14 +19077,14 @@ function isTransientLLMError(error) {
16885
19077
  if (isAbortError(error)) return false;
16886
19078
  const visited = /* @__PURE__ */ new WeakSet();
16887
19079
  function visit(candidate) {
16888
- if (isRecord$1(candidate)) {
19080
+ if (isRecord$2(candidate)) {
16889
19081
  if (visited.has(candidate)) return false;
16890
19082
  visited.add(candidate);
16891
19083
  }
16892
19084
  if (hasTransientMessage(candidate)) return true;
16893
19085
  if (hasRetryableHttpStatus(candidate)) return true;
16894
19086
  if (hasIsRetryableFlag(candidate)) return true;
16895
- if (isRecord$1(candidate)) {
19087
+ if (isRecord$2(candidate)) {
16896
19088
  if (visit(candidate.cause)) return true;
16897
19089
  if (visit(candidate.error)) return true;
16898
19090
  }
@@ -20131,40 +22323,6 @@ function createWorkingMemoryTool(config, options = {}) {
20131
22323
  };
20132
22324
  }
20133
22325
  //#endregion
20134
- //#region src/processors/observational-memory/model-by-input-tokens.ts
20135
- function isTieredModelTarget(model) {
20136
- if (typeof model === "string") return true;
20137
- if (!model || typeof model !== "object") return false;
20138
- return "modelId" in model || "id" in model || "providerId" in model || "provider" in model || "doGenerate" in model && "doStream" in model;
20139
- }
20140
- function normalizeThresholds(config) {
20141
- const entries = Object.entries(config.upTo);
20142
- if (entries.length === 0) throw new Error("ModelByInputTokens requires at least one threshold in \"upTo\"");
20143
- for (const [limitStr, model] of entries) {
20144
- const limit = Number(limitStr);
20145
- if (!Number.isFinite(limit) || limit <= 0) throw new Error(`ModelByInputTokens threshold keys must be positive numbers. Got: ${limitStr}`);
20146
- if (!isTieredModelTarget(model)) throw new Error(`ModelByInputTokens requires a valid model target for threshold ${limitStr}`);
20147
- }
20148
- return entries.map(([limitStr, model]) => ({
20149
- limit: Number(limitStr),
20150
- model
20151
- })).sort((a, b) => a.limit - b.limit);
20152
- }
20153
- var ModelByInputTokens = class {
20154
- thresholds;
20155
- constructor(config) {
20156
- this.thresholds = normalizeThresholds(config);
20157
- }
20158
- resolve(inputTokens) {
20159
- for (const { limit, model } of this.thresholds) if (inputTokens <= limit) return model;
20160
- const maxLimit = this.thresholds[this.thresholds.length - 1].limit;
20161
- throw new Error(`ModelByInputTokens: input token count (${inputTokens}) exceeds the largest configured threshold (${maxLimit}). Please configure a higher threshold or use a larger model.`);
20162
- }
20163
- getThresholds() {
20164
- return this.thresholds.map((t) => t.limit);
20165
- }
20166
- };
20167
- //#endregion
20168
22326
  //#region src/processors/observational-memory/activation-ttl.ts
20169
22327
  const MINUTE = 6e4;
20170
22328
  const HOUR = 60 * MINUTE;
@@ -20972,6 +23130,39 @@ function reconcileObservationGroupsFromReflection(content, sourceObservations) {
20972
23130
  if (derivedGroups.length > 0) return derivedGroups.map((group) => wrapInObservationGroup(group.content, group.range, group.id, void 0, group.kind)).join("\n\n");
20973
23131
  return wrapInObservationGroup(normalizedContent, combineObservationGroupRanges(sourceGroups), generateAnchorId(), void 0, "reflection");
20974
23132
  }
23133
+ function isRecord$1(value) {
23134
+ return !!value && typeof value === "object" && !Array.isArray(value);
23135
+ }
23136
+ function isSubconsciousSignalMessage(message) {
23137
+ if (message.role !== "signal" || !isRecord$1(message.content)) return false;
23138
+ const contentMetadata = message.content.metadata;
23139
+ if (!isRecord$1(contentMetadata) || !isRecord$1(contentMetadata.signal)) return false;
23140
+ const signal = contentMetadata.signal;
23141
+ const metadata = signal.metadata;
23142
+ const attributes = signal.attributes;
23143
+ return signal.origin === "subconscious" || isRecord$1(metadata) && metadata.origin === "subconscious" || isRecord$1(attributes) && attributes.source === "subconscious";
23144
+ }
23145
+ function isSubconsciousSignalPart(part) {
23146
+ if (!part || typeof part !== "object") return false;
23147
+ const candidate = part;
23148
+ return candidate.type === "data-signal" && (candidate.data?.metadata?.origin === "subconscious" || candidate.data?.attributes?.source === "subconscious");
23149
+ }
23150
+ function stripSubconsciousSignals(messages) {
23151
+ return messages.flatMap((message) => {
23152
+ if (isSubconsciousSignalMessage(message)) return [];
23153
+ if (typeof message.content === "string") return [message];
23154
+ const parts = message.content.parts.filter((part) => !isSubconsciousSignalPart(part));
23155
+ if (parts.length === message.content.parts.length) return [message];
23156
+ if (parts.length === 0) return [];
23157
+ return [{
23158
+ ...message,
23159
+ content: {
23160
+ ...message.content,
23161
+ parts
23162
+ }
23163
+ }];
23164
+ });
23165
+ }
20975
23166
  //#endregion
20976
23167
  //#region src/processors/observational-memory/thresholds.ts
20977
23168
  /**
@@ -21129,8 +23320,9 @@ var ObservationStrategy = class ObservationStrategy {
21129
23320
  if (fresh?.lastObservedAt && record.lastObservedAt && fresh.lastObservedAt > record.lastObservedAt) return { observed: false };
21130
23321
  }
21131
23322
  const { messages, existingObservations } = await this.prepare();
23323
+ const observationMessages = stripSubconsciousSignals(messages);
21132
23324
  await this.emitStartMarkers(cycleId);
21133
- const output = await this.observe(existingObservations, messages);
23325
+ const output = await this.observe(existingObservations, observationMessages);
21134
23326
  const processed = await this.process(output, existingObservations);
21135
23327
  await this.persist(processed);
21136
23328
  await this.emitEndMarkers(cycleId, processed);
@@ -21145,6 +23337,7 @@ var ObservationStrategy = class ObservationStrategy {
21145
23337
  abortSignal,
21146
23338
  mainAgent: this.opts.agent,
21147
23339
  sendSignal: this.opts.sendSignal,
23340
+ sendStateSignal: this.opts.sendStateSignal,
21148
23341
  reflectionHooks,
21149
23342
  requestContext,
21150
23343
  observabilityContext: this.opts.observabilityContext
@@ -21432,11 +23625,16 @@ var SyncObservationStrategy = class extends ObservationStrategy {
21432
23625
  values: result.extractedValues,
21433
23626
  failures: result.extractionFailures,
21434
23627
  previousValues: this.priorExtractedValues,
23628
+ rawObservations: result.observations,
23629
+ recentMessages: formatMessagesForObserver(this.opts.messages, { maxPartLength: 500 }),
21435
23630
  threadId: this.opts.threadId,
21436
23631
  resourceId: this.opts.resourceId,
21437
23632
  mainAgent: this.opts.agent,
21438
23633
  memory: this.deps.memory,
21439
23634
  sendSignal: this.opts.sendSignal,
23635
+ sendStateSignal: this.opts.sendStateSignal,
23636
+ writer: this.opts.writer,
23637
+ abortSignal: this.opts.abortSignal,
21440
23638
  requestContext: this.opts.requestContext
21441
23639
  });
21442
23640
  const output = {
@@ -21612,11 +23810,16 @@ var AsyncBufferObservationStrategy = class extends ObservationStrategy {
21612
23810
  values: result.extractedValues,
21613
23811
  failures: result.extractionFailures,
21614
23812
  previousValues: this.priorExtractedValues,
23813
+ rawObservations: result.observations,
23814
+ recentMessages: formatMessagesForObserver(messages, { maxPartLength: 500 }),
21615
23815
  threadId: this.opts.threadId,
21616
23816
  resourceId: this.opts.resourceId,
21617
23817
  mainAgent: this.opts.agent,
21618
23818
  memory: this.deps.memory,
21619
23819
  sendSignal: this.opts.sendSignal,
23820
+ sendStateSignal: this.opts.sendStateSignal,
23821
+ writer: this.opts.writer,
23822
+ abortSignal: this.opts.abortSignal,
21620
23823
  requestContext: this.opts.requestContext
21621
23824
  });
21622
23825
  return {
@@ -21951,11 +24154,25 @@ var ResourceScopedObservationStrategy = class extends ObservationStrategy {
21951
24154
  values: result.extractedValues,
21952
24155
  failures: result.extractionFailures,
21953
24156
  previousValues,
24157
+ rawObservations: result.observations,
24158
+ recentMessages: formatMessagesForObserver(threadMessages, { maxPartLength: 500 }),
21954
24159
  threadId,
21955
24160
  resourceId: this.resourceId,
21956
24161
  mainAgent: this.opts.agent,
21957
24162
  memory: this.deps.memory,
21958
- sendSignal: this.opts.sendSignal,
24163
+ sendSignal: this.opts.agent ? async (signal) => {
24164
+ const delivery = this.opts.agent.sendSignal(signal, {
24165
+ resourceId: this.resourceId,
24166
+ threadId,
24167
+ ifActive: { behavior: "deliver" },
24168
+ ifIdle: { behavior: "persist" }
24169
+ });
24170
+ await delivery.accepted;
24171
+ return delivery.signal;
24172
+ } : void 0,
24173
+ sendStateSignal: this.opts.sendStateSignal,
24174
+ writer: this.opts.writer,
24175
+ abortSignal: this.opts.abortSignal,
21959
24176
  requestContext: this.opts.requestContext
21960
24177
  });
21961
24178
  this.observationResults.push({
@@ -22274,18 +24491,21 @@ var ObservationStep = class {
22274
24491
  messageList.removeByIds(candidates.map((msg) => msg.id));
22275
24492
  for (const msg of candidates) messageList.add(msg, "memory");
22276
24493
  }
22277
- om.buffer({
24494
+ om.trackBackgroundWork(om.buffer({
22278
24495
  threadId,
22279
24496
  resourceId,
22280
24497
  messages: unobservedMessages,
22281
24498
  pendingTokens: statusSnapshot.pendingTokens,
22282
24499
  record: statusSnapshot.record,
22283
24500
  writer: this.turn.writer,
24501
+ agent: this.turn.agent,
24502
+ sendSignal: this.turn.sendSignal,
24503
+ sendStateSignal: this.turn.sendStateSignal,
22284
24504
  requestContext: this.turn.requestContext,
22285
24505
  observabilityContext: this.turn.observabilityContext
22286
24506
  }).catch((err) => {
22287
24507
  omDebug(`[OM:buffer] fire-and-forget buffer failed: ${err?.message}`);
22288
- });
24508
+ }));
22289
24509
  buffered = true;
22290
24510
  }
22291
24511
  const willObserveNow = statusSnapshot.shouldObserve && !hasIncompleteToolCalls;
@@ -22457,6 +24677,9 @@ var ObservationStep = class {
22457
24677
  messages: observableMessages,
22458
24678
  messageList,
22459
24679
  trigger: "turn-sync",
24680
+ agent: this.turn.agent,
24681
+ sendSignal: this.turn.sendSignal,
24682
+ sendStateSignal: this.turn.sendStateSignal,
22460
24683
  requestContext: this.turn.requestContext,
22461
24684
  writer: this.turn.writer,
22462
24685
  observabilityContext: this.turn.observabilityContext
@@ -22536,6 +24759,8 @@ var ObservationTurn = class {
22536
24759
  agent;
22537
24760
  /** Optional signal sender for processor-originated notifications. */
22538
24761
  sendSignal;
24762
+ /** Optional state signal sender for processor-originated snapshots. */
24763
+ sendStateSignal;
22539
24764
  /** Current actor model for this step. Updated by the processor before prepare(). */
22540
24765
  actorModelContext;
22541
24766
  /** The active assistant response message ID for this step. Updated by the processor before prepare(). */
@@ -22549,6 +24774,7 @@ var ObservationTurn = class {
22549
24774
  this.messageList = opts.messageList;
22550
24775
  this.agent = opts.agent;
22551
24776
  this.sendSignal = opts.sendSignal;
24777
+ this.sendStateSignal = opts.sendStateSignal;
22552
24778
  this.requestContext = opts.requestContext;
22553
24779
  this.observabilityContext = opts.observabilityContext;
22554
24780
  this.hooks = opts.hooks ?? {};
@@ -22653,7 +24879,7 @@ var ObservationTurn = class {
22653
24879
  const allMessages = getObservableMessages(this.messageList);
22654
24880
  const record = this._record;
22655
24881
  const unobservedMessages = this.om.getUnobservedMessages(allMessages, record);
22656
- if (unobservedMessages.length > 0) this.om.buffer({
24882
+ if (unobservedMessages.length > 0) this.om.trackBackgroundWork(this.om.buffer({
22657
24883
  threadId: this.threadId,
22658
24884
  resourceId: this.resourceId,
22659
24885
  messages: unobservedMessages,
@@ -22661,13 +24887,14 @@ var ObservationTurn = class {
22661
24887
  writer: this.writer,
22662
24888
  agent: this.agent,
22663
24889
  sendSignal: this.sendSignal,
24890
+ sendStateSignal: this.sendStateSignal,
22664
24891
  requestContext: this.requestContext,
22665
24892
  currentModel: this.actorModelContext,
22666
24893
  observabilityContext: this.observabilityContext,
22667
24894
  skipMinimumTokenCheck: true
22668
24895
  }).catch((err) => {
22669
24896
  omDebug(`[OM:turn.end] idle buffer failed: ${err?.message}`);
22670
- });
24897
+ }));
22671
24898
  }
22672
24899
  return { record: this._record };
22673
24900
  }
@@ -23069,6 +25296,7 @@ var ReflectorRunner = class {
23069
25296
  persistMarkerToMessage;
23070
25297
  getCompressionStartLevel;
23071
25298
  memory;
25299
+ onReflectionCommitted;
23072
25300
  mastra;
23073
25301
  constructor(opts) {
23074
25302
  this.reflectionConfig = opts.reflectionConfig;
@@ -23084,6 +25312,7 @@ var ReflectorRunner = class {
23084
25312
  this.getCompressionStartLevel = opts.getCompressionStartLevel;
23085
25313
  this.mastra = opts.mastra;
23086
25314
  this.memory = opts.memory;
25315
+ this.onReflectionCommitted = opts.onReflectionCommitted;
23087
25316
  }
23088
25317
  __registerMastra(mastra) {
23089
25318
  this.mastra = mastra;
@@ -23151,6 +25380,8 @@ var ReflectorRunner = class {
23151
25380
  };
23152
25381
  let reflectedTokens = 0;
23153
25382
  let attemptNumber = 0;
25383
+ /** Observations from the previous attempt, used to detect a no-progress ladder. */
25384
+ let previousObservations;
23154
25385
  while (currentLevel <= maxLevel) {
23155
25386
  attemptNumber++;
23156
25387
  const isRetry = attemptNumber > 1;
@@ -23224,6 +25455,11 @@ var ReflectorRunner = class {
23224
25455
  omDebug(`[OM:callReflector] degenerate output persists at maxLevel=${maxLevel}, breaking`);
23225
25456
  break;
23226
25457
  }
25458
+ if (!parsed.degenerate && previousObservations !== void 0 && parsed.observations === previousObservations) {
25459
+ omDebug(`[OM:callReflector] attempt #${attemptNumber} returned output identical to the previous attempt; escalating cannot help, stopping the ladder`);
25460
+ break;
25461
+ }
25462
+ previousObservations = parsed.observations;
23227
25463
  if (streamContext?.writer) {
23228
25464
  const failedMarker = createObservationFailedMarker({
23229
25465
  cycleId: streamContext.cycleId,
@@ -23280,6 +25516,8 @@ var ReflectorRunner = class {
23280
25516
  mainAgent,
23281
25517
  memory: this.memory,
23282
25518
  sendSignal,
25519
+ writer: streamContext?.writer,
25520
+ abortSignal,
23283
25521
  requestContext
23284
25522
  });
23285
25523
  const extractedValues = hookedValues.values;
@@ -23435,7 +25673,7 @@ var ReflectorRunner = class {
23435
25673
  * "activated", "no buffer present", and "suppressed by overshoot guard"
23436
25674
  * without re-deriving that state.
23437
25675
  */
23438
- async tryActivateBufferedReflection(record, lockKey, writer, messageList, activationMetadata) {
25676
+ async tryActivateBufferedReflection(record, lockKey, writer, messageList, activationMetadata, committedContext) {
23439
25677
  const bufferKey = this.buffering.getReflectionBufferKey(lockKey);
23440
25678
  const asyncOp = BufferingCoordinator.asyncBufferingOps.get(bufferKey);
23441
25679
  if (asyncOp) if (activationMetadata?.triggeredBy === "ttl" || activationMetadata?.triggeredBy === "provider_change") omDebug(`[OM:reflect] tryActivateBufferedReflection: async op in progress, not blocking for ${activationMetadata.triggeredBy} trigger`);
@@ -23454,7 +25692,8 @@ var ReflectorRunner = class {
23454
25692
  }
23455
25693
  const beforeTokens = freshRecord.observationTokenCount ?? 0;
23456
25694
  const reflectedLineCount = freshRecord.reflectedObservationLineCount ?? 0;
23457
- const unreflectedLines = (freshRecord.activeObservations ?? "").split("\n").slice(reflectedLineCount);
25695
+ const allLines = (freshRecord.activeObservations ?? "").split("\n");
25696
+ const unreflectedLines = allLines.slice(reflectedLineCount);
23458
25697
  const unreflectedContent = unreflectedLines.join("\n").trim();
23459
25698
  const combinedObservations = unreflectedContent ? `${freshRecord.bufferedReflection}\n\n${unreflectedContent}` : freshRecord.bufferedReflection;
23460
25699
  const combinedTokenCount = this.tokenCounter.countObservations(combinedObservations);
@@ -23485,6 +25724,11 @@ var ReflectorRunner = class {
23485
25724
  currentRecord: freshRecord,
23486
25725
  tokenCount: combinedTokenCount
23487
25726
  });
25727
+ if (committedContext) await this.notifyReflectionCommitted({
25728
+ ...committedContext,
25729
+ observations: allLines.slice(0, reflectedLineCount).join("\n").trim(),
25730
+ writer
25731
+ });
23488
25732
  BufferingCoordinator.lastBufferedBoundary.delete(bufferKey);
23489
25733
  const afterRecord = await this.storage.getObservationalMemory(record.threadId, record.resourceId);
23490
25734
  const afterTokens = afterRecord?.observationTokenCount ?? 0;
@@ -23520,13 +25764,21 @@ var ReflectorRunner = class {
23520
25764
  BufferingCoordinator.reflectionBufferCycleIds.delete(bufferKey);
23521
25765
  return { status: "activated" };
23522
25766
  }
25767
+ async notifyReflectionCommitted(context) {
25768
+ if (!this.onReflectionCommitted || !context.parentThreadId || !context.resourceId) return;
25769
+ try {
25770
+ await this.onReflectionCommitted(context);
25771
+ } catch (error) {
25772
+ omDebug(`[OM:reflect] post-commit reflection agent failed: ${error}`);
25773
+ }
25774
+ }
23523
25775
  /**
23524
25776
  * Check if reflection needed and trigger if so.
23525
25777
  * Supports both synchronous reflection and async buffered reflection.
23526
25778
  * @internal Used by observation strategies. Do not call directly.
23527
25779
  */
23528
25780
  async maybeReflect(opts) {
23529
- const { record, observationTokens, writer, abortSignal, mainAgent, sendSignal, messageList, currentModel, reflectionHooks, requestContext, observabilityContext, lastActivityAt, threadId: requestedThreadId } = opts;
25781
+ const { record, observationTokens, writer, abortSignal, mainAgent, sendSignal, sendStateSignal, messageList, currentModel, reflectionHooks, requestContext, observabilityContext, lastActivityAt, threadId: requestedThreadId } = opts;
23530
25782
  const lockKey = this.buffering.getLockKey(record.threadId, record.resourceId);
23531
25783
  const reflectThreshold = getMaxThreshold(this.getEffectiveReflectionTokens(record));
23532
25784
  const priorExtractedValues = await getThreadExtractedValues(this.storage, this.reflectionConfig.extractors, requestedThreadId ?? record.threadId);
@@ -23571,7 +25823,15 @@ var ReflectorRunner = class {
23571
25823
  await this.storage.setReflectingFlag(record.id, false);
23572
25824
  }
23573
25825
  if (this.buffering.isAsyncReflectionEnabled()) {
23574
- const activationResult = await this.tryActivateBufferedReflection(record, lockKey, writer, messageList, activationMetadata);
25826
+ const activationResult = await this.tryActivateBufferedReflection(record, lockKey, writer, messageList, activationMetadata, {
25827
+ parentThreadId: requestedThreadId ?? record.threadId ?? "",
25828
+ resourceId: record.resourceId ?? "",
25829
+ requestContext,
25830
+ mainAgent,
25831
+ sendStateSignal,
25832
+ abortSignal,
25833
+ observabilityContext
25834
+ });
23575
25835
  if (activationResult.status === "activated") return;
23576
25836
  if (activationResult.status === "suppressed") {
23577
25837
  omDebug(`[OM:reflect] skipping sync fallback / re-buffer after suppressed early ${activationMetadata.triggeredBy} activation (reason=${activationResult.reason})`);
@@ -23642,6 +25902,17 @@ var ReflectorRunner = class {
23642
25902
  reflection: reflectResult.observations,
23643
25903
  tokenCount: reflectionTokenCount
23644
25904
  });
25905
+ await this.notifyReflectionCommitted({
25906
+ parentThreadId: requestedThreadId ?? record.threadId ?? "",
25907
+ resourceId: record.resourceId ?? "",
25908
+ observations: record.activeObservations ?? "",
25909
+ requestContext,
25910
+ mainAgent,
25911
+ sendStateSignal,
25912
+ writer,
25913
+ abortSignal,
25914
+ observabilityContext
25915
+ });
23645
25916
  if (writer && streamContext) {
23646
25917
  const endMarker = createObservationEndMarker({
23647
25918
  cycleId: streamContext.cycleId,
@@ -24171,6 +26442,7 @@ var ObservationalMemory = class ObservationalMemory {
24171
26442
  hasher = xxhash();
24172
26443
  mastra;
24173
26444
  memory;
26445
+ curationCadence;
24174
26446
  /**
24175
26447
  * Track message IDs observed during this instance's lifetime.
24176
26448
  * Prevents re-observing messages when per-thread lastObservedAt cursors
@@ -24202,6 +26474,39 @@ var ObservationalMemory = class ObservationalMemory {
24202
26474
  */
24203
26475
  recordInitializations = /* @__PURE__ */ new Map();
24204
26476
  /**
26477
+ * In-flight fire-and-forget background cycles (buffered observation, reflection).
26478
+ * Tracked so callers can join them via `settled()` before closing a storage
26479
+ * connection they own — otherwise a background cycle's tail statements race the close.
26480
+ */
26481
+ pendingBackgroundWork = /* @__PURE__ */ new Set();
26482
+ /**
26483
+ * Register fire-and-forget background work so `settled()` can join it.
26484
+ * Returns the original promise so callers keep their own rejection handling.
26485
+ */
26486
+ trackBackgroundWork(work) {
26487
+ const tracked = work.catch(() => {}).finally(() => {
26488
+ this.pendingBackgroundWork.delete(tracked);
26489
+ });
26490
+ this.pendingBackgroundWork.add(tracked);
26491
+ return work;
26492
+ }
26493
+ /**
26494
+ * Resolve once all background observational-memory work started so far has finished.
26495
+ *
26496
+ * Background cycles enqueue further background work (a buffered observation can
26497
+ * trigger a reflection, which runs nested agent streams), so this drains repeatedly
26498
+ * until nothing is left rather than joining a single snapshot.
26499
+ */
26500
+ async settled() {
26501
+ const maxDrainRounds = 100;
26502
+ for (let round = 0; round < maxDrainRounds; round++) {
26503
+ if (this.pendingBackgroundWork.size === 0) return;
26504
+ await Promise.allSettled([...this.pendingBackgroundWork]);
26505
+ }
26506
+ if (this.pendingBackgroundWork.size === 0) return;
26507
+ omError(`[OM:settled] background work still pending after ${maxDrainRounds} drain rounds; giving up waiting`);
26508
+ }
26509
+ /**
24205
26510
  * Acquire a lock for the given key, execute the callback, then release.
24206
26511
  * If a lock is already held, waits for it to be released before acquiring.
24207
26512
  */
@@ -24235,6 +26540,7 @@ var ObservationalMemory = class ObservationalMemory {
24235
26540
  this.hooks = config.hooks;
24236
26541
  this.mastra = config.mastra;
24237
26542
  this.memory = config.memory;
26543
+ this.curationCadence = config.curationCadence;
24238
26544
  const resolveModel = (model, defaultModel) => model === "default" ? defaultModel : model;
24239
26545
  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;
24240
26546
  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;
@@ -24330,7 +26636,8 @@ var ObservationalMemory = class ObservationalMemory {
24330
26636
  getCompressionStartLevel: (rc) => this.getCompressionStartLevel(rc),
24331
26637
  resolveModel: (inputTokens) => this.resolveReflectionModel(inputTokens),
24332
26638
  mastra: config.mastra,
24333
- memory: this.memory
26639
+ memory: this.memory,
26640
+ onReflectionCommitted: config.onReflectionCommitted
24334
26641
  });
24335
26642
  this.validateBufferConfig();
24336
26643
  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}`);
@@ -25202,7 +27509,7 @@ ${formattedMessages}
25202
27509
  if (!this.buffering.isAsyncObservationEnabled()) return false;
25203
27510
  const lockKey = this.buffering.getLockKey(opts.threadId, opts.resourceId);
25204
27511
  const shouldTrigger = this.buffering.shouldTriggerAsyncObservation(opts.pendingTokens, lockKey, opts.record, this.storage, opts.threshold);
25205
- if (shouldTrigger) this.startAsyncBufferedObservation(opts.record, opts.threadId, opts.unobservedMessages, lockKey, opts.writer, opts.unbufferedPendingTokens, opts.requestContext);
27512
+ if (shouldTrigger) this.trackBackgroundWork(this.startAsyncBufferedObservation(opts.record, opts.threadId, opts.unobservedMessages, lockKey, opts.writer, opts.unbufferedPendingTokens, opts.requestContext));
25206
27513
  return shouldTrigger;
25207
27514
  }
25208
27515
  isMessageList(value) {
@@ -25804,6 +28111,7 @@ ${formattedMessages}
25804
28111
  writer,
25805
28112
  agent: opts.agent,
25806
28113
  sendSignal: opts.sendSignal,
28114
+ sendStateSignal: opts.sendStateSignal,
25807
28115
  requestContext,
25808
28116
  currentModel: opts.currentModel,
25809
28117
  observabilityContext
@@ -26148,6 +28456,8 @@ ${formattedMessages}
26148
28456
  messageList: opts.messageList,
26149
28457
  reflectionHooks,
26150
28458
  agent: opts.agent,
28459
+ sendSignal: opts.sendSignal,
28460
+ sendStateSignal: opts.sendStateSignal,
26151
28461
  requestContext,
26152
28462
  writer: opts.writer,
26153
28463
  observabilityContext: opts.observabilityContext
@@ -26168,6 +28478,9 @@ ${formattedMessages}
26168
28478
  });
26169
28479
  const record = await this.getOrCreateRecord(threadId, resourceId);
26170
28480
  const reflected = record.generationCount > generationBefore && generationBefore >= 0;
28481
+ if (observed) this.maybeTriggerCadenceCuration(threadId, resourceId, record, requestContext).catch((error) => {
28482
+ omDebug(`[OM:observe] cadence curation failed: ${error instanceof Error ? error.message : String(error)}`);
28483
+ });
26171
28484
  return {
26172
28485
  observed,
26173
28486
  reflected,
@@ -26175,6 +28488,31 @@ ${formattedMessages}
26175
28488
  };
26176
28489
  }
26177
28490
  /**
28491
+ * Count committed observation runs on the sync observe path and run the curator every
28492
+ * `curationCadence` runs. The counter is persisted at `record.config.subconscious.observationRuns`
28493
+ * (the OM record's config jsonb, deep-merged; threshold resolution only reads `_overrides`, so the
28494
+ * sibling key is inert). Concurrent commits can miscount — the curator run is advisory, and the
28495
+ * curation cursor is the real serializer. The async-buffer lane bypasses observe(); deployments
28496
+ * that gate on this cadence (factory's resource scope) disable async buffering.
28497
+ */
28498
+ async maybeTriggerCadenceCuration(threadId, resourceId, record, requestContext) {
28499
+ const cadence = this.curationCadence;
28500
+ const memory = this.memory;
28501
+ if (!cadence || cadence < 1 || !memory) return;
28502
+ const runs = ((record.config ?? {}).subconscious?.observationRuns ?? 0) + 1;
28503
+ const fire = runs >= cadence;
28504
+ await this.storage.updateObservationalMemoryConfig({
28505
+ id: record.id,
28506
+ config: { subconscious: { observationRuns: fire ? 0 : runs } }
28507
+ });
28508
+ if (!fire) return;
28509
+ omDebug(`[OM:observe] cadence curation outcome=${(await memory.runCuration({
28510
+ threadId,
28511
+ resourceId: resourceId ?? threadId,
28512
+ requestContext
28513
+ })).outcome} thread=${threadId}`);
28514
+ }
28515
+ /**
26178
28516
  * Manually trigger reflection with optional guidance prompt.
26179
28517
  *
26180
28518
  * @example
@@ -26192,6 +28530,18 @@ ${formattedMessages}
26192
28530
  record,
26193
28531
  usage: void 0
26194
28532
  };
28533
+ if (record.isReflecting) {
28534
+ if (isOpActiveInProcess(record.id, "reflecting")) {
28535
+ omDebug(`[OM:reflect] isReflecting=true and active in this process, skipping`);
28536
+ return {
28537
+ reflected: false,
28538
+ record,
28539
+ usage: void 0
28540
+ };
28541
+ }
28542
+ omDebug(`[OM:reflect] isReflecting=true but NOT active in this process — stale flag from dead process, clearing`);
28543
+ await this.storage.setReflectingFlag(record.id, false);
28544
+ }
26195
28545
  await this.storage.setReflectingFlag(record.id, true);
26196
28546
  registerOp(record.id, "reflecting");
26197
28547
  const hooks = this.composeHooks(void 0, {
@@ -26377,6 +28727,8 @@ ${formattedMessages}
26377
28727
  resourceId: opts.resourceId,
26378
28728
  messageList: opts.messageList,
26379
28729
  agent: opts.agent,
28730
+ sendSignal: opts.sendSignal,
28731
+ sendStateSignal: opts.sendStateSignal,
26380
28732
  observabilityContext: opts.observabilityContext,
26381
28733
  hooks: opts.hooks
26382
28734
  });
@@ -26603,6 +28955,7 @@ var ObservationalMemoryProcessor = class {
26603
28955
  });
26604
28956
  this.turn.writer = writer;
26605
28957
  this.turn.sendSignal = args.sendSignal;
28958
+ this.turn.sendStateSignal = args.sendStateSignal;
26606
28959
  this.turn.agent = args.agent;
26607
28960
  this.turn.requestContext = requestContext;
26608
28961
  await this.turn.start(this.memory);
@@ -26747,6 +29100,7 @@ function getObservationsAsOf(activeObservations, asOf) {
26747
29100
  //#region src/processors/observational-memory/index.ts
26748
29101
  var observational_memory_exports = /* @__PURE__ */ __exportAll({
26749
29102
  Extractor: () => Extractor,
29103
+ KnowledgeSemanticIndexCoordinator: () => KnowledgeSemanticIndexCoordinator,
26750
29104
  ModelByInputTokens: () => ModelByInputTokens,
26751
29105
  OBSERVATIONAL_MEMORY_DEFAULTS: () => OBSERVATIONAL_MEMORY_DEFAULTS,
26752
29106
  OBSERVATION_CONTEXT_INSTRUCTIONS: () => OBSERVATION_CONTEXT_INSTRUCTIONS,
@@ -26755,10 +29109,16 @@ var observational_memory_exports = /* @__PURE__ */ __exportAll({
26755
29109
  OBSERVER_SYSTEM_PROMPT: () => OBSERVER_SYSTEM_PROMPT,
26756
29110
  ObservationalMemory: () => ObservationalMemory,
26757
29111
  ObservationalMemoryProcessor: () => ObservationalMemoryProcessor,
29112
+ SUBCONSCIOUS_ACTIVITY_STATE_ID: () => SUBCONSCIOUS_ACTIVITY_STATE_ID,
29113
+ StaleKnowledgeSemanticIndexError: () => StaleKnowledgeSemanticIndexError,
29114
+ Subconscious: () => Subconscious,
29115
+ SubconsciousCaptureExtractor: () => SubconsciousCaptureExtractor,
29116
+ SubconsciousRemindExtractor: () => SubconsciousRemindExtractor,
26758
29117
  TokenCounter: () => TokenCounter,
26759
29118
  WorkingMemoryExtractor: () => WorkingMemoryExtractor,
26760
29119
  buildObserverPrompt: () => buildObserverPrompt,
26761
29120
  buildObserverSystemPrompt: () => buildObserverSystemPrompt,
29121
+ buildSubconsciousActivitySnapshot: () => buildSubconsciousActivitySnapshot,
26762
29122
  combineObservationGroupRanges: () => combineObservationGroupRanges,
26763
29123
  deriveObservationGroupProvenance: () => deriveObservationGroupProvenance,
26764
29124
  extractCurrentTask: () => extractCurrentTask,
@@ -26770,10 +29130,13 @@ var observational_memory_exports = /* @__PURE__ */ __exportAll({
26770
29130
  parseAnchorId: () => parseAnchorId,
26771
29131
  parseObservationGroups: () => parseObservationGroups,
26772
29132
  parseObserverOutput: () => parseObserverOutput,
29133
+ publishSubconsciousActivity: () => publishSubconsciousActivity,
26773
29134
  reconcileObservationGroupsFromReflection: () => reconcileObservationGroupsFromReflection,
26774
29135
  renderObservationGroupsForReflection: () => renderObservationGroupsForReflection,
29136
+ renderSubconsciousActivity: () => renderSubconsciousActivity,
26775
29137
  stripEphemeralAnchorIds: () => stripEphemeralAnchorIds,
26776
29138
  stripObservationGroups: () => stripObservationGroups,
29139
+ subconsciousCaptureSchema: () => subconsciousCaptureSchema,
26777
29140
  summarizeConversation: () => summarizeConversation,
26778
29141
  wrapInObservationGroup: () => wrapInObservationGroup
26779
29142
  });
@@ -27016,10 +29379,11 @@ const DEFAULT_EMBEDDING_CACHE_MAX_SIZE = 1e3;
27016
29379
  * Concrete implementation of MastraMemory that adds support for thread configuration
27017
29380
  * and message injection.
27018
29381
  */
27019
- var Memory = class extends MastraMemory {
29382
+ var Memory = class Memory extends MastraMemory {
27020
29383
  _omEngine;
27021
29384
  _omEngineInstance;
27022
29385
  _mastraInstance;
29386
+ _knowledgeSemanticIndex;
27023
29387
  /**
27024
29388
  * Every vector cleanup that deleteThread or deleteMessages started in the background.
27025
29389
  * Callers do not wait for the cleanup, so this handle is the only join point.
@@ -27032,6 +29396,25 @@ var Memory = class extends MastraMemory {
27032
29396
  trackVectorCleanup(cleanup) {
27033
29397
  this.pendingVectorCleanup = Promise.allSettled([this.pendingVectorCleanup, cleanup]).then(() => void 0);
27034
29398
  }
29399
+ /**
29400
+ * Resolve once all background work this Memory started has finished: observational-memory
29401
+ * cycles (buffered observation and reflection, including the nested agent runs they spawn)
29402
+ * and vector cleanup from `deleteThread` / `deleteMessages`.
29403
+ *
29404
+ * Callers that own the storage connection should await this before closing it, otherwise
29405
+ * background statements can race the close.
29406
+ *
29407
+ * ```ts
29408
+ * await agent.generate('hello', { memory: { thread, resource } });
29409
+ * await memory.settled();
29410
+ * await store.close();
29411
+ * ```
29412
+ */
29413
+ async settled() {
29414
+ await this.pendingVectorCleanup;
29415
+ await (this._omEngine ? await this._omEngine : this._omEngineInstance)?.settled();
29416
+ await this.pendingVectorCleanup;
29417
+ }
27035
29418
  /** The shared ObservationalMemory engine. Lazily created on first access. */
27036
29419
  get omEngine() {
27037
29420
  if (!this._omEngine) this._omEngine = this._initOMEngine().then((engine) => {
@@ -27048,7 +29431,60 @@ var Memory = class extends MastraMemory {
27048
29431
  else this._omEngine?.then((engine) => engine?.__registerMastra(mastra));
27049
29432
  }
27050
29433
  getMergedThreadConfig(config) {
27051
- return this.applyManagedWorkingMemoryDefaults(super.getMergedThreadConfig(config));
29434
+ const merged = super.getMergedThreadConfig(config);
29435
+ return this.applyManagedWorkingMemoryDefaults(this.applySubconsciousDefaults(merged));
29436
+ }
29437
+ applySubconsciousDefaults(config) {
29438
+ const omConfig = normalizeObservationalMemoryConfig(config.observationalMemory);
29439
+ if (!omConfig?.experimental_subconscious) return config;
29440
+ if (!(omConfig.experimental_subconscious instanceof Subconscious)) throw new Error("observationalMemory.experimental_subconscious must be a Subconscious instance.");
29441
+ const observation = omConfig.observation ?? {};
29442
+ const extract = observation.extract ?? [];
29443
+ const existingSlugs = new Set(extract.map((extractor) => extractor.slug));
29444
+ const subconsciousExtractors = omConfig.experimental_subconscious.createObservationExtractors(observation.model ?? omConfig.model).filter((extractor) => !existingSlugs.has(extractor.slug));
29445
+ return {
29446
+ ...config,
29447
+ observationalMemory: {
29448
+ ...omConfig,
29449
+ observation: {
29450
+ ...observation,
29451
+ extract: [...extract, ...subconsciousExtractors]
29452
+ }
29453
+ }
29454
+ };
29455
+ }
29456
+ /** Threads with a curation currently in flight in this process; guards same-process double-fire only. */
29457
+ _curationsInFlight = /* @__PURE__ */ new Set();
29458
+ /**
29459
+ * Run the subconscious curator directly over the pending fact worklist, without a reflection.
29460
+ * Cross-process serialization is the curation cursor's job; a lost race wastes one advisory run.
29461
+ *
29462
+ * Outcomes: `ran` (curator executed), `no-op` (empty worklist and no prompt, or no curate agent
29463
+ * configured), `skipped` (a curation for this thread is already in flight in this process), and
29464
+ * `no-model` (no per-agent, observational memory, or main-agent model could be resolved).
29465
+ */
29466
+ async runCuration(options) {
29467
+ const omConfig = normalizeObservationalMemoryConfig(this.threadConfig.observationalMemory);
29468
+ const subconscious = omConfig?.experimental_subconscious;
29469
+ if (!omConfig || !(subconscious instanceof Subconscious)) return { outcome: "no-op" };
29470
+ if (this._curationsInFlight.has(options.threadId)) return { outcome: "skipped" };
29471
+ this._curationsInFlight.add(options.threadId);
29472
+ try {
29473
+ return { outcome: await createCuratorHandler(this, subconscious.resolved, new Memory({
29474
+ storage: this.storage,
29475
+ options: { observationalMemory: false }
29476
+ }), { omModel: omConfig.observation?.model ?? omConfig.model })({
29477
+ parentThreadId: options.threadId,
29478
+ resourceId: options.resourceId,
29479
+ observations: options.prompt ?? "",
29480
+ requestContext: options.requestContext
29481
+ }) === "ran" ? "ran" : "no-op" };
29482
+ } catch (error) {
29483
+ if (error instanceof Error && error.message.includes("requires the main agent to resolve its model")) return { outcome: "no-model" };
29484
+ throw error;
29485
+ } finally {
29486
+ this._curationsInFlight.delete(options.threadId);
29487
+ }
27052
29488
  }
27053
29489
  applyManagedWorkingMemoryDefaults(config) {
27054
29490
  const omConfig = normalizeObservationalMemoryConfig(config.observationalMemory);
@@ -27092,6 +29528,28 @@ var Memory = class extends MastraMemory {
27092
29528
  if (!this.vector) throw new Error("`retrieval: { vector: true }` requires a vector store. Pass a `vector` option to your Memory instance.");
27093
29529
  if (!this.embedder) throw new Error("`retrieval: { vector: true }` requires an embedder. Pass an `embedder` option to your Memory instance.");
27094
29530
  }
29531
+ if (omConfig?.experimental_subconscious) {
29532
+ if (!this.vector) throw new Error("Subconscious semantic knowledge requires a vector store. Pass a `vector` option to Memory.");
29533
+ if (!this.embedder) throw new Error("Subconscious semantic knowledge requires an embedder. Pass an `embedder` option to Memory.");
29534
+ }
29535
+ }
29536
+ async getKnowledgeStore() {
29537
+ const store = await this.storage.getStore("knowledge");
29538
+ if (!store) throw new Error(`Knowledge storage domain is not available on ${this.storage.constructor.name}`);
29539
+ return store;
29540
+ }
29541
+ async getKnowledgeSemanticIndex() {
29542
+ if (!this.vector || !this.embedder) throw new Error("Subconscious semantic knowledge requires both a vector store and an embedder.");
29543
+ this._knowledgeSemanticIndex ??= this.getKnowledgeStore().then((knowledge) => new KnowledgeSemanticIndexCoordinator({
29544
+ knowledge,
29545
+ vector: this.vector,
29546
+ embedder: this.embedder,
29547
+ embedderOptions: this.embedderOptions
29548
+ }));
29549
+ return this._knowledgeSemanticIndex;
29550
+ }
29551
+ async drainKnowledgeSemanticIndex(scope) {
29552
+ return (await this.getKnowledgeSemanticIndex()).drain(scope);
27095
29553
  }
27096
29554
  /**
27097
29555
  * Gets the memory storage domain, throwing if not available.
@@ -27860,6 +30318,7 @@ ${workingMemory}`;
27860
30318
  if (!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.");
27861
30319
  if (omConfig.observation?.bufferTokens !== false && !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.");
27862
30320
  if (!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.");
30321
+ if (omConfig.experimental_subconscious) await this.getKnowledgeStore();
27863
30322
  const { ObservationalMemory: OMClass } = await Promise.resolve().then(() => observational_memory_exports);
27864
30323
  const onIndexObservations = this.hasRetrievalSearch(omConfig.retrieval) ? async (observation) => {
27865
30324
  await this.indexObservation(observation);
@@ -27873,9 +30332,21 @@ ${workingMemory}`;
27873
30332
  activateOnProviderChange: omConfig.activateOnProviderChange,
27874
30333
  shareTokenBudget: omConfig.shareTokenBudget,
27875
30334
  model: omConfig.model,
30335
+ curationCadence: omConfig.experimental_subconscious instanceof Subconscious ? omConfig.experimental_subconscious.resolved.curationCadence : void 0,
27876
30336
  mastra: this._mastraInstance,
27877
30337
  onIndexObservations,
27878
30338
  hooks: omConfig.hooks,
30339
+ onReflectionCommitted: omConfig.experimental_subconscious instanceof Subconscious ? (() => {
30340
+ const resolved = omConfig.experimental_subconscious.resolved;
30341
+ const omModel = omConfig.observation?.model ?? omConfig.model;
30342
+ return composeReflectionAgentHandlers([createCuratorHandler(this, resolved, new Memory({
30343
+ storage: this.storage,
30344
+ options: { observationalMemory: false }
30345
+ }), { omModel }), createLearnerHandler(this, resolved, new Memory({
30346
+ storage: this.storage,
30347
+ options: { observationalMemory: false }
30348
+ }), { omModel })]);
30349
+ })() : void 0,
27879
30350
  observation: omConfig.observation ? {
27880
30351
  model: omConfig.observation.model,
27881
30352
  messageTokens: omConfig.observation.messageTokens,
@@ -28265,6 +30736,7 @@ Notes:
28265
30736
  retrievalScope: typeof omConfig.retrieval === "object" ? omConfig.retrieval.scope ?? "resource" : "resource",
28266
30737
  searchEnabled: this.hasRetrievalSearch(omConfig.retrieval)
28267
30738
  });
30739
+ if (omConfig?.experimental_subconscious instanceof Subconscious && omConfig.experimental_subconscious.resolved.tools) Object.assign(tools, createKnowledgeTools(this));
28268
30740
  return tools;
28269
30741
  }
28270
30742
  /**
@@ -28745,6 +31217,8 @@ Notes:
28745
31217
  if (om) processors.push(om);
28746
31218
  const wm = await this.createWorkingMemoryStateProcessor(configuredProcessors, context);
28747
31219
  if (wm) processors.push(wm);
31220
+ const pins = await this.createPinnedStateProcessor(configuredProcessors, context);
31221
+ if (pins) processors.push(pins);
28748
31222
  return processors;
28749
31223
  }
28750
31224
  /**
@@ -28792,8 +31266,23 @@ Notes:
28792
31266
  if (configuredProcessors.some((p) => !("workflow" in p) && p.id === WORKING_MEMORY_STATE_PROCESSOR_ID)) return null;
28793
31267
  return new WorkingMemoryStateProcessor(this, runtimeMemory?.memoryConfig);
28794
31268
  }
31269
+ /**
31270
+ * Creates a PinnedStateProcessor when Subconscious pins are enabled on the
31271
+ * merged thread config. The gate is the validated `resolved.pins` on the
31272
+ * Subconscious instance, never the raw user object. Returns null when pins
31273
+ * are off or the processor is already present in the user's configured
31274
+ * processors.
31275
+ */
31276
+ async createPinnedStateProcessor(configuredProcessors = [], context) {
31277
+ const runtimeMemory = context?.get("MastraMemory");
31278
+ const subconscious = normalizeObservationalMemoryConfig(this.getMergedThreadConfig(runtimeMemory?.memoryConfig).observationalMemory)?.experimental_subconscious;
31279
+ if (!(subconscious instanceof Subconscious) || subconscious.resolved.pins === false) return null;
31280
+ const { PinnedStateProcessor, SUBCONSCIOUS_PINS_STATE_ID } = await Promise.resolve().then(() => subconscious_exports);
31281
+ if (configuredProcessors.some((p) => !("workflow" in p) && p.id === SUBCONSCIOUS_PINS_STATE_ID)) return null;
31282
+ return new PinnedStateProcessor({ getKnowledgeStore: () => this.storage.getStore("knowledge") });
31283
+ }
28795
31284
  };
28796
31285
  //#endregion
28797
- export { extractCurrentTask as A, OBSERVATION_CONTEXT_INSTRUCTIONS as B, WorkingMemoryExtractor as C, OBSERVER_SYSTEM_PROMPT as D, TokenCounter as E, injectAnchorIds as F, OBSERVATION_CONTINUATION_HINT as H, parseAnchorId as I, stripEphemeralAnchorIds as L, hasCurrentTaskSection as M, optimizeObservationsForContext as N, buildObserverPrompt as O, parseObserverOutput as P, Extractor as R, deepMergeWorkingMemory as S, summarizeConversation as T, OBSERVATION_CONTEXT_PROMPT as V, reconcileObservationGroupsFromReflection as _, extractWorkingMemoryContent as a, wrapInObservationGroup as b, WORKING_MEMORY_STATE_ID as c, getObservationsAsOf as d, ObservationalMemoryProcessor as f, parseObservationGroups as g, deriveObservationGroupProvenance as h, WorkingMemory as i, formatMessagesForObserver as j, buildObserverSystemPrompt as k, WORKING_MEMORY_STATE_PROCESSOR_ID as l, combineObservationGroupRanges as m, MessageHistory$1 as n, extractWorkingMemoryTags as o, ObservationalMemory as p, SemanticRecall as r, removeWorkingMemoryTags as s, Memory as t, WorkingMemoryStateProcessor as u, renderObservationGroupsForReflection as v, SUMMARIZE_THREAD_DEFAULTS as w, ModelByInputTokens as x, stripObservationGroups as y, OBSERVATIONAL_MEMORY_DEFAULTS as z };
31286
+ export { formatMessagesForObserver as A, OBSERVATION_CONTINUATION_HINT as B, SUMMARIZE_THREAD_DEFAULTS as C, buildObserverPrompt as D, OBSERVER_SYSTEM_PROMPT as E, parseAnchorId as F, ModelByInputTokens as G, KnowledgeSemanticIndexCoordinator as H, stripEphemeralAnchorIds as I, SUBCONSCIOUS_ACTIVITY_STATE_ID as J, SubconsciousCaptureExtractor as K, OBSERVATIONAL_MEMORY_DEFAULTS as L, optimizeObservationsForContext as M, parseObserverOutput as N, buildObserverSystemPrompt as O, injectAnchorIds as P, Extractor as Q, OBSERVATION_CONTEXT_INSTRUCTIONS as R, WorkingMemoryExtractor as S, TokenCounter as T, StaleKnowledgeSemanticIndexError as U, Subconscious as V, SubconsciousRemindExtractor as W, publishSubconsciousActivity as X, buildSubconsciousActivitySnapshot as Y, renderSubconsciousActivity as Z, reconcileObservationGroupsFromReflection as _, extractWorkingMemoryContent as a, wrapInObservationGroup as b, WORKING_MEMORY_STATE_ID as c, getObservationsAsOf as d, ObservationalMemoryProcessor as f, parseObservationGroups as g, deriveObservationGroupProvenance as h, WorkingMemory as i, hasCurrentTaskSection as j, extractCurrentTask as k, WORKING_MEMORY_STATE_PROCESSOR_ID as l, combineObservationGroupRanges as m, MessageHistory$1 as n, extractWorkingMemoryTags as o, ObservationalMemory as p, subconsciousCaptureSchema as q, SemanticRecall as r, removeWorkingMemoryTags as s, Memory as t, WorkingMemoryStateProcessor as u, renderObservationGroupsForReflection as v, summarizeConversation as w, deepMergeWorkingMemory as x, stripObservationGroups as y, OBSERVATION_CONTEXT_PROMPT as z };
28798
31287
 
28799
- //# sourceMappingURL=src-BKRU-0Fx.js.map
31288
+ //# sourceMappingURL=src-t_4yoTX9.js.map