@lota-sdk/core 0.4.9 → 0.4.11
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 +2 -2
- package/src/ai/embedding-cache.ts +3 -1
- package/src/ai-gateway/ai-gateway.ts +164 -82
- package/src/ai-gateway/index.ts +16 -1
- package/src/config/agent-defaults.ts +4 -107
- package/src/config/agent-types.ts +1 -1
- package/src/config/background-processing.ts +1 -1
- package/src/config/index.ts +0 -1
- package/src/config/logger.ts +22 -25
- package/src/config/thread-defaults.ts +1 -10
- package/src/create-runtime.ts +145 -670
- package/src/db/base.service.ts +30 -38
- package/src/db/memory-query-builder.ts +2 -1
- package/src/db/memory-store.ts +29 -20
- package/src/db/memory.ts +188 -195
- package/src/db/service-normalization.ts +97 -64
- package/src/db/service.ts +496 -384
- package/src/db/startup.ts +30 -19
- package/src/effect/helpers.ts +30 -5
- package/src/effect/index.ts +7 -7
- package/src/effect/layers.ts +75 -72
- package/src/effect/services.ts +15 -11
- package/src/embeddings/provider.ts +65 -71
- package/src/index.ts +13 -12
- package/src/queues/autonomous-job.queue.ts +177 -143
- package/src/queues/context-compaction.queue.ts +41 -39
- package/src/queues/delayed-node-promotion.queue.ts +61 -42
- package/src/queues/document-processor.queue.ts +5 -3
- package/src/queues/index.ts +1 -0
- package/src/queues/memory-consolidation.queue.ts +79 -53
- package/src/queues/organization-learning.queue.ts +70 -33
- package/src/queues/plan-agent-heartbeat.queue.ts +111 -83
- package/src/queues/plan-scheduler.queue.ts +101 -97
- package/src/queues/post-chat-memory.queue.ts +56 -46
- package/src/queues/queue-factory.ts +146 -69
- package/src/queues/queues.service.ts +61 -0
- package/src/queues/title-generation.queue.ts +44 -44
- package/src/redis/connection.ts +181 -164
- package/src/redis/org-memory-lock.ts +24 -9
- package/src/redis/redis-lease-lock.ts +8 -1
- package/src/redis/stream-context.ts +17 -9
- package/src/runtime/agent-identity-overrides.ts +7 -3
- package/src/runtime/agent-runtime-policy.ts +10 -5
- package/src/runtime/agent-stream-helpers.ts +24 -15
- package/src/runtime/chat-run-orchestration.ts +1 -1
- package/src/runtime/context-compaction/context-compaction-runtime.ts +28 -32
- package/src/runtime/context-compaction/context-compaction.ts +131 -85
- package/src/runtime/domain-layer.ts +203 -0
- package/src/runtime/execution-plan-visibility.ts +5 -2
- package/src/runtime/graph-designer.ts +0 -14
- package/src/runtime/helper-model.ts +8 -4
- package/src/runtime/index.ts +1 -1
- package/src/runtime/indexed-repositories-policy.ts +2 -6
- package/src/runtime/memory/memory-block.ts +19 -9
- package/src/runtime/memory/memory-pipeline.ts +53 -66
- package/src/runtime/memory/memory-scope.ts +33 -29
- package/src/runtime/plugin-resolution.ts +58 -62
- package/src/runtime/post-turn-side-effects.ts +139 -161
- package/src/runtime/retrieval-adapters.ts +4 -4
- package/src/runtime/runtime-config.ts +3 -9
- package/src/runtime/runtime-extensions.ts +0 -43
- package/src/runtime/runtime-lifecycle.ts +124 -0
- package/src/runtime/runtime-services.ts +455 -0
- package/src/runtime/runtime-worker-registry.ts +113 -30
- package/src/runtime/social-chat/social-chat-agent-runner.ts +13 -8
- package/src/runtime/social-chat/social-chat-history.ts +24 -13
- package/src/runtime/social-chat/social-chat.ts +420 -369
- package/src/runtime/team-consultation/team-consultation-orchestrator.ts +64 -57
- package/src/runtime/team-consultation/team-consultation-prompts.ts +11 -6
- package/src/runtime/thread-chat-helpers.ts +18 -9
- package/src/runtime/thread-turn-context.ts +28 -74
- package/src/runtime/turn-lifecycle.ts +6 -14
- package/src/services/agent-activity.service.ts +169 -176
- package/src/services/agent-executor.service.ts +207 -196
- package/src/services/artifact.service.ts +10 -5
- package/src/services/attachment.service.ts +16 -48
- package/src/services/autonomous-job.service.ts +81 -87
- package/src/services/background-work.service.ts +54 -0
- package/src/services/chat-run-registry.service.ts +3 -1
- package/src/services/context-compaction.service.ts +8 -10
- package/src/services/document-chunk.service.ts +8 -17
- package/src/services/execution-plan/execution-plan-graph.ts +122 -109
- package/src/services/execution-plan/execution-plan-schedule.ts +1 -15
- package/src/services/execution-plan/execution-plan.service.ts +68 -51
- package/src/services/feedback-loop.service.ts +1 -1
- package/src/services/global-orchestrator.service.ts +49 -15
- package/src/services/graph-full-routing.ts +49 -37
- package/src/services/index.ts +1 -0
- package/src/services/institutional-memory.service.ts +8 -17
- package/src/services/learned-skill.service.ts +38 -35
- package/src/services/memory/memory-conversation.ts +10 -5
- package/src/services/memory/memory-errors.ts +27 -0
- package/src/services/memory/memory-org-memory.ts +14 -3
- package/src/services/memory/memory-preseeded.ts +10 -4
- package/src/services/memory/memory-utils.ts +2 -1
- package/src/services/memory/memory.service.ts +37 -52
- package/src/services/memory/rerank.service.ts +3 -11
- package/src/services/monitoring-window.service.ts +1 -1
- package/src/services/mutating-approval.service.ts +1 -1
- package/src/services/node-workspace.service.ts +2 -2
- package/src/services/notification.service.ts +16 -4
- package/src/services/organization-member.service.ts +1 -1
- package/src/services/organization.service.ts +34 -51
- package/src/services/ownership-dispatcher.service.ts +148 -95
- package/src/services/plan/plan-agent-heartbeat.service.ts +30 -16
- package/src/services/plan/plan-agent-query.service.ts +13 -9
- package/src/services/plan/plan-approval.service.ts +52 -48
- package/src/services/plan/plan-artifact.service.ts +2 -2
- package/src/services/plan/plan-builder.service.ts +2 -2
- package/src/services/plan/plan-checkpoint.service.ts +1 -1
- package/src/services/plan/plan-compiler.service.ts +1 -1
- package/src/services/plan/plan-completion-side-effects.ts +99 -113
- package/src/services/plan/plan-coordination.service.ts +1 -1
- package/src/services/plan/plan-cycle.service.ts +171 -202
- package/src/services/plan/plan-deadline.service.ts +304 -307
- package/src/services/plan/plan-event-delivery.service.ts +84 -72
- package/src/services/plan/plan-executor-context.ts +2 -0
- package/src/services/plan/plan-executor-graph.ts +375 -353
- package/src/services/plan/plan-executor-helpers.ts +60 -75
- package/src/services/plan/plan-executor.service.ts +494 -489
- package/src/services/plan/plan-run.service.ts +12 -19
- package/src/services/plan/plan-scheduler.service.ts +89 -82
- package/src/services/plan/plan-template.service.ts +1 -1
- package/src/services/plan/plan-transaction-events.ts +8 -5
- package/src/services/plan/plan-validator.service.ts +1 -1
- package/src/services/plan/plan-workspace.service.ts +17 -11
- package/src/services/plugin-executor.service.ts +26 -21
- package/src/services/quality-metrics.service.ts +1 -1
- package/src/services/queue-job.service.ts +8 -17
- package/src/services/recent-activity-title.service.ts +22 -10
- package/src/services/recent-activity.service.ts +1 -1
- package/src/services/skill-resolver.service.ts +1 -1
- package/src/services/social-chat-history.service.ts +37 -20
- package/src/services/system-executor.service.ts +25 -20
- package/src/services/thread/thread-bootstrap.ts +37 -19
- package/src/services/thread/thread-listing.ts +2 -1
- package/src/services/thread/thread-memory-block.ts +18 -5
- package/src/services/thread/thread-message.service.ts +30 -13
- package/src/services/thread/thread-title.service.ts +1 -1
- package/src/services/thread/thread-turn-execution.ts +87 -83
- package/src/services/thread/thread-turn-preparation.service.ts +65 -40
- package/src/services/thread/thread-turn-streaming.ts +32 -36
- package/src/services/thread/thread-turn.ts +43 -29
- package/src/services/thread/thread.service.ts +32 -8
- package/src/services/user.service.ts +1 -1
- package/src/services/write-intent-validator.service.ts +1 -1
- package/src/storage/attachment-storage.service.ts +7 -4
- package/src/storage/generated-document-storage.service.ts +1 -1
- package/src/system-agents/context-compaction.agent.ts +1 -1
- package/src/system-agents/helper-agent-options.ts +1 -1
- package/src/system-agents/memory-reranker.agent.ts +1 -1
- package/src/system-agents/memory.agent.ts +1 -1
- package/src/system-agents/recent-activity-title-refiner.agent.ts +9 -6
- package/src/system-agents/regular-chat-memory-digest.agent.ts +1 -1
- package/src/system-agents/skill-extractor.agent.ts +1 -1
- package/src/system-agents/skill-manager.agent.ts +1 -1
- package/src/system-agents/thread-router.agent.ts +23 -20
- package/src/system-agents/title-generator.agent.ts +1 -1
- package/src/tools/execution-plan.tool.ts +36 -20
- package/src/tools/fetch-webpage.tool.ts +30 -22
- package/src/tools/firecrawl-client.ts +1 -6
- package/src/tools/plan-approval.tool.ts +9 -1
- package/src/tools/remember-memory.tool.ts +3 -6
- package/src/tools/research-topic.tool.ts +12 -3
- package/src/tools/search-web.tool.ts +26 -18
- package/src/tools/search.tool.ts +4 -5
- package/src/tools/team-think.tool.ts +139 -121
- package/src/utils/async.ts +15 -6
- package/src/utils/errors.ts +27 -15
- package/src/workers/bootstrap.ts +34 -58
- package/src/workers/memory-consolidation.worker.ts +4 -1
- package/src/workers/organization-learning.worker.ts +16 -3
- package/src/workers/regular-chat-memory-digest.helpers.ts +3 -4
- package/src/workers/regular-chat-memory-digest.runner.ts +46 -29
- package/src/workers/skill-extraction.runner.ts +13 -15
- package/src/workers/worker-utils.ts +14 -8
- package/src/config/search.ts +0 -3
- package/src/effect/awaitable-effect.ts +0 -87
- package/src/effect/runtime-ref.ts +0 -25
- package/src/effect/runtime.ts +0 -31
- package/src/redis/runtime-connection.ts +0 -10
- package/src/runtime/agent-types.ts +0 -1
|
@@ -9,14 +9,19 @@ import type {
|
|
|
9
9
|
} from '@lota-sdk/shared'
|
|
10
10
|
import { Effect } from 'effect'
|
|
11
11
|
|
|
12
|
+
import type { ResolvedAgentConfig } from '../config/agent-defaults'
|
|
12
13
|
import { serverLogger } from '../config/logger'
|
|
13
14
|
import { recordIdToString } from '../db/record-id'
|
|
14
15
|
import { TABLES } from '../db/tables'
|
|
15
|
-
import { BadRequestError } from '../effect/errors'
|
|
16
|
+
import { BadRequestError, ServiceError } from '../effect/errors'
|
|
17
|
+
import { makeEffectTryPromiseWithMessage } from '../effect/helpers'
|
|
18
|
+
import type { PlanAgentHeartbeatQueueRuntime } from '../queues/plan-agent-heartbeat.queue'
|
|
16
19
|
import { shouldPlanNodeUseVisibleTurn } from '../runtime/execution-plan-visibility'
|
|
17
20
|
import type { makePlanExecutorService } from './plan/plan-executor.service'
|
|
18
21
|
import type { makePlanRunService } from './plan/plan-run.service'
|
|
19
22
|
|
|
23
|
+
const tryGraphFullPromise = makeEffectTryPromiseWithMessage((message, cause) => new ServiceError({ message, cause }))
|
|
24
|
+
|
|
20
25
|
function classifyDispatchFailure(ownerType: string, error: unknown): PlanFailureClass {
|
|
21
26
|
const errorMessage = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase()
|
|
22
27
|
if (errorMessage.includes('timeout')) return 'timeout_exceeded'
|
|
@@ -30,19 +35,21 @@ function formatDispatchError(error: unknown): string {
|
|
|
30
35
|
|
|
31
36
|
const STABLE_RUN_STATUSES = new Set(['pending-approval', 'awaiting-human', 'blocked', 'failed', 'completed', 'aborted'])
|
|
32
37
|
|
|
33
|
-
interface GraphFullRoutingDeps {
|
|
38
|
+
interface GraphFullRoutingDeps<E = never> {
|
|
39
|
+
agentConfig: ResolvedAgentConfig
|
|
34
40
|
dispatchReadyNode(params: {
|
|
35
41
|
run: PlanRunRecord
|
|
36
42
|
nodeSpecRecord: PlanNodeSpecRecord
|
|
37
43
|
nodeRun: PlanNodeRunRecord
|
|
38
44
|
spec: PlanSpecRecord
|
|
39
45
|
executionModeOverride?: ExecutionMode
|
|
40
|
-
}): Effect.Effect<PlanNodeResultSubmission,
|
|
46
|
+
}): Effect.Effect<PlanNodeResultSubmission, E, never>
|
|
41
47
|
planExecutorService: ReturnType<typeof makePlanExecutorService>
|
|
42
48
|
planRunService: ReturnType<typeof makePlanRunService>
|
|
49
|
+
planAgentHeartbeatQueue: PlanAgentHeartbeatQueueRuntime
|
|
43
50
|
}
|
|
44
51
|
|
|
45
|
-
export function routeGraphFullEffect(params: { threadId: string; runId: string }, deps: GraphFullRoutingDeps) {
|
|
52
|
+
export function routeGraphFullEffect<E>(params: { threadId: string; runId: string }, deps: GraphFullRoutingDeps<E>) {
|
|
46
53
|
return Effect.gen(function* () {
|
|
47
54
|
const MAX_ROUNDS = 32
|
|
48
55
|
const STRUCTURAL_TYPES = new Set(['switch', 'join', 'deliberation-fork'])
|
|
@@ -65,26 +72,25 @@ export function routeGraphFullEffect(params: { threadId: string; runId: string }
|
|
|
65
72
|
|
|
66
73
|
const silentNodes = readyNodes.filter((nr: PlanNodeRunRecord) => {
|
|
67
74
|
const ns = nodeSpecs.find((s: PlanNodeSpecRecord) => s.nodeId === nr.nodeId)
|
|
68
|
-
return ns && !shouldPlanNodeUseVisibleTurn(spec, ns)
|
|
75
|
+
return ns && !shouldPlanNodeUseVisibleTurn(deps.agentConfig, spec, ns)
|
|
69
76
|
})
|
|
70
77
|
const visibleNodes = readyNodes.filter((nr: PlanNodeRunRecord) => {
|
|
71
78
|
const ns = nodeSpecs.find((s: PlanNodeSpecRecord) => s.nodeId === nr.nodeId)
|
|
72
|
-
return ns && shouldPlanNodeUseVisibleTurn(spec, ns)
|
|
79
|
+
return ns && shouldPlanNodeUseVisibleTurn(deps.agentConfig, spec, ns)
|
|
73
80
|
})
|
|
74
81
|
|
|
75
82
|
yield* Effect.forEach(
|
|
76
83
|
readyNodes,
|
|
77
84
|
(nodeRun) =>
|
|
78
|
-
|
|
79
|
-
deps.planExecutorService.transitionNodeToRunning({ runId: params.runId, nodeId: nodeRun.nodeId }),
|
|
85
|
+
tryGraphFullPromise(
|
|
86
|
+
() => deps.planExecutorService.transitionNodeToRunning({ runId: params.runId, nodeId: nodeRun.nodeId }),
|
|
87
|
+
'Failed to transition plan node to running.',
|
|
80
88
|
),
|
|
81
89
|
{ concurrency: 'unbounded' },
|
|
82
90
|
)
|
|
83
91
|
|
|
84
92
|
if (visibleNodes.length > 0) {
|
|
85
|
-
const
|
|
86
|
-
() => import('../queues/plan-agent-heartbeat.queue'),
|
|
87
|
-
)
|
|
93
|
+
const enqueuePlanAgentHeartbeatWake = deps.planAgentHeartbeatQueue.enqueuePlanAgentHeartbeatWake
|
|
88
94
|
const updatedRunForWake = yield* deps.planRunService.getRunById(params.runId)
|
|
89
95
|
yield* Effect.forEach(
|
|
90
96
|
visibleNodes,
|
|
@@ -95,15 +101,17 @@ export function routeGraphFullEffect(params: { threadId: string; runId: string }
|
|
|
95
101
|
return
|
|
96
102
|
}
|
|
97
103
|
|
|
98
|
-
yield*
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
104
|
+
yield* tryGraphFullPromise(
|
|
105
|
+
() =>
|
|
106
|
+
enqueuePlanAgentHeartbeatWake({
|
|
107
|
+
organizationId: recordIdToString(updatedRunForWake.organizationId, TABLES.ORGANIZATION),
|
|
108
|
+
threadId: recordIdToString(updatedRunForWake.threadId, TABLES.THREAD),
|
|
109
|
+
runId: recordIdToString(updatedRunForWake.id, TABLES.PLAN_RUN),
|
|
110
|
+
nodeId: nodeRun.nodeId,
|
|
111
|
+
agentId: ns.owner.ref,
|
|
112
|
+
reason: 'graph-full-visible',
|
|
113
|
+
}),
|
|
114
|
+
'Failed to enqueue plan agent heartbeat wake.',
|
|
107
115
|
)
|
|
108
116
|
}),
|
|
109
117
|
{ concurrency: 'unbounded' },
|
|
@@ -150,26 +158,30 @@ export function routeGraphFullEffect(params: { threadId: string; runId: string }
|
|
|
150
158
|
const nodeSpecRecord = nodeSpecs.find((ns) => ns.nodeId === nodeRun.nodeId)
|
|
151
159
|
|
|
152
160
|
if (settled.status === 'fulfilled') {
|
|
153
|
-
yield*
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
+
yield* tryGraphFullPromise(
|
|
162
|
+
() =>
|
|
163
|
+
deps.planExecutorService.submitNodeResult({
|
|
164
|
+
threadId,
|
|
165
|
+
runId,
|
|
166
|
+
nodeId: settled.value.nodeId,
|
|
167
|
+
emittedBy: settled.value.ownerRef,
|
|
168
|
+
result: settled.value.result,
|
|
169
|
+
}),
|
|
170
|
+
'Failed to submit plan node result.',
|
|
161
171
|
)
|
|
162
172
|
} else {
|
|
163
173
|
serverLogger.warn`routeGraphFull: dispatch failed for node "${nodeRun.nodeId}": ${settled.reason}`
|
|
164
|
-
yield*
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
174
|
+
yield* tryGraphFullPromise(
|
|
175
|
+
() =>
|
|
176
|
+
deps.planExecutorService.blockNodeOnDispatchFailure({
|
|
177
|
+
threadId,
|
|
178
|
+
runId,
|
|
179
|
+
nodeId: nodeRun.nodeId,
|
|
180
|
+
emittedBy: nodeSpecRecord?.owner.ref ?? 'unknown',
|
|
181
|
+
message: formatDispatchError(settled.reason),
|
|
182
|
+
failureClass: classifyDispatchFailure(nodeSpecRecord?.owner.executorType ?? 'agent', settled.reason),
|
|
183
|
+
}),
|
|
184
|
+
'Failed to block plan node on dispatch failure.',
|
|
173
185
|
)
|
|
174
186
|
}
|
|
175
187
|
}
|
package/src/services/index.ts
CHANGED
|
@@ -3,6 +3,7 @@ export * from './agent-activity.service'
|
|
|
3
3
|
export * from './artifact.service'
|
|
4
4
|
export * from './attachment.service'
|
|
5
5
|
export * from './autonomous-job.service'
|
|
6
|
+
export * from './background-work.service'
|
|
6
7
|
export * from './document-chunk.service'
|
|
7
8
|
export * from './execution-plan/execution-plan.service'
|
|
8
9
|
export * from './institutional-memory.service'
|
|
@@ -11,6 +11,7 @@ import { BoundQuery } from 'surrealdb'
|
|
|
11
11
|
import { ensureRecordId } from '../db/record-id'
|
|
12
12
|
import type { SurrealDBService } from '../db/service'
|
|
13
13
|
import { TABLES } from '../db/tables'
|
|
14
|
+
import { makeEffectTryPromiseWithMessage } from '../effect/helpers'
|
|
14
15
|
import { DatabaseServiceTag } from '../effect/services'
|
|
15
16
|
import { unsafeDateFrom } from '../utils/date-time'
|
|
16
17
|
import type { makePlanRunService } from './plan/plan-run.service'
|
|
@@ -26,25 +27,15 @@ class InstitutionalMemoryServiceError extends Schema.TaggedErrorClass<Institutio
|
|
|
26
27
|
{ message: Schema.String, cause: Schema.Defect },
|
|
27
28
|
) {}
|
|
28
29
|
|
|
30
|
+
const effectTryInstitutionalMemoryPromise = makeEffectTryPromiseWithMessage(
|
|
31
|
+
(message, cause) => new InstitutionalMemoryServiceError({ message, cause }),
|
|
32
|
+
)
|
|
33
|
+
|
|
29
34
|
function tryInstitutionalMemoryPromise<A>(
|
|
30
35
|
message: string,
|
|
31
|
-
|
|
36
|
+
evaluate: () => PromiseLike<A> | Effect.Effect<A, unknown>,
|
|
32
37
|
): Effect.Effect<A, InstitutionalMemoryServiceError> {
|
|
33
|
-
return
|
|
34
|
-
try {
|
|
35
|
-
const value = thunk()
|
|
36
|
-
if (Effect.isEffect(value)) {
|
|
37
|
-
return value.pipe(Effect.mapError((cause) => new InstitutionalMemoryServiceError({ message, cause })))
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return Effect.tryPromise({
|
|
41
|
-
try: () => Promise.resolve(value),
|
|
42
|
-
catch: (cause) => new InstitutionalMemoryServiceError({ message, cause }),
|
|
43
|
-
})
|
|
44
|
-
} catch (cause) {
|
|
45
|
-
return Effect.fail(new InstitutionalMemoryServiceError({ message, cause }))
|
|
46
|
-
}
|
|
47
|
-
})
|
|
38
|
+
return effectTryInstitutionalMemoryPromise(evaluate, message)
|
|
48
39
|
}
|
|
49
40
|
|
|
50
41
|
export function makeInstitutionalMemoryService(deps: InstitutionalMemoryDeps) {
|
|
@@ -230,7 +221,7 @@ export function makeInstitutionalMemoryService(deps: InstitutionalMemoryDeps) {
|
|
|
230
221
|
export class InstitutionalMemoryServiceTag extends Context.Service<
|
|
231
222
|
InstitutionalMemoryServiceTag,
|
|
232
223
|
ReturnType<typeof makeInstitutionalMemoryService>
|
|
233
|
-
>()('InstitutionalMemoryService') {}
|
|
224
|
+
>()('@lota-sdk/core/InstitutionalMemoryService') {}
|
|
234
225
|
|
|
235
226
|
export const InstitutionalMemoryServiceLive = Layer.effect(
|
|
236
227
|
InstitutionalMemoryServiceTag,
|
|
@@ -8,10 +8,12 @@ import { serverLogger } from '../config/logger'
|
|
|
8
8
|
import { ensureRecordId, recordIdToString } from '../db/record-id'
|
|
9
9
|
import type { SurrealDBService } from '../db/service'
|
|
10
10
|
import { TABLES } from '../db/tables'
|
|
11
|
+
import { makeEffectTryPromiseWithMessage } from '../effect/helpers'
|
|
11
12
|
import { DatabaseServiceTag, RuntimeConfigServiceTag } from '../effect/services'
|
|
12
13
|
import { ProviderEmbeddings } from '../embeddings/provider'
|
|
13
14
|
import { sha256HexFromParts } from '../utils/crypto'
|
|
14
15
|
import { nowDate } from '../utils/date-time'
|
|
16
|
+
import { BackgroundWorkService } from './background-work.service'
|
|
15
17
|
|
|
16
18
|
const PROMOTION_MIN_USES = 5
|
|
17
19
|
const PROMOTION_MIN_SUCCESS_RATE = 0.6
|
|
@@ -64,25 +66,15 @@ class LearnedSkillNotFoundError extends Schema.TaggedErrorClass<LearnedSkillNotF
|
|
|
64
66
|
{ message: Schema.String },
|
|
65
67
|
) {}
|
|
66
68
|
|
|
69
|
+
const effectTryLearnedSkillPromise = makeEffectTryPromiseWithMessage(
|
|
70
|
+
(message, cause) => new LearnedSkillServiceError({ message, cause }),
|
|
71
|
+
)
|
|
72
|
+
|
|
67
73
|
function tryLearnedSkillPromise<A>(
|
|
68
74
|
message: string,
|
|
69
|
-
|
|
75
|
+
evaluate: () => PromiseLike<A> | Effect.Effect<A, unknown>,
|
|
70
76
|
): Effect.Effect<A, LearnedSkillServiceError> {
|
|
71
|
-
return
|
|
72
|
-
try {
|
|
73
|
-
const value = thunk()
|
|
74
|
-
if (Effect.isEffect(value)) {
|
|
75
|
-
return value.pipe(Effect.mapError((cause) => new LearnedSkillServiceError({ message, cause })))
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
return Effect.tryPromise({
|
|
79
|
-
try: () => Promise.resolve(value),
|
|
80
|
-
catch: (cause) => new LearnedSkillServiceError({ message, cause }),
|
|
81
|
-
})
|
|
82
|
-
} catch (cause) {
|
|
83
|
-
return Effect.fail(new LearnedSkillServiceError({ message, cause }))
|
|
84
|
-
}
|
|
85
|
-
})
|
|
77
|
+
return effectTryLearnedSkillPromise(evaluate, message)
|
|
86
78
|
}
|
|
87
79
|
|
|
88
80
|
interface CreateLearnedSkillInput {
|
|
@@ -126,6 +118,7 @@ export function makeLearnedSkillService(
|
|
|
126
118
|
db: SurrealDBService,
|
|
127
119
|
options: { embeddingModel: string; openRouterApiKey?: string },
|
|
128
120
|
skillExistsCache: Cache.Cache<string, boolean, LearnedSkillServiceError>,
|
|
121
|
+
background: Context.Service.Shape<typeof BackgroundWorkService>,
|
|
129
122
|
) {
|
|
130
123
|
const embeddings = new ProviderEmbeddings({
|
|
131
124
|
modelId: options.embeddingModel,
|
|
@@ -265,30 +258,33 @@ export function makeLearnedSkillService(
|
|
|
265
258
|
}),
|
|
266
259
|
),
|
|
267
260
|
).pipe(Effect.withSpan('LearnedSkillService.queryNearestSkills'))
|
|
268
|
-
|
|
261
|
+
const parsedRows = yield* Effect.try({
|
|
262
|
+
try: () => rows.map((row) => SearchResultRowSchema.parse(row)),
|
|
263
|
+
catch: (cause) =>
|
|
264
|
+
new LearnedSkillServiceError({ message: 'Failed to parse learned skill search results.', cause }),
|
|
265
|
+
})
|
|
266
|
+
return parsedRows.filter((row) => row.similarity >= 0.3)
|
|
269
267
|
})
|
|
270
268
|
|
|
271
269
|
const retrieveForTurn = Effect.fn('LearnedSkillService.retrieveForTurn')(function* (params: RetrieveForTurnParams) {
|
|
272
270
|
const results = yield* searchForTurn(params)
|
|
273
271
|
if (results.length === 0) return undefined
|
|
274
272
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
serverLogger.warn`Failed to record learned skill usage for ${result.id}: ${error}`
|
|
285
|
-
}),
|
|
286
|
-
),
|
|
273
|
+
yield* background.run(
|
|
274
|
+
Effect.forEach(
|
|
275
|
+
results,
|
|
276
|
+
(result) =>
|
|
277
|
+
recordUsage(result.id).pipe(
|
|
278
|
+
Effect.tapError((error) =>
|
|
279
|
+
Effect.sync(() => {
|
|
280
|
+
serverLogger.warn`Failed to record learned skill usage for ${result.id}: ${error}`
|
|
281
|
+
}),
|
|
287
282
|
),
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
)
|
|
291
|
-
|
|
283
|
+
),
|
|
284
|
+
{ discard: true },
|
|
285
|
+
),
|
|
286
|
+
'learned-skill.recordUsage',
|
|
287
|
+
)
|
|
292
288
|
|
|
293
289
|
const section = renderLearnedSkillInstructions(
|
|
294
290
|
results.map((row) => ({ name: row.name, instructions: row.instructions })),
|
|
@@ -365,7 +361,12 @@ export function makeLearnedSkillService(
|
|
|
365
361
|
const rows = yield* tryLearnedSkillPromise('Failed to query most similar learned skill.', () =>
|
|
366
362
|
db.query<unknown>(new BoundQuery(sql, { organizationId: orgRef, embedding: descEmbedding })),
|
|
367
363
|
)
|
|
368
|
-
|
|
364
|
+
if (rows.length === 0) return null
|
|
365
|
+
return yield* Effect.try({
|
|
366
|
+
try: () => LearnedSkillRowSchema.parse(rows[0]),
|
|
367
|
+
catch: (cause) =>
|
|
368
|
+
new LearnedSkillServiceError({ message: 'Failed to parse most similar learned skill row.', cause }),
|
|
369
|
+
})
|
|
369
370
|
})
|
|
370
371
|
|
|
371
372
|
const listForOrg = (orgId: string) =>
|
|
@@ -450,13 +451,14 @@ export function makeLearnedSkillService(
|
|
|
450
451
|
export class LearnedSkillServiceTag extends Context.Service<
|
|
451
452
|
LearnedSkillServiceTag,
|
|
452
453
|
ReturnType<typeof makeLearnedSkillService>
|
|
453
|
-
>()('LearnedSkillService') {}
|
|
454
|
+
>()('@lota-sdk/core/LearnedSkillService') {}
|
|
454
455
|
|
|
455
456
|
export const LearnedSkillServiceLive = Layer.effect(
|
|
456
457
|
LearnedSkillServiceTag,
|
|
457
458
|
Effect.gen(function* () {
|
|
458
459
|
const db = yield* DatabaseServiceTag
|
|
459
460
|
const runtimeConfig = yield* RuntimeConfigServiceTag
|
|
461
|
+
const background = yield* BackgroundWorkService
|
|
460
462
|
const skillExistsCache = yield* Cache.make({
|
|
461
463
|
lookup: (key: string) =>
|
|
462
464
|
Effect.gen(function* () {
|
|
@@ -486,6 +488,7 @@ export const LearnedSkillServiceLive = Layer.effect(
|
|
|
486
488
|
openRouterApiKey: runtimeConfig.aiGateway.openRouterApiKey,
|
|
487
489
|
},
|
|
488
490
|
skillExistsCache,
|
|
491
|
+
background,
|
|
489
492
|
)
|
|
490
493
|
}),
|
|
491
494
|
)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { ResolvedAgentConfig } from '../../config/agent-defaults'
|
|
2
|
+
import { isAgentName } from '../../config/agent-defaults'
|
|
2
3
|
import type { ExtractedFact, Message } from '../../db/memory-types'
|
|
3
4
|
import { sanitizeAgentOutputForMemory } from '../../runtime/llm-content'
|
|
4
5
|
import { compactWhitespace, truncateText } from '../../utils/string'
|
|
@@ -9,8 +10,8 @@ const MAX_CONVERSATION_MEMORY_BLOCK_CHARS = 2_000
|
|
|
9
10
|
const MAX_CONVERSATION_ATTACHMENT_CONTEXT_CHARS = 6_000
|
|
10
11
|
const LOW_VALUE_MEMORY_IMPORTANCE_THRESHOLD = 0.45
|
|
11
12
|
|
|
12
|
-
export const isRoutableAgentName = (value?: string): value is string =>
|
|
13
|
-
|
|
13
|
+
export const isRoutableAgentName = (agentConfig: ResolvedAgentConfig, value?: string): value is string =>
|
|
14
|
+
isAgentName(agentConfig, value)
|
|
14
15
|
|
|
15
16
|
function normalizeConversationText(value: string, maxChars: number): string {
|
|
16
17
|
const normalized = compactWhitespace(value)
|
|
@@ -18,7 +19,11 @@ function normalizeConversationText(value: string, maxChars: number): string {
|
|
|
18
19
|
return truncateText(normalized, maxChars)
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
export function resolveAgentScopeNames(
|
|
22
|
+
export function resolveAgentScopeNames(
|
|
23
|
+
agentConfig: ResolvedAgentConfig,
|
|
24
|
+
agentName?: string,
|
|
25
|
+
agentNames: string[] = [],
|
|
26
|
+
): string[] {
|
|
22
27
|
const unique = new Set<string>()
|
|
23
28
|
if (typeof agentName === 'string' && agentName.trim()) {
|
|
24
29
|
unique.add(agentName.trim())
|
|
@@ -29,7 +34,7 @@ export function resolveAgentScopeNames(agentName?: string, agentNames: string[]
|
|
|
29
34
|
if (!normalized) continue
|
|
30
35
|
unique.add(normalized)
|
|
31
36
|
}
|
|
32
|
-
return [...unique].filter((name) => isRoutableAgentName(name))
|
|
37
|
+
return [...unique].filter((name) => isRoutableAgentName(agentConfig, name))
|
|
33
38
|
}
|
|
34
39
|
|
|
35
40
|
export function buildConversationMessages(params: {
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Effect, Schema } from 'effect'
|
|
2
|
+
|
|
3
|
+
export class MemoryServiceError extends Schema.TaggedErrorClass<MemoryServiceError>()('MemoryServiceError', {
|
|
4
|
+
message: Schema.String,
|
|
5
|
+
cause: Schema.Defect,
|
|
6
|
+
}) {}
|
|
7
|
+
|
|
8
|
+
export function tryMemoryPromise<A, E, R = never>(
|
|
9
|
+
message: string,
|
|
10
|
+
thunk: () => PromiseLike<A> | Effect.Effect<A, E, R>,
|
|
11
|
+
): Effect.Effect<A, MemoryServiceError, R> {
|
|
12
|
+
return Effect.suspend(() => {
|
|
13
|
+
try {
|
|
14
|
+
const value = thunk()
|
|
15
|
+
if (Effect.isEffect(value)) {
|
|
16
|
+
return value.pipe(Effect.mapError((cause) => new MemoryServiceError({ message, cause })))
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return Effect.tryPromise({
|
|
20
|
+
try: () => Promise.resolve(value),
|
|
21
|
+
catch: (cause) => new MemoryServiceError({ message, cause }),
|
|
22
|
+
})
|
|
23
|
+
} catch (cause) {
|
|
24
|
+
return Effect.fail(new MemoryServiceError({ message, cause }))
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Context } from 'effect'
|
|
1
2
|
import { Cache, Duration, Effect } from 'effect'
|
|
2
3
|
|
|
3
4
|
import { aiLogger } from '../../config/logger'
|
|
@@ -7,6 +8,7 @@ import type { HelperModelRuntime } from '../../runtime/helper-model'
|
|
|
7
8
|
import { ORG_SCOPE_PREFIX, scopeId } from '../../runtime/memory/memory-scope'
|
|
8
9
|
import type { ResolvedLotaRuntimeConfig } from '../../runtime/runtime-config'
|
|
9
10
|
import { createOrgMemoryAgent, ORG_MEMORY_PROMPT } from '../../system-agents/memory.agent'
|
|
11
|
+
import type { BackgroundWorkService } from '../background-work.service'
|
|
10
12
|
|
|
11
13
|
const MAX_ORG_MEMORY_CLIENTS = 128
|
|
12
14
|
|
|
@@ -14,6 +16,7 @@ interface OrgMemoryDeps {
|
|
|
14
16
|
db: SurrealDBService
|
|
15
17
|
runtimeConfig: ResolvedLotaRuntimeConfig
|
|
16
18
|
helperModelRuntime: HelperModelRuntime
|
|
19
|
+
background: Context.Service.Shape<typeof BackgroundWorkService>
|
|
17
20
|
}
|
|
18
21
|
|
|
19
22
|
export type OrgMemoryCache = Cache.Cache<string, Memory>
|
|
@@ -24,7 +27,12 @@ export function makeOrgMemoryCache(deps: OrgMemoryDeps) {
|
|
|
24
27
|
Effect.sync(() => {
|
|
25
28
|
aiLogger.debug`Memory client created and cached for ${cacheKey}`
|
|
26
29
|
return new Memory(
|
|
27
|
-
{
|
|
30
|
+
{
|
|
31
|
+
db: deps.db,
|
|
32
|
+
runtimeConfig: deps.runtimeConfig,
|
|
33
|
+
helperModelRuntime: deps.helperModelRuntime,
|
|
34
|
+
background: deps.background,
|
|
35
|
+
},
|
|
28
36
|
{ createAgent: createOrgMemoryAgent },
|
|
29
37
|
{ customPrompt: ORG_MEMORY_PROMPT },
|
|
30
38
|
)
|
|
@@ -34,6 +42,9 @@ export function makeOrgMemoryCache(deps: OrgMemoryDeps) {
|
|
|
34
42
|
})
|
|
35
43
|
}
|
|
36
44
|
|
|
37
|
-
export function getOrgMemory(cache: OrgMemoryCache, orgId: string)
|
|
38
|
-
return
|
|
45
|
+
export function getOrgMemory(cache: OrgMemoryCache, orgId: string) {
|
|
46
|
+
return Effect.gen(function* () {
|
|
47
|
+
const key = yield* scopeId(ORG_SCOPE_PREFIX, orgId)
|
|
48
|
+
return yield* Cache.get(cache, key)
|
|
49
|
+
})
|
|
39
50
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Effect } from 'effect'
|
|
2
2
|
|
|
3
|
+
import type { Memory } from '../../db/memory'
|
|
3
4
|
import type { MemoryRecord } from '../../db/memory-types'
|
|
5
|
+
import type { MemoryScopeError } from '../../runtime/memory/memory-scope'
|
|
4
6
|
import { ORG_SCOPE_PREFIX, agentScopeId, scopeId } from '../../runtime/memory/memory-scope'
|
|
5
7
|
import { compactWhitespace, truncateText } from '../../utils/string'
|
|
6
8
|
import type { OrgMemoryCache } from './memory-org-memory'
|
|
@@ -10,6 +12,7 @@ const PRESEEDED_MEMORY_LIMIT = 5
|
|
|
10
12
|
const PRESEEDED_MEMORY_MAX_CHARS = 300
|
|
11
13
|
const PRESEEDED_MIN_IMPORTANCE = 0.7
|
|
12
14
|
const PRESEEDED_MEMORY_DURABILITY: MemoryRecord['durability'] = 'core'
|
|
15
|
+
type PreSeededMemoryError = Effect.Error<ReturnType<Memory['listTopMemories']>> | MemoryScopeError
|
|
13
16
|
|
|
14
17
|
function normalizePreSeededMemoryText(value: string): string {
|
|
15
18
|
return truncateText(compactWhitespace(value), PRESEEDED_MEMORY_MAX_CHARS)
|
|
@@ -30,13 +33,16 @@ export function getTopMemoriesSection(params: {
|
|
|
30
33
|
orgId: string
|
|
31
34
|
agentName?: string
|
|
32
35
|
limit?: number
|
|
33
|
-
}): Effect.Effect<string | undefined,
|
|
36
|
+
}): Effect.Effect<string | undefined, PreSeededMemoryError, never> {
|
|
34
37
|
return Effect.gen(function* () {
|
|
35
38
|
const orgMemory = yield* getOrgMemory(params.orgMemoryCache, params.orgId)
|
|
36
|
-
const orgScopeId = scopeId(ORG_SCOPE_PREFIX, params.orgId)
|
|
39
|
+
const orgScopeId = yield* scopeId(ORG_SCOPE_PREFIX, params.orgId)
|
|
37
40
|
const requestedLimit = params.limit ?? PRESEEDED_MEMORY_LIMIT
|
|
38
41
|
const limit = Math.max(1, Math.min(requestedLimit, PRESEEDED_MEMORY_LIMIT))
|
|
39
42
|
|
|
43
|
+
const agentScopedId =
|
|
44
|
+
params.agentName && params.agentName.trim() ? yield* agentScopeId(params.orgId, params.agentName) : undefined
|
|
45
|
+
|
|
40
46
|
const [orgTopMemories, agentTopMemories] = yield* Effect.all([
|
|
41
47
|
orgMemory.listTopMemories({
|
|
42
48
|
scopeId: orgScopeId,
|
|
@@ -45,9 +51,9 @@ export function getTopMemoriesSection(params: {
|
|
|
45
51
|
durability: PRESEEDED_MEMORY_DURABILITY,
|
|
46
52
|
minImportance: PRESEEDED_MIN_IMPORTANCE,
|
|
47
53
|
}),
|
|
48
|
-
|
|
54
|
+
agentScopedId
|
|
49
55
|
? orgMemory.listTopMemories({
|
|
50
|
-
scopeId:
|
|
56
|
+
scopeId: agentScopedId,
|
|
51
57
|
limit,
|
|
52
58
|
memoryType: 'fact',
|
|
53
59
|
durability: PRESEEDED_MEMORY_DURABILITY,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { VECTOR_SEARCH_OVERFETCH_MULTIPLIER } from '@lota-sdk/shared'
|
|
2
|
+
|
|
1
3
|
import { MEMORY } from '../../config/constants'
|
|
2
|
-
import { VECTOR_SEARCH_OVERFETCH_MULTIPLIER } from '../../config/search'
|
|
3
4
|
import type { MemorySearchResult } from '../../db/memory-types'
|
|
4
5
|
import { compactWhitespace, truncateText } from '../../utils/string'
|
|
5
6
|
import type { MemoryRerankOutput } from './memory-rerank'
|