@lota-sdk/core 0.1.15 → 0.1.17
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/infrastructure/schema/00_identity.surql +0 -2
- package/infrastructure/schema/01_memory.surql +1 -1
- package/infrastructure/schema/02_execution_plan.surql +62 -1
- package/infrastructure/schema/03_learned_skill.surql +1 -1
- package/infrastructure/schema/06_playbook.surql +25 -0
- package/infrastructure/schema/07_institutional_memory.surql +13 -0
- package/infrastructure/schema/08_quality_metrics.surql +17 -0
- package/package.json +12 -8
- package/src/ai/definitions.ts +81 -3
- package/src/ai/embedding-cache.ts +2 -4
- package/src/ai/index.ts +0 -2
- package/src/bifrost/bifrost.ts +2 -7
- package/src/bifrost/cache-headers.ts +8 -0
- package/src/bifrost/index.ts +1 -0
- package/src/config/agent-defaults.ts +31 -21
- package/src/config/agent-types.ts +11 -0
- package/src/config/constants.ts +2 -14
- package/src/config/debug-logger.ts +5 -1
- package/src/config/index.ts +3 -0
- package/src/config/model-constants.ts +16 -34
- package/src/config/search.ts +1 -15
- package/src/create-runtime.ts +269 -178
- package/src/db/cursor-pagination.ts +3 -6
- package/src/db/index.ts +2 -0
- package/src/db/memory-store.helpers.ts +1 -3
- package/src/db/memory-store.rows.ts +7 -7
- package/src/db/memory-store.ts +14 -18
- package/src/db/memory.ts +13 -13
- package/src/db/schema-fingerprint.ts +1 -3
- package/src/db/service.ts +153 -79
- package/src/db/startup.ts +6 -10
- package/src/db/surreal-mutation.ts +43 -0
- package/src/db/tables.ts +7 -0
- package/src/db/workstream-message-row.ts +15 -0
- package/src/embeddings/provider.ts +1 -1
- package/src/queues/context-compaction.queue.ts +15 -46
- package/src/queues/delayed-node-promotion.queue.ts +41 -0
- package/src/queues/document-processor.queue.ts +2 -4
- package/src/queues/index.ts +3 -0
- package/src/queues/memory-consolidation.queue.ts +16 -51
- package/src/queues/plan-scheduler.queue.ts +97 -0
- package/src/queues/post-chat-memory.queue.ts +20 -55
- package/src/queues/queue-factory.ts +100 -0
- package/src/queues/recent-activity-title-refinement.queue.ts +15 -50
- package/src/queues/regular-chat-memory-digest.queue.ts +16 -52
- package/src/queues/skill-extraction.queue.ts +15 -47
- package/src/queues/workstream-title-generation.queue.ts +15 -47
- package/src/redis/connection.ts +6 -0
- package/src/redis/index.ts +1 -1
- package/src/redis/redis-lease-lock.ts +1 -2
- package/src/redis/stream-context.ts +11 -0
- package/src/runtime/agent-runtime-policy.ts +109 -35
- package/src/runtime/approval-continuation.ts +12 -6
- package/src/runtime/context-compaction-runtime.ts +1 -1
- package/src/runtime/context-compaction.ts +24 -64
- package/src/runtime/execution-plan.ts +22 -18
- package/src/runtime/graph-designer.ts +15 -0
- package/src/runtime/helper-model.ts +9 -197
- package/src/runtime/index.ts +3 -1
- package/src/runtime/llm-content.ts +1 -1
- package/src/runtime/memory-block.ts +9 -11
- package/src/runtime/memory-pipeline.ts +6 -9
- package/src/runtime/plugin-resolution.ts +35 -0
- package/src/runtime/plugin-types.ts +72 -0
- package/src/runtime/retrieval-adapters.ts +1 -1
- package/src/runtime/runtime-config.ts +111 -14
- package/src/runtime/runtime-extensions.ts +2 -3
- package/src/runtime/runtime-worker-registry.ts +6 -0
- package/src/runtime/social-chat.ts +752 -0
- package/src/runtime/team-consultation-orchestrator.ts +45 -32
- package/src/runtime/team-consultation-prompts.ts +11 -2
- package/src/runtime/title-helpers.ts +2 -4
- package/src/runtime/workstream-chat-helpers.ts +1 -1
- package/src/services/adaptive-playbook.service.ts +152 -0
- package/src/services/agent-executor.service.ts +292 -0
- package/src/services/artifact-provenance.service.ts +172 -0
- package/src/services/attachment.service.ts +6 -11
- package/src/services/context-compaction.service.ts +72 -55
- package/src/services/context-enrichment.service.ts +33 -0
- package/src/services/coordination-registry.service.ts +117 -0
- package/src/services/document-chunk.service.ts +2 -4
- package/src/services/domain-agent-executor.service.ts +71 -0
- package/src/services/execution-plan.service.ts +269 -50
- package/src/services/feedback-loop.service.ts +96 -0
- package/src/services/global-orchestrator.service.ts +148 -0
- package/src/services/index.ts +27 -0
- package/src/services/institutional-memory.service.ts +145 -0
- package/src/services/learned-skill.service.ts +24 -5
- package/src/services/memory-assessment.service.ts +3 -2
- package/src/services/memory-utils.ts +3 -8
- package/src/services/memory.service.ts +49 -61
- package/src/services/monitoring-window.service.ts +86 -0
- package/src/services/mutating-approval.service.ts +1 -1
- package/src/services/node-workspace.service.ts +155 -0
- package/src/services/notification.service.ts +39 -0
- package/src/services/organization-member.service.ts +11 -4
- package/src/services/organization.service.ts +5 -5
- package/src/services/ownership-dispatcher.service.ts +403 -0
- package/src/services/plan-approval.service.ts +1 -1
- package/src/services/plan-builder.service.ts +1 -0
- package/src/services/plan-checkpoint.service.ts +30 -2
- package/src/services/plan-compiler.service.ts +5 -0
- package/src/services/plan-coordination.service.ts +152 -0
- package/src/services/plan-cycle.service.ts +284 -0
- package/src/services/plan-deadline.service.ts +287 -0
- package/src/services/plan-executor.service.ts +384 -40
- package/src/services/plan-run.service.ts +41 -7
- package/src/services/plan-scheduler.service.ts +240 -0
- package/src/services/plan-template.service.ts +117 -0
- package/src/services/plan-validator.service.ts +84 -2
- package/src/services/plan-workspace.service.ts +83 -0
- package/src/services/playbook-registry.service.ts +67 -0
- package/src/services/plugin-executor.service.ts +103 -0
- package/src/services/quality-metrics.service.ts +132 -0
- package/src/services/recent-activity.service.ts +28 -34
- package/src/services/skill-resolver.service.ts +19 -0
- package/src/services/social-chat-history.service.ts +197 -0
- package/src/services/system-executor.service.ts +105 -0
- package/src/services/workstream-message.service.ts +13 -37
- package/src/services/workstream-plan-registry.service.ts +22 -0
- package/src/services/workstream-title.service.ts +3 -1
- package/src/services/workstream-turn-preparation.service.ts +34 -89
- package/src/services/workstream.service.ts +33 -55
- package/src/services/workstream.types.ts +9 -9
- package/src/services/write-intent-validator.service.ts +81 -0
- package/src/storage/attachment-parser.ts +1 -1
- package/src/storage/attachment-utils.ts +1 -1
- package/src/storage/generated-document-storage.service.ts +3 -2
- package/src/system-agents/context-compaction.agent.ts +2 -0
- package/src/system-agents/delegated-agent-factory.ts +5 -0
- package/src/system-agents/memory-reranker.agent.ts +4 -2
- package/src/system-agents/memory.agent.ts +2 -0
- package/src/system-agents/recent-activity-title-refiner.agent.ts +2 -0
- package/src/system-agents/regular-chat-memory-digest.agent.ts +2 -0
- package/src/system-agents/skill-extractor.agent.ts +2 -0
- package/src/system-agents/skill-manager.agent.ts +2 -0
- package/src/system-agents/title-generator.agent.ts +2 -0
- package/src/tools/execution-plan.tool.ts +17 -23
- package/src/tools/index.ts +0 -1
- package/src/tools/research-topic.tool.ts +2 -0
- package/src/tools/team-think.tool.ts +5 -6
- package/src/utils/async.ts +2 -1
- package/src/utils/date-time.ts +4 -32
- package/src/utils/env.ts +8 -0
- package/src/utils/errors.ts +42 -10
- package/src/utils/index.ts +9 -0
- package/src/utils/string.ts +114 -1
- package/src/workers/index.ts +1 -0
- package/src/workers/regular-chat-memory-digest.helpers.ts +1 -1
- package/src/workers/regular-chat-memory-digest.runner.ts +45 -12
- package/src/workers/skill-extraction.runner.ts +26 -6
- package/src/workers/utils/file-section-chunker.ts +2 -1
- package/src/workers/utils/repo-structure-extractor.ts +2 -2
- package/src/workers/utils/repomix-file-sections.ts +2 -2
- package/src/workers/utils/sandbox-error.ts +11 -2
- package/src/workers/utils/workstream-message-query.ts +14 -25
- package/src/workers/worker-utils.ts +2 -2
- package/src/runtime/workstream-routing-policy.ts +0 -267
- package/src/tools/log-hello-world.tool.ts +0 -17
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import type { ConvergenceState, PlanFailureClass } from '@lota-sdk/shared'
|
|
2
|
+
|
|
3
|
+
import { serverLogger } from '../config/logger'
|
|
4
|
+
import { recordIdToString } from '../db/record-id'
|
|
5
|
+
import { TABLES } from '../db/tables'
|
|
6
|
+
|
|
7
|
+
function classifyDispatchFailure(ownerType: string, error: unknown): PlanFailureClass {
|
|
8
|
+
const errorMessage = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase()
|
|
9
|
+
if (errorMessage.includes('timeout')) return 'timeout_exceeded'
|
|
10
|
+
if (ownerType === 'plugin' || ownerType === 'system') return 'external_system_unavailable'
|
|
11
|
+
return 'non_recoverable_logic_error'
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function formatDispatchError(error: unknown): string {
|
|
15
|
+
return error instanceof Error ? error.message : String(error)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const STABLE_RUN_STATUSES = new Set(['awaiting-human', 'blocked', 'failed', 'completed', 'aborted'])
|
|
19
|
+
|
|
20
|
+
class GlobalOrchestratorService {
|
|
21
|
+
detectConvergence(params: {
|
|
22
|
+
totalNodes: number
|
|
23
|
+
completedNodes: number
|
|
24
|
+
failedNodes: number
|
|
25
|
+
previousCompletedNodes?: number
|
|
26
|
+
previousFailedNodes?: number
|
|
27
|
+
}): ConvergenceState {
|
|
28
|
+
const completionRatio = params.totalNodes > 0 ? params.completedNodes / params.totalNodes : 0
|
|
29
|
+
const failureRatio = params.totalNodes > 0 ? params.failedNodes / params.totalNodes : 0
|
|
30
|
+
|
|
31
|
+
if (params.previousCompletedNodes !== undefined) {
|
|
32
|
+
const completionVelocity = params.completedNodes - params.previousCompletedNodes
|
|
33
|
+
const failureVelocity = params.failedNodes - (params.previousFailedNodes ?? 0)
|
|
34
|
+
|
|
35
|
+
if (completionVelocity > 0 && failureVelocity === 0) return 'converging'
|
|
36
|
+
if (completionVelocity === 0 && failureVelocity === 0) return 'stalled'
|
|
37
|
+
if (failureVelocity > 0) return 'diverging'
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (completionRatio > 0.5 && failureRatio === 0) return 'converging'
|
|
41
|
+
if (failureRatio > 0.3) return 'diverging'
|
|
42
|
+
return 'progressing'
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
decideRerouteAction(params: {
|
|
46
|
+
failedNodeId: string
|
|
47
|
+
retryCount: number
|
|
48
|
+
maxRetries: number
|
|
49
|
+
convergenceState: ConvergenceState
|
|
50
|
+
}): 'retry' | 'skip' | 'abort' {
|
|
51
|
+
if (params.retryCount < params.maxRetries) return 'retry'
|
|
52
|
+
if (params.convergenceState === 'converging') return 'skip'
|
|
53
|
+
if (params.convergenceState === 'diverging') return 'abort'
|
|
54
|
+
return 'skip'
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async routeGraphFull(params: { workstreamId: string; runId: string }): Promise<void> {
|
|
58
|
+
const MAX_ROUNDS = 32
|
|
59
|
+
const STRUCTURAL_TYPES = new Set(['switch', 'join', 'deliberation-fork'])
|
|
60
|
+
|
|
61
|
+
// Dynamic imports to avoid circular dependencies
|
|
62
|
+
const { planRunService } = await import('./plan-run.service')
|
|
63
|
+
const { ownershipDispatcherService } = await import('./ownership-dispatcher.service')
|
|
64
|
+
const { planExecutorService } = await import('./plan-executor.service')
|
|
65
|
+
|
|
66
|
+
let round = 0
|
|
67
|
+
for (; round < MAX_ROUNDS; round++) {
|
|
68
|
+
const run = await planRunService.getRunById(params.runId)
|
|
69
|
+
if (STABLE_RUN_STATUSES.has(run.status)) break
|
|
70
|
+
|
|
71
|
+
const spec = await planRunService.getPlanSpecById(run.planSpecId)
|
|
72
|
+
const nodeSpecs = await planRunService.listNodeSpecs(spec.id)
|
|
73
|
+
const nodeRuns = await planRunService.listNodeRuns(run.id)
|
|
74
|
+
|
|
75
|
+
// Find ready action nodes (not structural, not human — those are handled by syncRunGraph)
|
|
76
|
+
const readyNodes = nodeRuns.filter((nr) => {
|
|
77
|
+
if (nr.status !== 'ready') return false
|
|
78
|
+
const ns = nodeSpecs.find((s) => s.nodeId === nr.nodeId)
|
|
79
|
+
return ns && ns.owner.executorType !== 'user' && !STRUCTURAL_TYPES.has(ns.type)
|
|
80
|
+
})
|
|
81
|
+
if (readyNodes.length === 0) break
|
|
82
|
+
|
|
83
|
+
// Transition all ready nodes to 'running' BEFORE dispatching
|
|
84
|
+
for (const nodeRun of readyNodes) {
|
|
85
|
+
await planExecutorService.transitionNodeToRunning({ runId: params.runId, nodeId: nodeRun.nodeId })
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Re-fetch run after transitions for accurate state in dispatch context
|
|
89
|
+
const updatedRun = await planRunService.getRunById(params.runId)
|
|
90
|
+
|
|
91
|
+
// Dispatch all in parallel with LINEAR mode override (prevents recursion)
|
|
92
|
+
const results = await Promise.allSettled(
|
|
93
|
+
readyNodes.map(async (nodeRun) => {
|
|
94
|
+
const nodeSpecRecord = nodeSpecs.find((ns) => ns.nodeId === nodeRun.nodeId)
|
|
95
|
+
if (!nodeSpecRecord) {
|
|
96
|
+
throw new Error(`Node spec not found for node "${nodeRun.nodeId}".`)
|
|
97
|
+
}
|
|
98
|
+
// Re-fetch the node run to get the updated 'running' state with resolvedInput
|
|
99
|
+
const updatedNodeRun = await planRunService.getNodeRunByNodeId(updatedRun.id, nodeRun.nodeId)
|
|
100
|
+
const result = await ownershipDispatcherService.dispatchReadyNode({
|
|
101
|
+
run: updatedRun,
|
|
102
|
+
nodeSpecRecord,
|
|
103
|
+
nodeRun: updatedNodeRun,
|
|
104
|
+
spec,
|
|
105
|
+
executionModeOverride: 'linear',
|
|
106
|
+
})
|
|
107
|
+
return { nodeId: nodeRun.nodeId, ownerRef: nodeSpecRecord.owner.ref, result }
|
|
108
|
+
}),
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
const workstreamId = recordIdToString(updatedRun.workstreamId, TABLES.WORKSTREAM)
|
|
112
|
+
const runId = recordIdToString(updatedRun.id, TABLES.PLAN_RUN)
|
|
113
|
+
|
|
114
|
+
// Submit results sequentially (each triggers syncRunGraph internally)
|
|
115
|
+
for (let i = 0; i < results.length; i++) {
|
|
116
|
+
const settled = results[i]
|
|
117
|
+
const nodeRun = readyNodes[i]
|
|
118
|
+
const nodeSpecRecord = nodeSpecs.find((ns) => ns.nodeId === nodeRun.nodeId)
|
|
119
|
+
|
|
120
|
+
if (settled.status === 'fulfilled') {
|
|
121
|
+
await planExecutorService.submitNodeResult({
|
|
122
|
+
workstreamId,
|
|
123
|
+
runId,
|
|
124
|
+
nodeId: settled.value.nodeId,
|
|
125
|
+
emittedBy: settled.value.ownerRef,
|
|
126
|
+
result: settled.value.result,
|
|
127
|
+
})
|
|
128
|
+
} else {
|
|
129
|
+
serverLogger.warn`routeGraphFull: dispatch failed for node "${nodeRun.nodeId}": ${settled.reason}`
|
|
130
|
+
await planExecutorService.blockNodeOnDispatchFailure({
|
|
131
|
+
workstreamId,
|
|
132
|
+
runId,
|
|
133
|
+
nodeId: nodeRun.nodeId,
|
|
134
|
+
emittedBy: nodeSpecRecord?.owner.ref ?? 'unknown',
|
|
135
|
+
message: formatDispatchError(settled.reason),
|
|
136
|
+
failureClass: classifyDispatchFailure(nodeSpecRecord?.owner.executorType ?? 'agent', settled.reason),
|
|
137
|
+
})
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (round === MAX_ROUNDS - 1) {
|
|
143
|
+
serverLogger.warn`graph-full execution reached max rounds (${MAX_ROUNDS}) for run ${params.runId} — possible non-converging graph`
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export const globalOrchestratorService = new GlobalOrchestratorService()
|
package/src/services/index.ts
CHANGED
|
@@ -1,14 +1,41 @@
|
|
|
1
|
+
export * from './adaptive-playbook.service'
|
|
2
|
+
export * from './agent-executor.service'
|
|
3
|
+
export * from './artifact-provenance.service'
|
|
1
4
|
export * from './attachment.service'
|
|
5
|
+
export * from './context-enrichment.service'
|
|
6
|
+
export * from './coordination-registry.service'
|
|
2
7
|
export * from './document-chunk.service'
|
|
8
|
+
export * from './domain-agent-executor.service'
|
|
3
9
|
export * from './execution-plan.service'
|
|
10
|
+
export * from './institutional-memory.service'
|
|
11
|
+
export * from './feedback-loop.service'
|
|
12
|
+
export * from './global-orchestrator.service'
|
|
4
13
|
export * from './memory.service'
|
|
14
|
+
export * from './node-workspace.service'
|
|
15
|
+
export * from './notification.service'
|
|
16
|
+
export * from './ownership-dispatcher.service'
|
|
5
17
|
export * from './organization-member.service'
|
|
6
18
|
export * from './organization.service'
|
|
19
|
+
export * from './plan-coordination.service'
|
|
20
|
+
export * from './plan-cycle.service'
|
|
21
|
+
export * from './plan-deadline.service'
|
|
22
|
+
export * from './plan-workspace.service'
|
|
7
23
|
export * from './plan-run.service'
|
|
24
|
+
export * from './plan-scheduler.service'
|
|
25
|
+
export * from './plan-template.service'
|
|
26
|
+
export * from './playbook-registry.service'
|
|
27
|
+
export * from './quality-metrics.service'
|
|
28
|
+
export * from './monitoring-window.service'
|
|
29
|
+
export * from './plugin-executor.service'
|
|
8
30
|
export * from './recent-activity-title.service'
|
|
9
31
|
export * from './recent-activity.service'
|
|
32
|
+
export * from './skill-resolver.service'
|
|
33
|
+
export * from './social-chat-history.service'
|
|
34
|
+
export * from './system-executor.service'
|
|
10
35
|
export * from './user.service'
|
|
11
36
|
export * from './workstream-message.service'
|
|
12
37
|
export * from './workstream-title.service'
|
|
13
38
|
export * from './workstream-turn'
|
|
39
|
+
export * from './workstream-plan-registry.service'
|
|
14
40
|
export * from './workstream.service'
|
|
41
|
+
export * from './write-intent-validator.service'
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import type { InstitutionalMemory, InstitutionalMemoryType } from '@lota-sdk/shared'
|
|
2
|
+
import { InstitutionalMemorySchema } from '@lota-sdk/shared'
|
|
3
|
+
import { BoundQuery } from 'surrealdb'
|
|
4
|
+
|
|
5
|
+
import { ensureRecordId } from '../db/record-id'
|
|
6
|
+
import { databaseService } from '../db/service'
|
|
7
|
+
import { TABLES } from '../db/tables'
|
|
8
|
+
|
|
9
|
+
class InstitutionalMemoryService {
|
|
10
|
+
async extractPatterns(params: { organizationId: string; runId: string }): Promise<InstitutionalMemory[]> {
|
|
11
|
+
const { planRunService } = await import('./plan-run.service')
|
|
12
|
+
|
|
13
|
+
const run = await planRunService.getRunById(params.runId)
|
|
14
|
+
const nodeRuns = await planRunService.listNodeRuns(run.id)
|
|
15
|
+
const nodeSpecs = await planRunService.listNodeSpecs(run.planSpecId)
|
|
16
|
+
const nodeSpecsByNodeId = new Map(nodeSpecs.map((n) => [n.nodeId, n]))
|
|
17
|
+
|
|
18
|
+
const patterns: InstitutionalMemory[] = []
|
|
19
|
+
const orgRef = ensureRecordId(params.organizationId, TABLES.ORGANIZATION)
|
|
20
|
+
const ownerRef = (nodeId: string): string => nodeSpecsByNodeId.get(nodeId)?.owner.ref ?? 'unknown'
|
|
21
|
+
|
|
22
|
+
const completedNodes = nodeRuns.filter((nr) => nr.status === 'completed' && nr.startedAt && nr.completedAt)
|
|
23
|
+
if (completedNodes.length > 0) {
|
|
24
|
+
const timings = completedNodes.map((nr) => ({
|
|
25
|
+
nodeId: nr.nodeId,
|
|
26
|
+
owner: ownerRef(nr.nodeId),
|
|
27
|
+
durationMs: new Date(nr.completedAt ?? 0).getTime() - new Date(nr.startedAt ?? 0).getTime(),
|
|
28
|
+
}))
|
|
29
|
+
|
|
30
|
+
const record = await this.persistPattern({
|
|
31
|
+
organizationId: orgRef,
|
|
32
|
+
type: 'timing-pattern',
|
|
33
|
+
pattern: { runId: params.runId, timings },
|
|
34
|
+
confidence: 0.7,
|
|
35
|
+
sampleCount: completedNodes.length,
|
|
36
|
+
})
|
|
37
|
+
patterns.push(record)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const failedNodes = nodeRuns.filter((nr) => nr.status === 'failed')
|
|
41
|
+
if (failedNodes.length > 0) {
|
|
42
|
+
const failures = failedNodes.map((nr) => ({
|
|
43
|
+
nodeId: nr.nodeId,
|
|
44
|
+
owner: ownerRef(nr.nodeId),
|
|
45
|
+
attemptCount: nr.attemptCount,
|
|
46
|
+
}))
|
|
47
|
+
|
|
48
|
+
const record = await this.persistPattern({
|
|
49
|
+
organizationId: orgRef,
|
|
50
|
+
type: 'failure-pattern',
|
|
51
|
+
pattern: { runId: params.runId, failures },
|
|
52
|
+
confidence: 0.6,
|
|
53
|
+
sampleCount: failedNodes.length,
|
|
54
|
+
})
|
|
55
|
+
patterns.push(record)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const agentCompletions = new Map<string, number>()
|
|
59
|
+
for (const nr of completedNodes) {
|
|
60
|
+
const owner = ownerRef(nr.nodeId)
|
|
61
|
+
agentCompletions.set(owner, (agentCompletions.get(owner) ?? 0) + 1)
|
|
62
|
+
}
|
|
63
|
+
if (agentCompletions.size > 0) {
|
|
64
|
+
const record = await this.persistPattern({
|
|
65
|
+
organizationId: orgRef,
|
|
66
|
+
type: 'agent-affinity',
|
|
67
|
+
pattern: { runId: params.runId, completions: Object.fromEntries(agentCompletions) },
|
|
68
|
+
confidence: 0.5,
|
|
69
|
+
sampleCount: completedNodes.length,
|
|
70
|
+
})
|
|
71
|
+
patterns.push(record)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return patterns
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async queryRelevant(params: {
|
|
78
|
+
organizationId: string
|
|
79
|
+
objective: string
|
|
80
|
+
limit?: number
|
|
81
|
+
}): Promise<InstitutionalMemory[]> {
|
|
82
|
+
const orgRef = ensureRecordId(params.organizationId, TABLES.ORGANIZATION)
|
|
83
|
+
const limit = params.limit ?? 10
|
|
84
|
+
|
|
85
|
+
// Fetch a broader set, then rank by relevance to the objective.
|
|
86
|
+
// SurrealDB does not support full-text scoring on FLEXIBLE object fields,
|
|
87
|
+
// so we over-fetch and rank in application code.
|
|
88
|
+
const fetchLimit = Math.max(limit * 3, 30)
|
|
89
|
+
const records = await databaseService.queryMany(
|
|
90
|
+
new BoundQuery(
|
|
91
|
+
`SELECT * FROM ${TABLES.INSTITUTIONAL_MEMORY}
|
|
92
|
+
WHERE organizationId = $orgId
|
|
93
|
+
ORDER BY confidence DESC, createdAt DESC
|
|
94
|
+
LIMIT $fetchLimit`,
|
|
95
|
+
{ orgId: orgRef, fetchLimit },
|
|
96
|
+
),
|
|
97
|
+
InstitutionalMemorySchema,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
// Score each record by how many objective keywords appear in its pattern JSON
|
|
101
|
+
const objectiveTerms = params.objective
|
|
102
|
+
.toLowerCase()
|
|
103
|
+
.split(/\s+/)
|
|
104
|
+
.filter((t) => t.length > 2)
|
|
105
|
+
|
|
106
|
+
if (objectiveTerms.length === 0) return records.slice(0, limit)
|
|
107
|
+
|
|
108
|
+
const scored = records.map((record) => {
|
|
109
|
+
const patternStr = JSON.stringify(record.pattern).toLowerCase()
|
|
110
|
+
const matchCount = objectiveTerms.filter((term) => patternStr.includes(term)).length
|
|
111
|
+
return { record, relevance: matchCount / objectiveTerms.length }
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
scored.sort((a, b) => {
|
|
115
|
+
// Primary: relevance score, secondary: confidence, tertiary: recency
|
|
116
|
+
if (b.relevance !== a.relevance) return b.relevance - a.relevance
|
|
117
|
+
if (b.record.confidence !== a.record.confidence) return b.record.confidence - a.record.confidence
|
|
118
|
+
return 0
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
return scored.slice(0, limit).map((s) => s.record)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
private async persistPattern(params: {
|
|
125
|
+
organizationId: ReturnType<typeof ensureRecordId>
|
|
126
|
+
type: InstitutionalMemoryType
|
|
127
|
+
pattern: Record<string, unknown>
|
|
128
|
+
confidence: number
|
|
129
|
+
sampleCount: number
|
|
130
|
+
}): Promise<InstitutionalMemory> {
|
|
131
|
+
return databaseService.create(
|
|
132
|
+
TABLES.INSTITUTIONAL_MEMORY,
|
|
133
|
+
{
|
|
134
|
+
organizationId: params.organizationId,
|
|
135
|
+
type: params.type,
|
|
136
|
+
pattern: params.pattern,
|
|
137
|
+
confidence: params.confidence,
|
|
138
|
+
sampleCount: params.sampleCount,
|
|
139
|
+
},
|
|
140
|
+
InstitutionalMemorySchema,
|
|
141
|
+
)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export const institutionalMemoryService = new InstitutionalMemoryService()
|
|
@@ -5,7 +5,7 @@ import { z } from 'zod'
|
|
|
5
5
|
import { renderLearnedSkillInstructions } from '../ai/definitions'
|
|
6
6
|
import { lotaDebugLogger } from '../config/debug-logger'
|
|
7
7
|
import { serverLogger } from '../config/logger'
|
|
8
|
-
import { ensureRecordId } from '../db/record-id'
|
|
8
|
+
import { ensureRecordId, recordIdToString } from '../db/record-id'
|
|
9
9
|
import { databaseService } from '../db/service'
|
|
10
10
|
import { TABLES } from '../db/tables'
|
|
11
11
|
import { getDefaultEmbeddings } from '../embeddings/provider'
|
|
@@ -124,7 +124,7 @@ class LearnedSkillService {
|
|
|
124
124
|
|
|
125
125
|
async update(skillId: string, input: UpdateLearnedSkillInput): Promise<LearnedSkillRow> {
|
|
126
126
|
const ref = ensureRecordId(skillId, TABLES.LEARNED_SKILL)
|
|
127
|
-
const data: Record<string, unknown> = {
|
|
127
|
+
const data: Record<string, unknown> = {}
|
|
128
128
|
|
|
129
129
|
if (input.name !== undefined) data.name = input.name
|
|
130
130
|
if (input.description !== undefined) data.description = input.description
|
|
@@ -149,11 +149,11 @@ class LearnedSkillService {
|
|
|
149
149
|
await databaseService.update(
|
|
150
150
|
TABLES.LEARNED_SKILL,
|
|
151
151
|
ref,
|
|
152
|
-
{ status: 'archived', archivedAt: new Date()
|
|
152
|
+
{ status: 'archived', archivedAt: new Date() },
|
|
153
153
|
LearnedSkillRowSchema,
|
|
154
154
|
)
|
|
155
155
|
if (skill) {
|
|
156
|
-
await this.invalidateSkillExistsCache(
|
|
156
|
+
await this.invalidateSkillExistsCache(recordIdToString(skill.organizationId), skill.agentId ?? null)
|
|
157
157
|
}
|
|
158
158
|
}
|
|
159
159
|
|
|
@@ -292,7 +292,7 @@ class LearnedSkillService {
|
|
|
292
292
|
await databaseService.update(
|
|
293
293
|
TABLES.LEARNED_SKILL,
|
|
294
294
|
ref,
|
|
295
|
-
{ status: 'verified', confidence: Math.min(skill.confidence + 0.1, 1.0)
|
|
295
|
+
{ status: 'verified', confidence: Math.min(skill.confidence + 0.1, 1.0) },
|
|
296
296
|
LearnedSkillRowSchema,
|
|
297
297
|
)
|
|
298
298
|
return true
|
|
@@ -346,6 +346,25 @@ class LearnedSkillService {
|
|
|
346
346
|
return hasher.digest('hex')
|
|
347
347
|
}
|
|
348
348
|
|
|
349
|
+
async findByNameOrTag(orgId: string, nameOrTag: string): Promise<LearnedSkillRow | null> {
|
|
350
|
+
const orgRef = ensureRecordId(orgId, TABLES.ORGANIZATION)
|
|
351
|
+
const normalizedRef = nameOrTag.trim().toLowerCase()
|
|
352
|
+
const rows = await databaseService.queryMany(
|
|
353
|
+
new BoundQuery(
|
|
354
|
+
`SELECT *, type::string(id) AS id, type::string(organizationId) AS organizationId
|
|
355
|
+
FROM ${TABLES.LEARNED_SKILL}
|
|
356
|
+
WHERE organizationId = $organizationId
|
|
357
|
+
${ACTIVE_SKILL_FILTER}
|
|
358
|
+
AND (string::lowercase(name) = $nameRef OR $nameRef IN tags)
|
|
359
|
+
ORDER BY confidence DESC
|
|
360
|
+
LIMIT 1`,
|
|
361
|
+
{ organizationId: orgRef, nameRef: normalizedRef },
|
|
362
|
+
),
|
|
363
|
+
LearnedSkillRowSchema,
|
|
364
|
+
)
|
|
365
|
+
return rows[0] ?? null
|
|
366
|
+
}
|
|
367
|
+
|
|
349
368
|
async findByHash(orgId: string, hash: string): Promise<LearnedSkillRow | null> {
|
|
350
369
|
const orgRef = ensureRecordId(orgId, TABLES.ORGANIZATION)
|
|
351
370
|
const rows = await databaseService.queryMany(
|
|
@@ -3,13 +3,14 @@ import type { z } from 'zod'
|
|
|
3
3
|
import { MemoryImportanceAssessmentSchema } from '../db/memory-types'
|
|
4
4
|
import { createHelperModelRuntime } from '../runtime/helper-model'
|
|
5
5
|
import { createOrgMemoryAgent } from '../system-agents/memory.agent'
|
|
6
|
+
import { clampImportance } from '../utils/string'
|
|
6
7
|
|
|
7
8
|
type MemoryImportanceAssessment = z.infer<typeof MemoryImportanceAssessmentSchema>
|
|
8
9
|
const MEMORY_IMPORTANCE_ASSESSMENT_TIMEOUT_MS = 10 * 60 * 1000
|
|
9
10
|
const helperModelRuntime = createHelperModelRuntime()
|
|
10
11
|
|
|
11
12
|
export function clampMemoryImportance(value: number): number {
|
|
12
|
-
return Math.max(0.2, Math.min(0.95, value))
|
|
13
|
+
return clampImportance(Math.max(0.2, Math.min(0.95, value)))
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
export async function assessMemoryImportance(params: {
|
|
@@ -32,7 +33,7 @@ export async function assessMemoryImportance(params: {
|
|
|
32
33
|
'Return only schema fields.',
|
|
33
34
|
].join('\n')
|
|
34
35
|
|
|
35
|
-
return
|
|
36
|
+
return helperModelRuntime.generateHelperStructured({
|
|
36
37
|
tag: params.tag ?? 'memory-importance-assessment',
|
|
37
38
|
createAgent: createOrgMemoryAgent,
|
|
38
39
|
systemPrompt: 'You are a strict long-term memory quality assessor for an AI agent.',
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { MEMORY } from '../config/constants'
|
|
2
2
|
import { VECTOR_SEARCH_OVERFETCH_MULTIPLIER } from '../config/search'
|
|
3
3
|
import type { MemorySearchResult } from '../db/memory-types'
|
|
4
|
-
import { compactWhitespace } from '../utils/string'
|
|
4
|
+
import { compactWhitespace, truncateText } from '../utils/string'
|
|
5
5
|
import type { MemoryRerankOutput } from './memory.service'
|
|
6
6
|
|
|
7
7
|
export function getCandidateLimit(limit: number): number {
|
|
@@ -11,11 +11,7 @@ export function getCandidateLimit(limit: number): number {
|
|
|
11
11
|
export function formatMemoryResults(results: MemorySearchResult[]): string {
|
|
12
12
|
if (results.length === 0) return 'No stored memories.'
|
|
13
13
|
|
|
14
|
-
const normalize = (value: string) =>
|
|
15
|
-
const trimmed = compactWhitespace(value)
|
|
16
|
-
if (trimmed.length <= 400) return trimmed
|
|
17
|
-
return `${trimmed.slice(0, 400)}...`
|
|
18
|
-
}
|
|
14
|
+
const normalize = (value: string) => truncateText(compactWhitespace(value), 400)
|
|
19
15
|
|
|
20
16
|
return results
|
|
21
17
|
.map((item) => {
|
|
@@ -61,8 +57,7 @@ export function formatRerankedResults(
|
|
|
61
57
|
used.add(candidate.id)
|
|
62
58
|
total += 1
|
|
63
59
|
const reason = item.relevance ? ` — ${item.relevance}` : ''
|
|
64
|
-
const
|
|
65
|
-
const content = trimmed.length <= 400 ? trimmed : `${trimmed.slice(0, 400)}...`
|
|
60
|
+
const content = truncateText(compactWhitespace(candidate.content), 400)
|
|
66
61
|
lines.push(`- ${content}${reason}`)
|
|
67
62
|
if (total >= limit) break
|
|
68
63
|
}
|