@lota-sdk/core 0.4.8 → 0.4.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +11 -12
- package/src/ai/embedding-cache.ts +96 -22
- package/src/ai-gateway/ai-gateway.ts +766 -223
- package/src/config/agent-defaults.ts +189 -75
- package/src/config/agent-types.ts +54 -4
- package/src/config/background-processing.ts +1 -1
- package/src/config/constants.ts +8 -2
- package/src/config/index.ts +0 -1
- package/src/config/logger.ts +299 -19
- package/src/config/thread-defaults.ts +40 -20
- package/src/create-runtime.ts +200 -449
- package/src/db/base.service.ts +52 -28
- package/src/db/cursor-pagination.ts +71 -30
- package/src/db/memory-query-builder.ts +2 -1
- package/src/db/memory-store.helpers.ts +4 -7
- package/src/db/memory-store.ts +868 -601
- package/src/db/memory.ts +396 -280
- package/src/db/record-id.ts +32 -10
- package/src/db/schema-fingerprint.ts +30 -12
- package/src/db/service-normalization.ts +288 -0
- package/src/db/service.ts +912 -779
- package/src/db/startup.ts +153 -68
- package/src/db/transaction-conflict.ts +15 -0
- package/src/effect/awaitable-effect.ts +96 -0
- package/src/effect/errors.ts +121 -0
- package/src/effect/helpers.ts +123 -0
- package/src/effect/index.ts +24 -0
- package/src/effect/layers.ts +238 -0
- package/src/effect/runtime-ref.ts +25 -0
- package/src/effect/runtime.ts +46 -0
- package/src/effect/services.ts +61 -0
- package/src/effect/zod.ts +43 -0
- package/src/embeddings/provider.ts +128 -83
- package/src/index.ts +48 -1
- package/src/openrouter/direct-provider.ts +11 -35
- package/src/queues/autonomous-job.queue.ts +117 -73
- package/src/queues/context-compaction.queue.ts +50 -17
- package/src/queues/delayed-node-promotion.queue.ts +46 -17
- package/src/queues/document-processor.queue.ts +52 -77
- package/src/queues/memory-consolidation.queue.ts +47 -32
- package/src/queues/organization-learning.queue.ts +26 -4
- package/src/queues/plan-agent-heartbeat.queue.ts +71 -24
- package/src/queues/plan-scheduler.queue.ts +97 -33
- package/src/queues/post-chat-memory.queue.ts +56 -26
- package/src/queues/queue-factory.ts +227 -59
- package/src/queues/standalone-worker.ts +39 -0
- package/src/queues/title-generation.queue.ts +45 -11
- package/src/redis/connection.ts +182 -113
- package/src/redis/index.ts +6 -8
- package/src/redis/org-memory-lock.ts +60 -27
- package/src/redis/redis-lease-lock.ts +200 -121
- package/src/redis/runtime-connection.ts +20 -0
- package/src/redis/stream-context.ts +92 -46
- package/src/runtime/agent-identity-overrides.ts +2 -2
- package/src/runtime/agent-runtime-policy.ts +5 -2
- package/src/runtime/agent-stream-helpers.ts +24 -9
- package/src/runtime/chat-run-orchestration.ts +102 -19
- package/src/runtime/chat-run-registry.ts +36 -2
- package/src/runtime/context-compaction/context-compaction-runtime.ts +107 -0
- package/src/runtime/{context-compaction.ts → context-compaction/context-compaction.ts} +161 -94
- package/src/runtime/domain-layer.ts +192 -0
- package/src/runtime/execution-plan-visibility.ts +2 -2
- package/src/runtime/execution-plan.ts +42 -15
- package/src/runtime/graph-designer.ts +16 -4
- package/src/runtime/helper-model.ts +139 -48
- package/src/runtime/index.ts +7 -8
- package/src/runtime/indexed-repositories-policy.ts +3 -3
- package/src/runtime/{memory-block.ts → memory/memory-block.ts} +50 -36
- package/src/runtime/{memory-digest-policy.ts → memory/memory-digest-policy.ts} +1 -1
- package/src/runtime/{memory-pipeline.ts → memory/memory-pipeline.ts} +54 -67
- package/src/runtime/{memory-prompts-fact.ts → memory/memory-prompts-fact.ts} +2 -2
- package/src/runtime/memory/memory-scope.ts +53 -0
- package/src/runtime/plugin-resolution.ts +124 -25
- package/src/runtime/plugin-types.ts +9 -1
- package/src/runtime/post-turn-side-effects.ts +177 -130
- package/src/runtime/retrieval-adapters.ts +40 -6
- package/src/runtime/runtime-accessors.ts +92 -0
- package/src/runtime/runtime-config.ts +150 -61
- package/src/runtime/runtime-extensions.ts +23 -25
- package/src/runtime/runtime-lifecycle.ts +124 -0
- package/src/runtime/runtime-services.ts +386 -0
- package/src/runtime/runtime-token.ts +47 -0
- package/src/runtime/social-chat/social-chat-agent-runner.ts +159 -0
- package/src/runtime/{social-chat-history.ts → social-chat/social-chat-history.ts} +51 -20
- package/src/runtime/social-chat/social-chat.ts +630 -0
- package/src/runtime/specialist-runner.ts +36 -10
- package/src/runtime/team-consultation/team-consultation-orchestrator.ts +433 -0
- package/src/runtime/{team-consultation-prompts.ts → team-consultation/team-consultation-prompts.ts} +6 -2
- package/src/runtime/thread-chat-helpers.ts +2 -2
- package/src/runtime/thread-plan-turn.ts +2 -1
- package/src/runtime/thread-turn-context.ts +183 -111
- package/src/runtime/turn-lifecycle.ts +93 -27
- package/src/services/agent-activity.service.ts +287 -203
- package/src/services/agent-executor.service.ts +253 -149
- package/src/services/artifact.service.ts +231 -149
- package/src/services/attachment.service.ts +171 -115
- package/src/services/autonomous-job.service.ts +890 -491
- package/src/services/background-work.service.ts +54 -0
- package/src/services/chat-run-registry.service.ts +13 -1
- package/src/services/context-compaction.service.ts +136 -86
- package/src/services/document-chunk.service.ts +151 -88
- package/src/services/execution-plan/execution-plan-approval.ts +26 -0
- package/src/services/execution-plan/execution-plan-context.ts +29 -0
- package/src/services/execution-plan/execution-plan-graph.ts +278 -0
- package/src/services/execution-plan/execution-plan-schedule.ts +84 -0
- package/src/services/execution-plan/execution-plan-spec.ts +75 -0
- package/src/services/execution-plan/execution-plan.service.ts +1041 -0
- package/src/services/feedback-loop.service.ts +132 -76
- package/src/services/global-orchestrator.service.ts +101 -168
- package/src/services/graph-full-routing.ts +193 -0
- package/src/services/index.ts +19 -21
- package/src/services/institutional-memory.service.ts +213 -125
- package/src/services/learned-skill.service.ts +368 -260
- package/src/services/memory/memory-conversation.ts +95 -0
- package/src/services/memory/memory-errors.ts +27 -0
- package/src/services/memory/memory-org-memory.ts +50 -0
- package/src/services/memory/memory-preseeded.ts +86 -0
- package/src/services/memory/memory-rerank.ts +297 -0
- package/src/services/{memory-utils.ts → memory/memory-utils.ts} +6 -5
- package/src/services/memory/memory.service.ts +674 -0
- package/src/services/memory/rerank.service.ts +201 -0
- package/src/services/monitoring-window.service.ts +92 -70
- package/src/services/mutating-approval.service.ts +62 -53
- package/src/services/node-workspace.service.ts +141 -98
- package/src/services/notification.service.ts +29 -16
- package/src/services/organization-member.service.ts +120 -66
- package/src/services/organization.service.ts +153 -77
- package/src/services/ownership-dispatcher.service.ts +456 -263
- package/src/services/plan/plan-agent-heartbeat.service.ts +234 -0
- package/src/services/plan/plan-agent-query.service.ts +322 -0
- package/src/services/{plan-approval.service.ts → plan/plan-approval.service.ts} +45 -22
- package/src/services/plan/plan-artifact.service.ts +60 -0
- package/src/services/plan/plan-builder.service.ts +76 -0
- package/src/services/plan/plan-checkpoint.service.ts +103 -0
- package/src/services/{plan-compiler.service.ts → plan/plan-compiler.service.ts} +26 -9
- package/src/services/plan/plan-completion-side-effects.ts +169 -0
- package/src/services/plan/plan-coordination.service.ts +181 -0
- package/src/services/plan/plan-cycle.service.ts +405 -0
- package/src/services/plan/plan-deadline.service.ts +533 -0
- package/src/services/plan/plan-event-delivery.service.ts +266 -0
- package/src/services/plan/plan-executor-context.ts +35 -0
- package/src/services/plan/plan-executor-graph.ts +522 -0
- package/src/services/plan/plan-executor-helpers.ts +307 -0
- package/src/services/plan/plan-executor-persistence.ts +209 -0
- package/src/services/plan/plan-executor.service.ts +1737 -0
- package/src/services/{plan-helpers.ts → plan/plan-helpers.ts} +1 -1
- package/src/services/{plan-run-data.ts → plan/plan-run-data.ts} +4 -4
- package/src/services/plan/plan-run-serialization.ts +15 -0
- package/src/services/plan/plan-run.service.ts +637 -0
- package/src/services/plan/plan-scheduler.service.ts +379 -0
- package/src/services/plan/plan-template.service.ts +224 -0
- package/src/services/plan/plan-transaction-events.ts +36 -0
- package/src/services/plan/plan-validator.service.ts +907 -0
- package/src/services/plan/plan-workspace.service.ts +131 -0
- package/src/services/plugin-executor.service.ts +102 -68
- package/src/services/quality-metrics.service.ts +112 -94
- package/src/services/queue-job.service.ts +288 -231
- package/src/services/recent-activity-title.service.ts +73 -36
- package/src/services/recent-activity.service.ts +274 -259
- package/src/services/skill-resolver.service.ts +38 -12
- package/src/services/social-chat-history.service.ts +190 -122
- package/src/services/system-executor.service.ts +96 -61
- package/src/services/thread/thread-active-run.ts +203 -0
- package/src/services/thread/thread-bootstrap.ts +385 -0
- package/src/services/thread/thread-listing.ts +199 -0
- package/src/services/thread/thread-memory-block.ts +130 -0
- package/src/services/thread/thread-message.service.ts +379 -0
- package/src/services/thread/thread-record-store.ts +155 -0
- package/src/services/thread/thread-title.service.ts +74 -0
- package/src/services/thread/thread-turn-execution.ts +280 -0
- package/src/services/thread/thread-turn-message-context.ts +73 -0
- package/src/services/thread/thread-turn-preparation.service.ts +1148 -0
- package/src/services/thread/thread-turn-streaming.ts +403 -0
- package/src/services/thread/thread-turn-tracing.ts +35 -0
- package/src/services/thread/thread-turn.ts +376 -0
- package/src/services/thread/thread.service.ts +344 -0
- package/src/services/user.service.ts +82 -32
- package/src/services/write-intent-validator.service.ts +63 -51
- package/src/storage/attachment-parser.ts +69 -27
- package/src/storage/attachment-storage.service.ts +334 -275
- package/src/storage/generated-document-storage.service.ts +66 -34
- package/src/system-agents/agent-result.ts +3 -1
- package/src/system-agents/context-compaction.agent.ts +3 -3
- package/src/system-agents/delegated-agent-factory.ts +159 -90
- package/src/system-agents/helper-agent-options.ts +1 -1
- package/src/system-agents/memory-reranker.agent.ts +3 -3
- package/src/system-agents/memory.agent.ts +3 -3
- package/src/system-agents/recent-activity-title-refiner.agent.ts +3 -3
- package/src/system-agents/regular-chat-memory-digest.agent.ts +3 -3
- package/src/system-agents/skill-extractor.agent.ts +3 -3
- package/src/system-agents/skill-manager.agent.ts +3 -3
- package/src/system-agents/thread-router.agent.ts +157 -113
- package/src/system-agents/title-generator.agent.ts +3 -3
- package/src/tools/execution-plan.tool.ts +241 -171
- package/src/tools/fetch-webpage.tool.ts +29 -18
- package/src/tools/firecrawl-client.ts +26 -6
- package/src/tools/index.ts +1 -0
- package/src/tools/memory-block.tool.ts +14 -6
- package/src/tools/plan-approval.tool.ts +57 -47
- package/src/tools/read-file-parts.tool.ts +44 -33
- package/src/tools/remember-memory.tool.ts +65 -45
- package/src/tools/search-web.tool.ts +33 -22
- package/src/tools/search.tool.ts +41 -29
- package/src/tools/team-think.tool.ts +125 -84
- package/src/tools/user-questions.tool.ts +4 -3
- package/src/tools/web-tool-shared.ts +6 -0
- package/src/utils/async.ts +25 -22
- package/src/utils/crypto.ts +21 -0
- package/src/utils/date-time.ts +40 -1
- package/src/utils/errors.ts +111 -20
- package/src/utils/hono-error-handler.ts +24 -39
- package/src/utils/index.ts +2 -1
- package/src/utils/null-proto-record.ts +41 -0
- package/src/utils/sse-keepalive.ts +124 -21
- package/src/workers/bootstrap.ts +164 -52
- package/src/workers/memory-consolidation.worker.ts +325 -237
- package/src/workers/organization-learning.worker.ts +50 -16
- package/src/workers/regular-chat-memory-digest.helpers.ts +28 -27
- package/src/workers/regular-chat-memory-digest.runner.ts +185 -114
- package/src/workers/skill-extraction.runner.ts +176 -93
- package/src/workers/utils/file-section-chunker.ts +8 -10
- package/src/workers/utils/repo-structure-extractor.ts +349 -260
- package/src/workers/utils/repomix-file-sections.ts +2 -2
- package/src/workers/utils/thread-message-query.ts +97 -38
- package/src/workers/worker-utils.ts +74 -31
- package/src/config/debug-logger.ts +0 -47
- package/src/config/search.ts +0 -3
- package/src/redis/connection-accessor.ts +0 -26
- package/src/runtime/agent-types.ts +0 -1
- package/src/runtime/context-compaction-runtime.ts +0 -87
- package/src/runtime/memory-scope.ts +0 -43
- package/src/runtime/social-chat-agent-runner.ts +0 -118
- package/src/runtime/social-chat.ts +0 -516
- package/src/runtime/team-consultation-orchestrator.ts +0 -272
- package/src/services/adaptive-playbook.service.ts +0 -152
- package/src/services/artifact-provenance.service.ts +0 -172
- package/src/services/chat-attachments.service.ts +0 -17
- package/src/services/context-compaction-runtime.singleton.ts +0 -13
- package/src/services/execution-plan.service.ts +0 -1118
- package/src/services/memory.service.ts +0 -914
- package/src/services/plan-agent-heartbeat.service.ts +0 -136
- package/src/services/plan-agent-query.service.ts +0 -267
- package/src/services/plan-artifact.service.ts +0 -50
- package/src/services/plan-builder.service.ts +0 -67
- package/src/services/plan-checkpoint.service.ts +0 -81
- package/src/services/plan-completion-side-effects.ts +0 -80
- package/src/services/plan-coordination.service.ts +0 -157
- package/src/services/plan-cycle.service.ts +0 -284
- package/src/services/plan-deadline.service.ts +0 -430
- package/src/services/plan-event-delivery.service.ts +0 -166
- package/src/services/plan-executor.service.ts +0 -1950
- package/src/services/plan-run.service.ts +0 -515
- package/src/services/plan-scheduler.service.ts +0 -240
- package/src/services/plan-template.service.ts +0 -177
- package/src/services/plan-validator.service.ts +0 -818
- package/src/services/plan-workspace.service.ts +0 -83
- package/src/services/rerank.service.ts +0 -156
- package/src/services/thread-message.service.ts +0 -275
- package/src/services/thread-plan-registry.service.ts +0 -22
- package/src/services/thread-title.service.ts +0 -39
- package/src/services/thread-turn-preparation.service.ts +0 -1147
- package/src/services/thread-turn.ts +0 -172
- package/src/services/thread.service.ts +0 -869
- package/src/utils/env.ts +0 -8
- /package/src/runtime/{context-compaction-constants.ts → context-compaction/context-compaction-constants.ts} +0 -0
- /package/src/runtime/{memory-format.ts → memory/memory-format.ts} +0 -0
- /package/src/runtime/{memory-prompts-parse.ts → memory/memory-prompts-parse.ts} +0 -0
- /package/src/runtime/{memory-prompts-update.ts → memory/memory-prompts-update.ts} +0 -0
- /package/src/runtime/{social-chat-prompts.ts → social-chat/social-chat-prompts.ts} +0 -0
- /package/src/services/{plan-node-spec.ts → plan/plan-node-spec.ts} +0 -0
- /package/src/services/{thread-constants.ts → thread/thread-constants.ts} +0 -0
- /package/src/services/{thread.types.ts → thread/thread.types.ts} +0 -0
|
@@ -1,914 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod'
|
|
2
|
-
|
|
3
|
-
import { agentRoster } from '../config/agent-defaults'
|
|
4
|
-
import { aiLogger } from '../config/logger'
|
|
5
|
-
import { Memory } from '../db/memory'
|
|
6
|
-
import { isUniqueIndexConflict } from '../db/memory-store.helpers'
|
|
7
|
-
import type {
|
|
8
|
-
AddOptions,
|
|
9
|
-
ExtractedFact,
|
|
10
|
-
MemoryListScalar,
|
|
11
|
-
MemoryRecord,
|
|
12
|
-
MemorySearchResult,
|
|
13
|
-
MemoryType,
|
|
14
|
-
Message,
|
|
15
|
-
RelationType,
|
|
16
|
-
} from '../db/memory-types'
|
|
17
|
-
import { withOrgMemoryLock } from '../redis/org-memory-lock'
|
|
18
|
-
import { createHelperModelRuntime } from '../runtime/helper-model'
|
|
19
|
-
import { sanitizeAgentOutputForMemory } from '../runtime/llm-content'
|
|
20
|
-
import { ORG_SCOPE_PREFIX, agentScopeId, scopeId } from '../runtime/memory-scope'
|
|
21
|
-
import {
|
|
22
|
-
countScopedRetrievalCandidates,
|
|
23
|
-
executeScopedRetrieval,
|
|
24
|
-
scopedRetrievalToMap,
|
|
25
|
-
} from '../runtime/retrieval-adapters'
|
|
26
|
-
import type { MemoryRerankerStrategy } from '../runtime/runtime-config'
|
|
27
|
-
import { getRuntimeConfig } from '../runtime/runtime-config'
|
|
28
|
-
import { createMemoryRerankerAgent, MEMORY_RERANKER_PROMPT } from '../system-agents/memory-reranker.agent'
|
|
29
|
-
import { createOrgMemoryAgent, ORG_MEMORY_PROMPT } from '../system-agents/memory.agent'
|
|
30
|
-
import { clampImportance, compactWhitespace, truncateText } from '../utils/string'
|
|
31
|
-
import { formatMemoryResults, formatRerankedResults, getCandidateLimit } from './memory-utils'
|
|
32
|
-
import { rerankService } from './rerank.service'
|
|
33
|
-
|
|
34
|
-
const ORG_MEMORY_TYPE = 'fact'
|
|
35
|
-
const RERANK_CANDIDATE_MAX_CHARS = 500
|
|
36
|
-
const MAX_MEMORY_RESULTS_PER_SCOPE = 10
|
|
37
|
-
const PRESEEDED_MEMORY_LIMIT = 5
|
|
38
|
-
const PRESEEDED_MEMORY_MAX_CHARS = 300
|
|
39
|
-
const PRESEEDED_MIN_IMPORTANCE = 0.7
|
|
40
|
-
const PRESEEDED_MEMORY_DURABILITY: MemoryRecord['durability'] = 'core'
|
|
41
|
-
const MAX_CONVERSATION_HISTORY_MESSAGES = 24
|
|
42
|
-
const MAX_CONVERSATION_MESSAGE_CHARS = 1_200
|
|
43
|
-
const MAX_CONVERSATION_MEMORY_BLOCK_CHARS = 2_000
|
|
44
|
-
const MAX_CONVERSATION_ATTACHMENT_CONTEXT_CHARS = 6_000
|
|
45
|
-
const ONBOARDING_MEMORY_MAX_FACTS = 16
|
|
46
|
-
const MAX_ORG_MEMORY_CLIENTS = 128
|
|
47
|
-
const LOW_VALUE_MEMORY_IMPORTANCE_THRESHOLD = 0.45
|
|
48
|
-
const ONBOARDING_MEMORY_EXTRACTION_PROMPT =
|
|
49
|
-
'Onboarding mode is active. Extract multiple concrete startup facts from user-provided context: company mission, product capabilities, customer segments, pricing, traction, go-to-market plans, roadmap, team composition, technical stack, risks, and referenced URLs. Prefer one fact per concrete claim.'
|
|
50
|
-
const DIRECT_MEMORY_ASSESSMENT_PROMPT =
|
|
51
|
-
'The user is submitting a direct memory candidate. Keep the wording faithful. Return one fact only when the statement is durable enough for memory; otherwise return no facts.'
|
|
52
|
-
const RERANK_SECTION_TITLE = 'Most relevant memories'
|
|
53
|
-
|
|
54
|
-
const helperModelRuntime = createHelperModelRuntime()
|
|
55
|
-
|
|
56
|
-
const MemoryRerankOutputSchema = z.object({
|
|
57
|
-
sections: z.array(
|
|
58
|
-
z.object({
|
|
59
|
-
title: z.string(),
|
|
60
|
-
items: z.array(
|
|
61
|
-
z.object({
|
|
62
|
-
id: z.string(),
|
|
63
|
-
relevance: z.string().describe('Short relevance reason. Use empty string when no reason is needed.'),
|
|
64
|
-
}),
|
|
65
|
-
),
|
|
66
|
-
}),
|
|
67
|
-
),
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
export type MemoryRerankOutput = z.infer<typeof MemoryRerankOutputSchema>
|
|
71
|
-
|
|
72
|
-
const isRoutableAgentName = (value?: string): value is string => Boolean(value && agentRoster.includes(value))
|
|
73
|
-
|
|
74
|
-
class MemoryService {
|
|
75
|
-
private orgMemoryCache = new Map<string, Memory>()
|
|
76
|
-
|
|
77
|
-
private getOrCreateMemory(cacheKey: string, cache: Map<string, Memory>): Memory {
|
|
78
|
-
const cached = cache.get(cacheKey)
|
|
79
|
-
if (cached) {
|
|
80
|
-
cache.delete(cacheKey)
|
|
81
|
-
cache.set(cacheKey, cached)
|
|
82
|
-
return cached
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const memory = new Memory({ createAgent: createOrgMemoryAgent }, { customPrompt: ORG_MEMORY_PROMPT })
|
|
86
|
-
|
|
87
|
-
if (cache.size >= MAX_ORG_MEMORY_CLIENTS) {
|
|
88
|
-
const oldestKey = cache.keys().next().value
|
|
89
|
-
if (typeof oldestKey === 'string') {
|
|
90
|
-
cache.delete(oldestKey)
|
|
91
|
-
aiLogger.debug`Evicted cached org memory client for ${oldestKey}`
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
cache.set(cacheKey, memory)
|
|
96
|
-
aiLogger.debug`Memory client created and cached for ${cacheKey}`
|
|
97
|
-
return memory
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
private getOrgMemory(orgId: string): Memory {
|
|
101
|
-
const cacheKey = scopeId(ORG_SCOPE_PREFIX, orgId)
|
|
102
|
-
return this.getOrCreateMemory(cacheKey, this.orgMemoryCache)
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
private truncateCandidateText(value: string): string {
|
|
106
|
-
return truncateText(compactWhitespace(value), RERANK_CANDIDATE_MAX_CHARS)
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
private readNumericMetadata(metadata: Record<string, unknown>, key: string): number {
|
|
110
|
-
const value = metadata[key]
|
|
111
|
-
if (typeof value === 'number' && Number.isFinite(value)) return value
|
|
112
|
-
return 0
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
private buildRerankerCandidateText(candidate: MemorySearchResult): string {
|
|
116
|
-
const metadata = candidate.metadata
|
|
117
|
-
const relationCount = this.readNumericMetadata(metadata, 'relationCount')
|
|
118
|
-
const supportCount = this.readNumericMetadata(metadata, 'supportCount')
|
|
119
|
-
const contradictCount = this.readNumericMetadata(metadata, 'contradictCount')
|
|
120
|
-
|
|
121
|
-
const relatedContextRaw = metadata.relatedContext
|
|
122
|
-
const relatedContext = Array.isArray(relatedContextRaw)
|
|
123
|
-
? relatedContextRaw
|
|
124
|
-
.slice(0, 3)
|
|
125
|
-
.map((item) => String(item).trim())
|
|
126
|
-
.filter((item) => item.length > 0)
|
|
127
|
-
: []
|
|
128
|
-
|
|
129
|
-
const contextLines: string[] = []
|
|
130
|
-
if (relatedContext.length > 0) {
|
|
131
|
-
contextLines.push(`Related context: ${relatedContext.join(' | ')}`)
|
|
132
|
-
}
|
|
133
|
-
if (relationCount > 0 || supportCount > 0 || contradictCount > 0) {
|
|
134
|
-
contextLines.push(
|
|
135
|
-
`Graph signals: relations=${relationCount}; supports=${supportCount}; contradicts=${contradictCount}`,
|
|
136
|
-
)
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
if (contextLines.length === 0) return candidate.content
|
|
140
|
-
return `${candidate.content}\n\n${contextLines.join('\n')}`
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
private normalizeConversationText(value: string, maxChars: number): string {
|
|
144
|
-
const normalized = compactWhitespace(value)
|
|
145
|
-
if (!normalized) return ''
|
|
146
|
-
return truncateText(normalized, maxChars)
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
private resolveAgentScopeNames(agentName?: string, agentNames: string[] = []): string[] {
|
|
150
|
-
const unique = new Set<string>()
|
|
151
|
-
if (typeof agentName === 'string' && agentName.trim()) {
|
|
152
|
-
unique.add(agentName.trim())
|
|
153
|
-
}
|
|
154
|
-
for (const candidate of agentNames) {
|
|
155
|
-
if (typeof candidate !== 'string') continue
|
|
156
|
-
const normalized = candidate.trim()
|
|
157
|
-
if (!normalized) continue
|
|
158
|
-
unique.add(normalized)
|
|
159
|
-
}
|
|
160
|
-
return [...unique].filter((name) => isRoutableAgentName(name))
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
private normalizePreSeededMemoryText(value: string): string {
|
|
164
|
-
return truncateText(compactWhitespace(value), PRESEEDED_MEMORY_MAX_CHARS)
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
private formatPreSeededMemoriesSection(memories: MemoryRecord[]): string | undefined {
|
|
168
|
-
const lines = memories
|
|
169
|
-
.map((memory) => this.normalizePreSeededMemoryText(memory.content))
|
|
170
|
-
.filter((line) => line.length > 0)
|
|
171
|
-
.map((line) => `- ${line}`)
|
|
172
|
-
if (lines.length === 0) return undefined
|
|
173
|
-
|
|
174
|
-
return ['<pre-seeded-memories>', ...lines, '</pre-seeded-memories>'].join('\n')
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
private buildConversationMessages(params: {
|
|
178
|
-
input: string
|
|
179
|
-
output: string
|
|
180
|
-
historyMessages: Array<{ role: 'user' | 'agent'; content: string; agentName?: string }>
|
|
181
|
-
memoryBlock?: string
|
|
182
|
-
attachmentContext?: string
|
|
183
|
-
}): { messages: Message[]; normalizedInput: string; sanitizedOutput: string } {
|
|
184
|
-
const normalizedInput = this.normalizeConversationText(params.input, MAX_CONVERSATION_MESSAGE_CHARS)
|
|
185
|
-
const sanitizedOutput = this.normalizeConversationText(
|
|
186
|
-
sanitizeAgentOutputForMemory(params.output),
|
|
187
|
-
MAX_CONVERSATION_MESSAGE_CHARS,
|
|
188
|
-
)
|
|
189
|
-
if (!normalizedInput || !sanitizedOutput) {
|
|
190
|
-
return { messages: [], normalizedInput, sanitizedOutput }
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
const messages: Message[] = []
|
|
194
|
-
|
|
195
|
-
const normalizedHistory: Message[] = params.historyMessages
|
|
196
|
-
.map((message): Message | null => {
|
|
197
|
-
const role: Message['role'] = message.role === 'agent' ? 'agent' : 'user'
|
|
198
|
-
const normalized = this.normalizeConversationText(
|
|
199
|
-
role === 'agent' ? sanitizeAgentOutputForMemory(message.content) : message.content,
|
|
200
|
-
MAX_CONVERSATION_MESSAGE_CHARS,
|
|
201
|
-
)
|
|
202
|
-
if (!normalized) return null
|
|
203
|
-
return { role, content: normalized }
|
|
204
|
-
})
|
|
205
|
-
.filter((message): message is Message => message !== null)
|
|
206
|
-
.slice(-MAX_CONVERSATION_HISTORY_MESSAGES)
|
|
207
|
-
|
|
208
|
-
messages.push(...normalizedHistory)
|
|
209
|
-
|
|
210
|
-
const normalizedMemoryBlock = this.normalizeConversationText(
|
|
211
|
-
params.memoryBlock ?? '',
|
|
212
|
-
MAX_CONVERSATION_MEMORY_BLOCK_CHARS,
|
|
213
|
-
)
|
|
214
|
-
if (normalizedMemoryBlock) {
|
|
215
|
-
messages.push({ role: 'user', content: `Thread memory block:\n${normalizedMemoryBlock}` })
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
const normalizedAttachmentContext = this.normalizeConversationText(
|
|
219
|
-
params.attachmentContext ?? '',
|
|
220
|
-
MAX_CONVERSATION_ATTACHMENT_CONTEXT_CHARS,
|
|
221
|
-
)
|
|
222
|
-
if (normalizedAttachmentContext) {
|
|
223
|
-
messages.push({ role: 'user', content: `Attachment context:\n${normalizedAttachmentContext}` })
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
messages.push({ role: 'user', content: normalizedInput })
|
|
227
|
-
messages.push({ role: 'agent', content: sanitizedOutput })
|
|
228
|
-
|
|
229
|
-
return { messages, normalizedInput, sanitizedOutput }
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
private async rerankCandidates(
|
|
233
|
-
query: string,
|
|
234
|
-
candidates: MemorySearchResult[],
|
|
235
|
-
maxItems: number,
|
|
236
|
-
): Promise<MemoryRerankOutput | null> {
|
|
237
|
-
if (candidates.length === 0) return null
|
|
238
|
-
|
|
239
|
-
try {
|
|
240
|
-
return this.getRerankerStrategy() === 'rerank'
|
|
241
|
-
? await this.rerankCandidatesWithRerankService(query, candidates, maxItems)
|
|
242
|
-
: await this.rerankCandidatesWithHelper(query, candidates, maxItems)
|
|
243
|
-
} catch (error) {
|
|
244
|
-
aiLogger.warn`Memory reranker failed: ${error}`
|
|
245
|
-
return null
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
private async rerankCandidatesMultiScope(
|
|
250
|
-
query: string,
|
|
251
|
-
scopedCandidates: Array<{ scopeTag: string; candidates: MemorySearchResult[] }>,
|
|
252
|
-
maxItems: number,
|
|
253
|
-
): Promise<MemoryRerankOutput | null> {
|
|
254
|
-
const flattened = scopedCandidates.flatMap(({ scopeTag, candidates }) =>
|
|
255
|
-
candidates.map((candidate) => ({ ...candidate, scopeTag })),
|
|
256
|
-
)
|
|
257
|
-
if (flattened.length === 0 || flattened.length <= maxItems) return null
|
|
258
|
-
|
|
259
|
-
try {
|
|
260
|
-
return this.getRerankerStrategy() === 'rerank'
|
|
261
|
-
? await this.rerankCandidatesMultiScopeWithRerankService(query, flattened, maxItems)
|
|
262
|
-
: await this.rerankCandidatesMultiScopeWithHelper(query, flattened, maxItems)
|
|
263
|
-
} catch (error) {
|
|
264
|
-
aiLogger.warn`Multi-scope memory reranker failed: ${error}`
|
|
265
|
-
return null
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
private getRerankerStrategy(): MemoryRerankerStrategy {
|
|
270
|
-
return getRuntimeConfig().memory.rerankerStrategy
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
private buildRerankOutput(ids: string[], title = RERANK_SECTION_TITLE): MemoryRerankOutput {
|
|
274
|
-
return { sections: [{ title, items: ids.map((id) => ({ id, relevance: '' })) }] }
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
private async rerankCandidatesWithHelper(
|
|
278
|
-
query: string,
|
|
279
|
-
candidates: MemorySearchResult[],
|
|
280
|
-
maxItems: number,
|
|
281
|
-
): Promise<MemoryRerankOutput> {
|
|
282
|
-
return await helperModelRuntime.generateHelperStructured({
|
|
283
|
-
tag: 'memory-reranker',
|
|
284
|
-
createAgent: createMemoryRerankerAgent,
|
|
285
|
-
defaultSystemPrompt: MEMORY_RERANKER_PROMPT,
|
|
286
|
-
messages: [
|
|
287
|
-
{
|
|
288
|
-
role: 'user',
|
|
289
|
-
content: JSON.stringify({
|
|
290
|
-
query,
|
|
291
|
-
maxItems,
|
|
292
|
-
candidates: candidates.map((candidate) => ({
|
|
293
|
-
id: candidate.id,
|
|
294
|
-
text: this.truncateCandidateText(this.buildRerankerCandidateText(candidate)),
|
|
295
|
-
score: candidate.score,
|
|
296
|
-
})),
|
|
297
|
-
}),
|
|
298
|
-
},
|
|
299
|
-
],
|
|
300
|
-
schema: MemoryRerankOutputSchema,
|
|
301
|
-
})
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
private async rerankCandidatesWithRerankService(
|
|
305
|
-
query: string,
|
|
306
|
-
candidates: MemorySearchResult[],
|
|
307
|
-
maxItems: number,
|
|
308
|
-
): Promise<MemoryRerankOutput> {
|
|
309
|
-
const reranked = await rerankService.rerankDocuments({
|
|
310
|
-
query,
|
|
311
|
-
topN: maxItems,
|
|
312
|
-
documents: candidates.map((candidate) => ({
|
|
313
|
-
id: candidate.id,
|
|
314
|
-
text: this.truncateCandidateText(this.buildRerankerCandidateText(candidate)),
|
|
315
|
-
})),
|
|
316
|
-
})
|
|
317
|
-
|
|
318
|
-
return this.buildRerankOutput(reranked.results.map((item) => item.id))
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
private async rerankCandidatesMultiScopeWithHelper(
|
|
322
|
-
query: string,
|
|
323
|
-
flattenedCandidates: Array<MemorySearchResult & { scopeTag: string }>,
|
|
324
|
-
maxItems: number,
|
|
325
|
-
): Promise<MemoryRerankOutput> {
|
|
326
|
-
return await helperModelRuntime.generateHelperStructured({
|
|
327
|
-
tag: 'memory-reranker-multi-scope',
|
|
328
|
-
createAgent: createMemoryRerankerAgent,
|
|
329
|
-
defaultSystemPrompt: MEMORY_RERANKER_PROMPT,
|
|
330
|
-
messages: [
|
|
331
|
-
{
|
|
332
|
-
role: 'user',
|
|
333
|
-
content: JSON.stringify({
|
|
334
|
-
query,
|
|
335
|
-
maxItems,
|
|
336
|
-
candidates: flattenedCandidates.map((candidate) => ({
|
|
337
|
-
id: candidate.id,
|
|
338
|
-
text: this.truncateCandidateText(this.buildRerankerCandidateText(candidate)),
|
|
339
|
-
score: candidate.score,
|
|
340
|
-
scope: candidate.scopeTag,
|
|
341
|
-
})),
|
|
342
|
-
}),
|
|
343
|
-
},
|
|
344
|
-
],
|
|
345
|
-
schema: MemoryRerankOutputSchema,
|
|
346
|
-
})
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
private async rerankCandidatesMultiScopeWithRerankService(
|
|
350
|
-
query: string,
|
|
351
|
-
flattenedCandidates: Array<MemorySearchResult & { scopeTag: string }>,
|
|
352
|
-
maxItems: number,
|
|
353
|
-
): Promise<MemoryRerankOutput> {
|
|
354
|
-
const reranked = await rerankService.rerankDocuments({
|
|
355
|
-
query,
|
|
356
|
-
topN: maxItems,
|
|
357
|
-
documents: flattenedCandidates.map((candidate) => ({
|
|
358
|
-
id: candidate.id,
|
|
359
|
-
text: this.truncateCandidateText(
|
|
360
|
-
`${this.buildRerankerCandidateText(candidate)}\n\nScope: ${candidate.scopeTag}`,
|
|
361
|
-
),
|
|
362
|
-
})),
|
|
363
|
-
})
|
|
364
|
-
|
|
365
|
-
return this.buildRerankOutput(
|
|
366
|
-
reranked.results.map((item) => item.id),
|
|
367
|
-
'Top matches across memory scopes',
|
|
368
|
-
)
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
private async searchMemories({
|
|
372
|
-
query,
|
|
373
|
-
memory,
|
|
374
|
-
scopeId,
|
|
375
|
-
memoryType,
|
|
376
|
-
fastMode = true,
|
|
377
|
-
}: {
|
|
378
|
-
query: string
|
|
379
|
-
memory: Memory
|
|
380
|
-
scopeId: string
|
|
381
|
-
memoryType: MemoryType
|
|
382
|
-
fastMode?: boolean
|
|
383
|
-
}): Promise<string> {
|
|
384
|
-
const limit = getRuntimeConfig().memory.searchK
|
|
385
|
-
const candidateLimit = fastMode ? limit : getCandidateLimit(limit)
|
|
386
|
-
|
|
387
|
-
const candidates = await memory.searchCandidates(query, {
|
|
388
|
-
scopeId,
|
|
389
|
-
limit: candidateLimit,
|
|
390
|
-
memoryType,
|
|
391
|
-
fastMode,
|
|
392
|
-
includeNeighborContext: !fastMode,
|
|
393
|
-
})
|
|
394
|
-
|
|
395
|
-
aiLogger.debug`Memory search candidates (scopeId: ${scopeId}, candidates: ${candidates.length})`
|
|
396
|
-
|
|
397
|
-
if (candidates.length === 0) {
|
|
398
|
-
return 'No stored memories.'
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
if (fastMode || candidates.length <= limit) {
|
|
402
|
-
aiLogger.debug`Skipping reranking (candidates: ${candidates.length} <= limit: ${limit})`
|
|
403
|
-
return formatMemoryResults(candidates.slice(0, limit))
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
const reranked = await this.rerankCandidates(query, candidates, limit)
|
|
407
|
-
return formatRerankedResults(reranked, candidates, limit)
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
async searchOrganizationMemories(orgId: string, query: string): Promise<string> {
|
|
411
|
-
const orgScopeId = scopeId(ORG_SCOPE_PREFIX, orgId)
|
|
412
|
-
aiLogger.debug`Organization memory search requested (orgId: ${orgId}, scopeId: ${orgScopeId}, queryLength: ${query.length})`
|
|
413
|
-
const memory = this.getOrgMemory(orgId)
|
|
414
|
-
|
|
415
|
-
const results = await this.searchMemories({ query, memory, scopeId: orgScopeId, memoryType: ORG_MEMORY_TYPE })
|
|
416
|
-
aiLogger.debug`Organization memory search completed (resultLength: ${results.length}, preview: ${results.slice(0, 100)})`
|
|
417
|
-
return results
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
async getStaleMemories(orgId: string): Promise<string> {
|
|
421
|
-
const orgScopeId = scopeId(ORG_SCOPE_PREFIX, orgId)
|
|
422
|
-
const memory = this.getOrgMemory(orgId)
|
|
423
|
-
try {
|
|
424
|
-
const stale = await memory.getStaleMemories(orgScopeId)
|
|
425
|
-
if (stale.length === 0) return ''
|
|
426
|
-
const items = stale.map((m) => `- [NEEDS REVIEW] ${m.content}`).join('\n')
|
|
427
|
-
return `Memories flagged for review (parent fact was superseded):\n${items}`
|
|
428
|
-
} catch (error) {
|
|
429
|
-
aiLogger.warn`Failed to get stale memories: ${error}`
|
|
430
|
-
return ''
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
async searchOrganizationMemoriesRaw(
|
|
435
|
-
orgId: string,
|
|
436
|
-
query: string,
|
|
437
|
-
options?: { fastMode?: boolean; limit?: number },
|
|
438
|
-
): Promise<string> {
|
|
439
|
-
const orgScopeId = scopeId(ORG_SCOPE_PREFIX, orgId)
|
|
440
|
-
aiLogger.debug`searchOrganizationMemoriesRaw - orgId: "${orgId}", scopeId: "${orgScopeId}"`
|
|
441
|
-
const memory = this.getOrgMemory(orgId)
|
|
442
|
-
const fastMode = options?.fastMode ?? true
|
|
443
|
-
const searchK = getRuntimeConfig().memory.searchK
|
|
444
|
-
const limit = options?.limit ?? (fastMode ? Math.min(searchK, 4) : searchK)
|
|
445
|
-
|
|
446
|
-
const candidates = await memory.searchCandidates(query, {
|
|
447
|
-
scopeId: orgScopeId,
|
|
448
|
-
limit,
|
|
449
|
-
memoryType: ORG_MEMORY_TYPE,
|
|
450
|
-
fastMode,
|
|
451
|
-
includeNeighborContext: !fastMode,
|
|
452
|
-
})
|
|
453
|
-
aiLogger.debug`Organization memory search (raw) completed (candidates: ${candidates.length})`
|
|
454
|
-
return formatMemoryResults(candidates)
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
async searchAgentMemories(orgId: string, agentName: string, query: string): Promise<string> {
|
|
458
|
-
if (!isRoutableAgentName(agentName)) {
|
|
459
|
-
aiLogger.debug`Agent memory search skipped - invalid agentName: ${agentName}`
|
|
460
|
-
return 'No stored memories.'
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
const scoped = agentScopeId(orgId, agentName)
|
|
464
|
-
aiLogger.debug`Agent memory search requested (orgId: ${orgId}, agentName: ${agentName}, scopeId: ${scoped}, queryLength: ${query.length})`
|
|
465
|
-
const memory = this.getOrgMemory(orgId)
|
|
466
|
-
|
|
467
|
-
const results = await this.searchMemories({ query, memory, scopeId: scoped, memoryType: ORG_MEMORY_TYPE })
|
|
468
|
-
aiLogger.debug`Agent memory search completed (agentName: ${agentName}, resultLength: ${results.length}, preview: ${results.slice(0, 100)})`
|
|
469
|
-
return results
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
async searchOrgMemoriesForAgent(orgId: string, agentName: string, query: string): Promise<string> {
|
|
473
|
-
if (!isRoutableAgentName(agentName)) {
|
|
474
|
-
return this.searchOrganizationMemories(orgId, query)
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
const [agentResult, orgResult] = await Promise.all([
|
|
478
|
-
this.searchAgentMemories(orgId, agentName, query),
|
|
479
|
-
this.searchOrganizationMemories(orgId, query),
|
|
480
|
-
])
|
|
481
|
-
|
|
482
|
-
return `Agent memory (${agentName}):\n${agentResult}\n\nGlobal org memory:\n${orgResult}`
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
async listOrganizationMemoryRecords(params: {
|
|
486
|
-
orgId: string
|
|
487
|
-
limit?: number
|
|
488
|
-
memoryType?: MemoryType
|
|
489
|
-
metadataEquals?: Record<string, MemoryListScalar>
|
|
490
|
-
metadataNotEquals?: Record<string, MemoryListScalar>
|
|
491
|
-
sort?: 'createdAtAsc' | 'createdAtDesc'
|
|
492
|
-
}): Promise<MemoryRecord[]> {
|
|
493
|
-
const { orgId, ...listOptions } = params
|
|
494
|
-
const orgMemory = this.getOrgMemory(orgId)
|
|
495
|
-
return orgMemory.list({ scopeId: scopeId(ORG_SCOPE_PREFIX, orgId), ...listOptions })
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
async getTopMemories(params: { orgId: string; agentName?: string; limit?: number }): Promise<string | undefined> {
|
|
499
|
-
const orgMemory = this.getOrgMemory(params.orgId)
|
|
500
|
-
const orgScopeId = scopeId(ORG_SCOPE_PREFIX, params.orgId)
|
|
501
|
-
const requestedLimit = params.limit ?? PRESEEDED_MEMORY_LIMIT
|
|
502
|
-
const limit = Math.max(1, Math.min(requestedLimit, PRESEEDED_MEMORY_LIMIT))
|
|
503
|
-
|
|
504
|
-
const orgRequest = orgMemory.listTopMemories({
|
|
505
|
-
scopeId: orgScopeId,
|
|
506
|
-
limit,
|
|
507
|
-
memoryType: ORG_MEMORY_TYPE,
|
|
508
|
-
durability: PRESEEDED_MEMORY_DURABILITY,
|
|
509
|
-
minImportance: PRESEEDED_MIN_IMPORTANCE,
|
|
510
|
-
})
|
|
511
|
-
|
|
512
|
-
const agentRequest =
|
|
513
|
-
params.agentName && isRoutableAgentName(params.agentName)
|
|
514
|
-
? orgMemory.listTopMemories({
|
|
515
|
-
scopeId: agentScopeId(params.orgId, params.agentName),
|
|
516
|
-
limit,
|
|
517
|
-
memoryType: ORG_MEMORY_TYPE,
|
|
518
|
-
durability: PRESEEDED_MEMORY_DURABILITY,
|
|
519
|
-
minImportance: PRESEEDED_MIN_IMPORTANCE,
|
|
520
|
-
})
|
|
521
|
-
: Promise.resolve([] as MemoryRecord[])
|
|
522
|
-
|
|
523
|
-
const [orgTopMemories, agentTopMemories] = await Promise.all([orgRequest, agentRequest])
|
|
524
|
-
const combined = [...agentTopMemories, ...orgTopMemories].sort((left, right) => {
|
|
525
|
-
if (right.importance !== left.importance) return right.importance - left.importance
|
|
526
|
-
if (right.accessCount !== left.accessCount) return right.accessCount - left.accessCount
|
|
527
|
-
const rightLastAccess = right.lastAccessedAt?.getTime() ?? 0
|
|
528
|
-
const leftLastAccess = left.lastAccessedAt?.getTime() ?? 0
|
|
529
|
-
if (rightLastAccess !== leftLastAccess) return rightLastAccess - leftLastAccess
|
|
530
|
-
return right.createdAt.getTime() - left.createdAt.getTime()
|
|
531
|
-
})
|
|
532
|
-
|
|
533
|
-
const deduped: MemoryRecord[] = []
|
|
534
|
-
const seen = new Set<string>()
|
|
535
|
-
for (const memory of combined) {
|
|
536
|
-
const normalizedKey = compactWhitespace(memory.content).toLowerCase()
|
|
537
|
-
if (!normalizedKey || seen.has(normalizedKey)) continue
|
|
538
|
-
seen.add(normalizedKey)
|
|
539
|
-
deduped.push(memory)
|
|
540
|
-
if (deduped.length >= limit) break
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
return this.formatPreSeededMemoriesSection(deduped)
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
async searchAllMemoriesBatched({
|
|
547
|
-
orgId,
|
|
548
|
-
agentName,
|
|
549
|
-
query,
|
|
550
|
-
fastMode = true,
|
|
551
|
-
allowMultiScopeRerank = true,
|
|
552
|
-
}: {
|
|
553
|
-
orgId: string
|
|
554
|
-
agentName?: string
|
|
555
|
-
query: string
|
|
556
|
-
fastMode?: boolean
|
|
557
|
-
allowMultiScopeRerank?: boolean
|
|
558
|
-
}): Promise<string> {
|
|
559
|
-
const limit = Math.min(getRuntimeConfig().memory.searchK, MAX_MEMORY_RESULTS_PER_SCOPE)
|
|
560
|
-
const candidateLimit = fastMode ? limit : getCandidateLimit(limit)
|
|
561
|
-
const orgScopeId = scopeId(ORG_SCOPE_PREFIX, orgId)
|
|
562
|
-
const orgMemory = this.getOrgMemory(orgId)
|
|
563
|
-
|
|
564
|
-
const retrievalTasks = [
|
|
565
|
-
{
|
|
566
|
-
scopeTag: 'org',
|
|
567
|
-
retrieve: async () =>
|
|
568
|
-
await orgMemory.searchCandidates(query, {
|
|
569
|
-
scopeId: orgScopeId,
|
|
570
|
-
limit: candidateLimit,
|
|
571
|
-
memoryType: ORG_MEMORY_TYPE,
|
|
572
|
-
fastMode,
|
|
573
|
-
includeNeighborContext: !fastMode,
|
|
574
|
-
}),
|
|
575
|
-
},
|
|
576
|
-
]
|
|
577
|
-
|
|
578
|
-
if (isRoutableAgentName(agentName)) {
|
|
579
|
-
const agentScoped = agentScopeId(orgId, agentName)
|
|
580
|
-
retrievalTasks.push({
|
|
581
|
-
scopeTag: `agent:${agentName}`,
|
|
582
|
-
retrieve: async () =>
|
|
583
|
-
await orgMemory.searchCandidates(query, {
|
|
584
|
-
scopeId: agentScoped,
|
|
585
|
-
limit: candidateLimit,
|
|
586
|
-
memoryType: ORG_MEMORY_TYPE,
|
|
587
|
-
fastMode,
|
|
588
|
-
includeNeighborContext: !fastMode,
|
|
589
|
-
}),
|
|
590
|
-
})
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
const results = await executeScopedRetrieval<MemorySearchResult>(retrievalTasks)
|
|
594
|
-
|
|
595
|
-
const totalCandidates = countScopedRetrievalCandidates(results)
|
|
596
|
-
aiLogger.debug`Batched memory search candidates (scopes: ${results.length}, total: ${totalCandidates})`
|
|
597
|
-
|
|
598
|
-
if (totalCandidates === 0) {
|
|
599
|
-
return 'No stored memories.'
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
if (fastMode || !allowMultiScopeRerank) {
|
|
603
|
-
const candidatesByScopeTag = scopedRetrievalToMap(results)
|
|
604
|
-
return this.formatBatchedResults(null, candidatesByScopeTag, limit, agentName)
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
const reranked = await this.rerankCandidatesMultiScope(query, results, limit)
|
|
608
|
-
const candidatesByScopeTag = scopedRetrievalToMap(results)
|
|
609
|
-
|
|
610
|
-
return this.formatBatchedResults(reranked, candidatesByScopeTag, limit, agentName)
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
private formatBatchedResults(
|
|
614
|
-
reranked: MemoryRerankOutput | null,
|
|
615
|
-
candidatesByScopeTag: Map<string, MemorySearchResult[]>,
|
|
616
|
-
limit: number,
|
|
617
|
-
agentName?: string,
|
|
618
|
-
): string {
|
|
619
|
-
if (reranked && reranked.sections.length > 0) {
|
|
620
|
-
const allCandidates = Array.from(candidatesByScopeTag.values()).flat()
|
|
621
|
-
return formatRerankedResults(reranked, allCandidates, limit)
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
const sections: string[] = []
|
|
625
|
-
|
|
626
|
-
if (agentName) {
|
|
627
|
-
const agentCandidates = candidatesByScopeTag.get(`agent:${agentName}`) ?? []
|
|
628
|
-
if (agentCandidates.length > 0) {
|
|
629
|
-
sections.push(`Agent memory (${agentName}):\n${formatMemoryResults(agentCandidates.slice(0, limit))}`)
|
|
630
|
-
} else {
|
|
631
|
-
sections.push(`Agent memory (${agentName}):\nNo stored memories.`)
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
const orgCandidates = candidatesByScopeTag.get('org') ?? []
|
|
636
|
-
if (orgCandidates.length > 0) {
|
|
637
|
-
sections.push(
|
|
638
|
-
`${agentName ? 'Global org memory' : 'Organization memory'}:\n${formatMemoryResults(orgCandidates.slice(0, limit))}`,
|
|
639
|
-
)
|
|
640
|
-
} else {
|
|
641
|
-
sections.push(`${agentName ? 'Global org memory' : 'Organization memory'}:\nNo stored memories.`)
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
return sections.join('\n\n')
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
async createOrganizationMemory({
|
|
648
|
-
orgId,
|
|
649
|
-
content,
|
|
650
|
-
memoryType,
|
|
651
|
-
metadata,
|
|
652
|
-
importance,
|
|
653
|
-
durability,
|
|
654
|
-
}: {
|
|
655
|
-
orgId: string
|
|
656
|
-
content: string
|
|
657
|
-
memoryType: MemoryType
|
|
658
|
-
metadata?: Record<string, unknown>
|
|
659
|
-
importance?: number
|
|
660
|
-
durability?: MemoryRecord['durability']
|
|
661
|
-
}): Promise<string> {
|
|
662
|
-
const orgScopeId = scopeId(ORG_SCOPE_PREFIX, orgId)
|
|
663
|
-
aiLogger.debug`createOrganizationMemory - orgId: "${orgId}", scopeId: "${orgScopeId}", content preview: "${content.slice(0, 50)}"`
|
|
664
|
-
const memory = this.getOrgMemory(orgId)
|
|
665
|
-
try {
|
|
666
|
-
return await memory.insert(content, {
|
|
667
|
-
scopeId: orgScopeId,
|
|
668
|
-
memoryType,
|
|
669
|
-
importance: importance ?? 1,
|
|
670
|
-
durability,
|
|
671
|
-
metadata: { orgId, ...metadata },
|
|
672
|
-
})
|
|
673
|
-
} catch (error) {
|
|
674
|
-
if (isUniqueIndexConflict(error, 'memoryHashIdx')) {
|
|
675
|
-
aiLogger.debug`Organization memory already exists (hash conflict)`
|
|
676
|
-
return ''
|
|
677
|
-
}
|
|
678
|
-
throw error
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
async addOrganizationMemoryRelation({
|
|
683
|
-
orgId,
|
|
684
|
-
fromMemoryId,
|
|
685
|
-
toMemoryId,
|
|
686
|
-
relationType,
|
|
687
|
-
confidence,
|
|
688
|
-
}: {
|
|
689
|
-
orgId: string
|
|
690
|
-
fromMemoryId: string
|
|
691
|
-
toMemoryId: string
|
|
692
|
-
relationType: RelationType
|
|
693
|
-
confidence?: number
|
|
694
|
-
}): Promise<void> {
|
|
695
|
-
const memory = this.getOrgMemory(orgId)
|
|
696
|
-
await memory.addRelation(fromMemoryId, toMemoryId, relationType, confidence)
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
async createAgentMemory({
|
|
700
|
-
orgId,
|
|
701
|
-
agentName,
|
|
702
|
-
content,
|
|
703
|
-
memoryType,
|
|
704
|
-
metadata,
|
|
705
|
-
importance,
|
|
706
|
-
}: {
|
|
707
|
-
orgId: string
|
|
708
|
-
agentName: string
|
|
709
|
-
content: string
|
|
710
|
-
memoryType: MemoryType
|
|
711
|
-
metadata?: Record<string, unknown>
|
|
712
|
-
importance?: number
|
|
713
|
-
}): Promise<string> {
|
|
714
|
-
if (!isRoutableAgentName(agentName)) {
|
|
715
|
-
throw new Error(`Invalid agentName for agent memory: ${agentName as string}`)
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
const memory = this.getOrgMemory(orgId)
|
|
719
|
-
const scoped = agentScopeId(orgId, agentName)
|
|
720
|
-
try {
|
|
721
|
-
return await memory.insert(content, {
|
|
722
|
-
scopeId: scoped,
|
|
723
|
-
memoryType,
|
|
724
|
-
importance: importance ?? 1,
|
|
725
|
-
metadata: { orgId, agentName, memoryScope: 'agent', ...metadata },
|
|
726
|
-
})
|
|
727
|
-
} catch (error) {
|
|
728
|
-
if (isUniqueIndexConflict(error, 'memoryHashIdx')) {
|
|
729
|
-
aiLogger.debug`Agent memory already exists (hash conflict)`
|
|
730
|
-
return ''
|
|
731
|
-
}
|
|
732
|
-
throw error
|
|
733
|
-
}
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
async assessMemoryCandidate(params: {
|
|
737
|
-
orgId: string
|
|
738
|
-
content: string
|
|
739
|
-
}): Promise<Pick<ExtractedFact, 'classification' | 'durability' | 'importance' | 'rationale'> | null> {
|
|
740
|
-
const trimmed = compactWhitespace(params.content)
|
|
741
|
-
if (!trimmed) return null
|
|
742
|
-
|
|
743
|
-
const facts = await this.getOrgMemory(params.orgId).extractFactsFromMessages([{ role: 'user', content: trimmed }], {
|
|
744
|
-
maxFacts: 1,
|
|
745
|
-
customPrompt: DIRECT_MEMORY_ASSESSMENT_PROMPT,
|
|
746
|
-
})
|
|
747
|
-
if (facts.length === 0) return null
|
|
748
|
-
const fact = facts[0]
|
|
749
|
-
|
|
750
|
-
return {
|
|
751
|
-
classification: fact.classification,
|
|
752
|
-
durability: fact.durability,
|
|
753
|
-
importance: clampImportance(fact.importance),
|
|
754
|
-
rationale: fact.rationale,
|
|
755
|
-
}
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
private shouldSkipExtractedFacts(facts: ExtractedFact[]): boolean {
|
|
759
|
-
if (facts.length === 0) return true
|
|
760
|
-
return facts.every(
|
|
761
|
-
(fact) =>
|
|
762
|
-
fact.durability === 'ephemeral' &&
|
|
763
|
-
fact.importance <= LOW_VALUE_MEMORY_IMPORTANCE_THRESHOLD &&
|
|
764
|
-
fact.classification !== 'durable',
|
|
765
|
-
)
|
|
766
|
-
}
|
|
767
|
-
|
|
768
|
-
async updateOrganizationMemoryById({
|
|
769
|
-
orgId,
|
|
770
|
-
memoryId,
|
|
771
|
-
content,
|
|
772
|
-
}: {
|
|
773
|
-
orgId: string
|
|
774
|
-
memoryId: string
|
|
775
|
-
content: string
|
|
776
|
-
}): Promise<void> {
|
|
777
|
-
const memory = this.getOrgMemory(orgId)
|
|
778
|
-
await memory.updateMemory(memoryId, content)
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
async addExtractedFactsToScopes(params: {
|
|
782
|
-
orgId: string
|
|
783
|
-
facts: ExtractedFact[]
|
|
784
|
-
source: string
|
|
785
|
-
sourceMetadata?: Record<string, unknown>
|
|
786
|
-
agentNames?: string[]
|
|
787
|
-
acquireLock?: boolean
|
|
788
|
-
}): Promise<void> {
|
|
789
|
-
if (params.facts.length === 0) return
|
|
790
|
-
|
|
791
|
-
const orgMemory = this.getOrgMemory(params.orgId)
|
|
792
|
-
const scopes: AddOptions[] = [
|
|
793
|
-
{
|
|
794
|
-
scopeId: scopeId(ORG_SCOPE_PREFIX, params.orgId),
|
|
795
|
-
memoryType: ORG_MEMORY_TYPE,
|
|
796
|
-
metadata: { orgId: params.orgId, source: params.source, ...params.sourceMetadata },
|
|
797
|
-
},
|
|
798
|
-
]
|
|
799
|
-
|
|
800
|
-
for (const scopedAgentName of this.resolveAgentScopeNames(undefined, params.agentNames ?? [])) {
|
|
801
|
-
scopes.push({
|
|
802
|
-
scopeId: agentScopeId(params.orgId, scopedAgentName),
|
|
803
|
-
memoryType: ORG_MEMORY_TYPE,
|
|
804
|
-
metadata: {
|
|
805
|
-
orgId: params.orgId,
|
|
806
|
-
source: params.source,
|
|
807
|
-
...params.sourceMetadata,
|
|
808
|
-
agentName: scopedAgentName,
|
|
809
|
-
memoryScope: 'agent',
|
|
810
|
-
},
|
|
811
|
-
})
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
const preparedUpdates = await orgMemory.prepareFactsToScopes(params.facts, scopes)
|
|
815
|
-
if (preparedUpdates.length === 0) return
|
|
816
|
-
|
|
817
|
-
if (params.acquireLock === false) {
|
|
818
|
-
await orgMemory.applyPreparedScopeUpdates(preparedUpdates)
|
|
819
|
-
} else {
|
|
820
|
-
await withOrgMemoryLock(params.orgId, async () => {
|
|
821
|
-
await orgMemory.applyPreparedScopeUpdates(preparedUpdates)
|
|
822
|
-
})
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
async addConversationMemories({
|
|
827
|
-
orgId,
|
|
828
|
-
input,
|
|
829
|
-
output,
|
|
830
|
-
sourceId,
|
|
831
|
-
source = 'chat',
|
|
832
|
-
sourceMetadata,
|
|
833
|
-
onboardStatus,
|
|
834
|
-
agentName,
|
|
835
|
-
historyMessages = [],
|
|
836
|
-
memoryBlock,
|
|
837
|
-
attachmentContext,
|
|
838
|
-
agentNames = [],
|
|
839
|
-
}: {
|
|
840
|
-
orgId: string
|
|
841
|
-
input: string
|
|
842
|
-
output: string
|
|
843
|
-
sourceId?: string
|
|
844
|
-
source?: string
|
|
845
|
-
sourceMetadata?: Record<string, unknown>
|
|
846
|
-
onboardStatus?: string
|
|
847
|
-
agentName?: string
|
|
848
|
-
historyMessages?: Array<{ role: 'user' | 'agent'; content: string; agentName?: string }>
|
|
849
|
-
memoryBlock?: string
|
|
850
|
-
attachmentContext?: string
|
|
851
|
-
agentNames?: string[]
|
|
852
|
-
}): Promise<void> {
|
|
853
|
-
const { messages, normalizedInput, sanitizedOutput } = this.buildConversationMessages({
|
|
854
|
-
input,
|
|
855
|
-
output,
|
|
856
|
-
historyMessages,
|
|
857
|
-
memoryBlock,
|
|
858
|
-
attachmentContext,
|
|
859
|
-
})
|
|
860
|
-
|
|
861
|
-
if (!normalizedInput || !sanitizedOutput || messages.length === 0) {
|
|
862
|
-
aiLogger.debug`Skipping memory add - empty input or output`
|
|
863
|
-
return
|
|
864
|
-
}
|
|
865
|
-
|
|
866
|
-
const orgScopeId = scopeId(ORG_SCOPE_PREFIX, orgId)
|
|
867
|
-
aiLogger.debug`addConversationMemories - orgId: "${orgId}", scopeId: "${orgScopeId}", sourceId: ${sourceId ?? 'none'}`
|
|
868
|
-
|
|
869
|
-
const orgMemory = this.getOrgMemory(orgId)
|
|
870
|
-
|
|
871
|
-
const scopes: AddOptions[] = [
|
|
872
|
-
{
|
|
873
|
-
scopeId: orgScopeId,
|
|
874
|
-
memoryType: ORG_MEMORY_TYPE,
|
|
875
|
-
metadata: { orgId, source, ...(sourceId ? { sourceId } : {}), ...sourceMetadata },
|
|
876
|
-
},
|
|
877
|
-
]
|
|
878
|
-
|
|
879
|
-
for (const scopedAgentName of this.resolveAgentScopeNames(agentName, agentNames)) {
|
|
880
|
-
const agentId = agentScopeId(orgId, scopedAgentName)
|
|
881
|
-
scopes.push({
|
|
882
|
-
scopeId: agentId,
|
|
883
|
-
memoryType: ORG_MEMORY_TYPE,
|
|
884
|
-
metadata: {
|
|
885
|
-
orgId,
|
|
886
|
-
agentName: scopedAgentName,
|
|
887
|
-
memoryScope: 'agent',
|
|
888
|
-
source,
|
|
889
|
-
...(sourceId ? { sourceId } : {}),
|
|
890
|
-
...sourceMetadata,
|
|
891
|
-
},
|
|
892
|
-
})
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
const onboardingActive = onboardStatus !== undefined && onboardStatus !== 'completed'
|
|
896
|
-
const extractionConfig = onboardingActive
|
|
897
|
-
? { maxFacts: ONBOARDING_MEMORY_MAX_FACTS, customPrompt: ONBOARDING_MEMORY_EXTRACTION_PROMPT }
|
|
898
|
-
: undefined
|
|
899
|
-
const extractedFacts = await orgMemory.extractFactsFromMessages(messages, extractionConfig)
|
|
900
|
-
if (this.shouldSkipExtractedFacts(extractedFacts)) {
|
|
901
|
-
aiLogger.debug`Skipping transient conversation memory`
|
|
902
|
-
return
|
|
903
|
-
}
|
|
904
|
-
const preparedUpdates = await orgMemory.prepareFactsToScopes(extractedFacts, scopes)
|
|
905
|
-
if (preparedUpdates.length === 0) return
|
|
906
|
-
|
|
907
|
-
await withOrgMemoryLock(orgId, async () => {
|
|
908
|
-
await orgMemory.applyPreparedScopeUpdates(preparedUpdates)
|
|
909
|
-
})
|
|
910
|
-
aiLogger.debug`Conversation memories added to ${scopes.length} scope(s) from ${messages.length} message(s)`
|
|
911
|
-
}
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
export const memoryService = new MemoryService()
|