@lota-sdk/core 0.4.13 → 0.4.15
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 +4 -4
- package/src/ai/embedding-cache.ts +17 -11
- package/src/ai-gateway/ai-gateway.ts +164 -94
- package/src/ai-gateway/index.ts +4 -1
- package/src/config/agent-defaults.ts +2 -2
- package/src/config/agent-types.ts +1 -1
- package/src/config/constants.ts +1 -1
- package/src/create-runtime.ts +259 -200
- package/src/db/cursor-pagination.ts +2 -9
- package/src/db/memory-store.ts +194 -175
- package/src/db/memory.ts +125 -71
- package/src/db/schema-fingerprint.ts +5 -4
- package/src/db/service-normalization.ts +4 -3
- package/src/db/service.ts +3 -2
- package/src/db/startup.ts +15 -16
- package/src/effect/errors.ts +161 -21
- package/src/effect/index.ts +0 -1
- package/src/embeddings/provider.ts +15 -7
- package/src/queues/autonomous-job.queue.ts +10 -22
- package/src/queues/delayed-node-promotion.queue.ts +8 -14
- package/src/queues/document-processor.queue.ts +13 -4
- package/src/queues/memory-consolidation.queue.ts +26 -14
- package/src/queues/plan-agent-heartbeat.queue.ts +48 -31
- package/src/queues/plan-scheduler.queue.ts +37 -15
- package/src/queues/queue-factory.ts +59 -35
- package/src/queues/standalone-worker.ts +3 -2
- package/src/redis/connection.ts +10 -3
- package/src/redis/org-memory-lock.ts +1 -1
- package/src/redis/redis-lease-lock.ts +5 -5
- package/src/redis/stream-context.ts +1 -1
- package/src/runtime/chat-message.ts +64 -1
- package/src/runtime/chat-run-orchestration.ts +33 -20
- package/src/runtime/context-compaction/context-compaction-runtime.ts +14 -7
- package/src/runtime/context-compaction/context-compaction.ts +78 -66
- package/src/runtime/domain-layer.ts +19 -13
- package/src/runtime/execution-plan.ts +7 -3
- package/src/runtime/memory/memory-block.ts +3 -9
- package/src/runtime/memory/memory-scope.ts +3 -1
- package/src/runtime/plugin-resolution.ts +2 -1
- package/src/runtime/post-turn-side-effects.ts +6 -5
- package/src/runtime/retrieval-adapters.ts +8 -20
- package/src/runtime/runtime-config.ts +3 -9
- package/src/runtime/runtime-extensions.ts +2 -4
- package/src/runtime/runtime-lifecycle.ts +56 -16
- package/src/runtime/runtime-services.ts +180 -102
- package/src/runtime/runtime-worker-registry.ts +3 -1
- package/src/runtime/social-chat/social-chat-agent-runner.ts +1 -1
- package/src/runtime/social-chat/social-chat-history.ts +21 -18
- package/src/runtime/social-chat/social-chat.ts +356 -223
- package/src/runtime/specialist-runner.ts +3 -1
- package/src/runtime/team-consultation/team-consultation-orchestrator.ts +3 -2
- package/src/runtime/thread-turn-context.ts +142 -102
- package/src/runtime/turn-lifecycle.ts +15 -46
- package/src/services/agent-activity.service.ts +1 -1
- package/src/services/agent-executor.service.ts +107 -77
- package/src/services/autonomous-job.service.ts +354 -293
- package/src/services/background-work.service.ts +3 -3
- package/src/services/context-compaction.service.ts +7 -2
- package/src/services/document-chunk.service.ts +50 -32
- package/src/services/execution-plan/execution-plan-schedule.ts +5 -3
- package/src/services/execution-plan/execution-plan.service.ts +162 -179
- package/src/services/feedback-loop.service.ts +5 -4
- package/src/services/graph-full-routing.ts +37 -36
- package/src/services/institutional-memory.service.ts +28 -30
- package/src/services/learned-skill.service.ts +107 -72
- package/src/services/memory/memory-errors.ts +4 -23
- package/src/services/memory/memory-org-memory.ts +10 -5
- package/src/services/memory/memory-rerank.ts +18 -6
- package/src/services/memory/memory.service.ts +170 -111
- package/src/services/memory/rerank.service.ts +29 -20
- package/src/services/organization-member.service.ts +1 -1
- package/src/services/organization.service.ts +69 -75
- package/src/services/ownership-dispatcher.service.ts +40 -39
- package/src/services/plan/plan-agent-heartbeat.service.ts +22 -24
- package/src/services/plan/plan-agent-query.service.ts +39 -31
- package/src/services/plan/plan-completion-side-effects.ts +13 -17
- package/src/services/plan/plan-coordination.service.ts +2 -1
- package/src/services/plan/plan-cycle.service.ts +6 -5
- package/src/services/plan/plan-deadline.service.ts +57 -54
- package/src/services/plan/plan-event-delivery.service.ts +5 -4
- package/src/services/plan/plan-executor-graph.ts +18 -15
- package/src/services/plan/plan-executor.service.ts +235 -262
- package/src/services/plan/plan-run.service.ts +169 -93
- package/src/services/plan/plan-scheduler.service.ts +192 -202
- package/src/services/plan/plan-template.service.ts +1 -1
- package/src/services/plan/plan-transaction-events.ts +1 -1
- package/src/services/plan/plan-workspace.service.ts +23 -14
- package/src/services/plugin-executor.service.ts +5 -9
- package/src/services/queue-job.service.ts +117 -59
- package/src/services/recent-activity-title.service.ts +13 -12
- package/src/services/recent-activity.service.ts +6 -1
- package/src/services/social-chat-history.service.ts +29 -25
- package/src/services/system-executor.service.ts +5 -9
- package/src/services/thread/thread-active-run.ts +2 -2
- package/src/services/thread/thread-listing.ts +61 -57
- package/src/services/thread/thread-memory-block.ts +73 -48
- package/src/services/thread/thread-message.service.ts +76 -65
- package/src/services/thread/thread-record-store.ts +8 -8
- package/src/services/thread/thread-title.service.ts +10 -4
- package/src/services/thread/thread-turn-execution.ts +43 -45
- package/src/services/thread/thread-turn-preparation.service.ts +257 -135
- package/src/services/thread/thread-turn-streaming.ts +83 -92
- package/src/services/thread/thread-turn.ts +18 -16
- package/src/services/thread/thread.service.ts +135 -100
- package/src/services/user.service.ts +45 -48
- package/src/storage/attachment-parser.ts +6 -2
- package/src/storage/attachment-storage.service.ts +5 -6
- package/src/storage/generated-document-storage.service.ts +1 -1
- package/src/system-agents/context-compaction.agent.ts +10 -9
- package/src/system-agents/delegated-agent-factory.ts +30 -6
- package/src/system-agents/memory-reranker.agent.ts +10 -9
- package/src/system-agents/memory.agent.ts +10 -9
- package/src/system-agents/recent-activity-title-refiner.agent.ts +13 -15
- package/src/system-agents/regular-chat-memory-digest.agent.ts +13 -12
- package/src/system-agents/skill-extractor.agent.ts +13 -12
- package/src/system-agents/skill-manager.agent.ts +13 -12
- package/src/system-agents/thread-router.agent.ts +11 -7
- package/src/system-agents/title-generator.agent.ts +13 -12
- package/src/tools/fetch-webpage.tool.ts +13 -13
- package/src/tools/memory-block.tool.ts +3 -1
- package/src/tools/plan-approval.tool.ts +4 -2
- package/src/tools/read-file-parts.tool.ts +10 -4
- package/src/tools/remember-memory.tool.ts +3 -1
- package/src/tools/research-topic.tool.ts +9 -5
- package/src/tools/search-web.tool.ts +16 -16
- package/src/tools/search.tool.ts +20 -5
- package/src/tools/team-think.tool.ts +61 -38
- package/src/utils/async.ts +5 -5
- package/src/utils/errors.ts +19 -18
- package/src/utils/sse-keepalive.ts +28 -25
- package/src/workers/bootstrap.ts +75 -11
- package/src/workers/memory-consolidation.worker.ts +82 -91
- package/src/workers/organization-learning.worker.ts +14 -4
- package/src/workers/regular-chat-memory-digest.runner.ts +105 -67
- package/src/workers/skill-extraction.runner.ts +97 -61
- package/src/workers/utils/repo-structure-extractor.ts +13 -8
- package/src/workers/utils/thread-message-query.ts +24 -24
- package/src/workers/worker-utils.ts +23 -4
- package/src/effect/helpers.ts +0 -123
|
@@ -14,14 +14,11 @@ import { serverLogger } from '../config/logger'
|
|
|
14
14
|
import { recordIdToString } from '../db/record-id'
|
|
15
15
|
import { TABLES } from '../db/tables'
|
|
16
16
|
import { BadRequestError, ServiceError } from '../effect/errors'
|
|
17
|
-
import { makeEffectTryPromiseWithMessage } from '../effect/helpers'
|
|
18
17
|
import type { PlanAgentHeartbeatQueueRuntime } from '../queues/plan-agent-heartbeat.queue'
|
|
19
18
|
import { shouldPlanNodeUseVisibleTurn } from '../runtime/execution-plan-visibility'
|
|
20
19
|
import type { makePlanExecutorService } from './plan/plan-executor.service'
|
|
21
20
|
import type { makePlanRunService } from './plan/plan-run.service'
|
|
22
21
|
|
|
23
|
-
const tryGraphFullPromise = makeEffectTryPromiseWithMessage((message, cause) => new ServiceError({ message, cause }))
|
|
24
|
-
|
|
25
22
|
function classifyDispatchFailure(ownerType: string, error: unknown): PlanFailureClass {
|
|
26
23
|
const errorMessage = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase()
|
|
27
24
|
if (errorMessage.includes('timeout')) return 'timeout_exceeded'
|
|
@@ -82,15 +79,17 @@ export function routeGraphFullEffect<E>(params: { threadId: string; runId: strin
|
|
|
82
79
|
yield* Effect.forEach(
|
|
83
80
|
readyNodes,
|
|
84
81
|
(nodeRun) =>
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
82
|
+
deps.planExecutorService
|
|
83
|
+
.transitionNodeToRunning({ runId: params.runId, nodeId: nodeRun.nodeId })
|
|
84
|
+
.pipe(
|
|
85
|
+
Effect.mapError(
|
|
86
|
+
(cause) => new ServiceError({ message: 'Failed to transition plan node to running.', cause }),
|
|
87
|
+
),
|
|
88
|
+
),
|
|
89
89
|
{ concurrency: 'unbounded' },
|
|
90
90
|
)
|
|
91
91
|
|
|
92
92
|
if (visibleNodes.length > 0) {
|
|
93
|
-
const enqueuePlanAgentHeartbeatWake = deps.planAgentHeartbeatQueue.enqueuePlanAgentHeartbeatWake
|
|
94
93
|
const updatedRunForWake = yield* deps.planRunService.getRunById(params.runId)
|
|
95
94
|
yield* Effect.forEach(
|
|
96
95
|
visibleNodes,
|
|
@@ -101,9 +100,9 @@ export function routeGraphFullEffect<E>(params: { threadId: string; runId: strin
|
|
|
101
100
|
return
|
|
102
101
|
}
|
|
103
102
|
|
|
104
|
-
yield*
|
|
105
|
-
() =>
|
|
106
|
-
enqueuePlanAgentHeartbeatWake({
|
|
103
|
+
yield* Effect.tryPromise({
|
|
104
|
+
try: () =>
|
|
105
|
+
deps.planAgentHeartbeatQueue.enqueuePlanAgentHeartbeatWake({
|
|
107
106
|
organizationId: recordIdToString(updatedRunForWake.organizationId, TABLES.ORGANIZATION),
|
|
108
107
|
threadId: recordIdToString(updatedRunForWake.threadId, TABLES.THREAD),
|
|
109
108
|
runId: recordIdToString(updatedRunForWake.id, TABLES.PLAN_RUN),
|
|
@@ -111,8 +110,8 @@ export function routeGraphFullEffect<E>(params: { threadId: string; runId: strin
|
|
|
111
110
|
agentId: ns.owner.ref,
|
|
112
111
|
reason: 'graph-full-visible',
|
|
113
112
|
}),
|
|
114
|
-
'Failed to enqueue plan agent heartbeat wake.',
|
|
115
|
-
)
|
|
113
|
+
catch: (cause) => new ServiceError({ message: 'Failed to enqueue plan agent heartbeat wake.', cause }),
|
|
114
|
+
})
|
|
116
115
|
}),
|
|
117
116
|
{ concurrency: 'unbounded' },
|
|
118
117
|
)
|
|
@@ -158,31 +157,33 @@ export function routeGraphFullEffect<E>(params: { threadId: string; runId: strin
|
|
|
158
157
|
const nodeSpecRecord = nodeSpecs.find((ns) => ns.nodeId === nodeRun.nodeId)
|
|
159
158
|
|
|
160
159
|
if (settled.status === 'fulfilled') {
|
|
161
|
-
yield*
|
|
162
|
-
(
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
160
|
+
yield* deps.planExecutorService
|
|
161
|
+
.submitNodeResult({
|
|
162
|
+
threadId,
|
|
163
|
+
runId,
|
|
164
|
+
nodeId: settled.value.nodeId,
|
|
165
|
+
emittedBy: settled.value.ownerRef,
|
|
166
|
+
result: settled.value.result,
|
|
167
|
+
})
|
|
168
|
+
.pipe(
|
|
169
|
+
Effect.mapError((cause) => new ServiceError({ message: 'Failed to submit plan node result.', cause })),
|
|
170
|
+
)
|
|
172
171
|
} else {
|
|
173
172
|
serverLogger.warn`routeGraphFull: dispatch failed for node "${nodeRun.nodeId}": ${settled.reason}`
|
|
174
|
-
yield*
|
|
175
|
-
(
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
173
|
+
yield* deps.planExecutorService
|
|
174
|
+
.blockNodeOnDispatchFailure({
|
|
175
|
+
threadId,
|
|
176
|
+
runId,
|
|
177
|
+
nodeId: nodeRun.nodeId,
|
|
178
|
+
emittedBy: nodeSpecRecord?.owner.ref ?? 'unknown',
|
|
179
|
+
message: formatDispatchError(settled.reason),
|
|
180
|
+
failureClass: classifyDispatchFailure(nodeSpecRecord?.owner.executorType ?? 'agent', settled.reason),
|
|
181
|
+
})
|
|
182
|
+
.pipe(
|
|
183
|
+
Effect.mapError(
|
|
184
|
+
(cause) => new ServiceError({ message: 'Failed to block plan node on dispatch failure.', cause }),
|
|
185
|
+
),
|
|
186
|
+
)
|
|
186
187
|
}
|
|
187
188
|
}
|
|
188
189
|
}
|
|
@@ -11,7 +11,6 @@ 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'
|
|
15
14
|
import { DatabaseServiceTag } from '../effect/services'
|
|
16
15
|
import { unsafeDateFrom } from '../utils/date-time'
|
|
17
16
|
import type { makePlanRunService } from './plan/plan-run.service'
|
|
@@ -23,21 +22,10 @@ interface InstitutionalMemoryDeps {
|
|
|
23
22
|
}
|
|
24
23
|
|
|
25
24
|
class InstitutionalMemoryServiceError extends Schema.TaggedErrorClass<InstitutionalMemoryServiceError>()(
|
|
26
|
-
'InstitutionalMemoryServiceError',
|
|
25
|
+
'@lota-sdk/core/InstitutionalMemoryServiceError',
|
|
27
26
|
{ message: Schema.String, cause: Schema.Defect },
|
|
28
27
|
) {}
|
|
29
28
|
|
|
30
|
-
const effectTryInstitutionalMemoryPromise = makeEffectTryPromiseWithMessage(
|
|
31
|
-
(message, cause) => new InstitutionalMemoryServiceError({ message, cause }),
|
|
32
|
-
)
|
|
33
|
-
|
|
34
|
-
function tryInstitutionalMemoryPromise<A>(
|
|
35
|
-
message: string,
|
|
36
|
-
evaluate: () => PromiseLike<A> | Effect.Effect<A, unknown>,
|
|
37
|
-
): Effect.Effect<A, InstitutionalMemoryServiceError> {
|
|
38
|
-
return effectTryInstitutionalMemoryPromise(evaluate, message)
|
|
39
|
-
}
|
|
40
|
-
|
|
41
29
|
export function makeInstitutionalMemoryService(deps: InstitutionalMemoryDeps) {
|
|
42
30
|
const { db, planRunService } = deps
|
|
43
31
|
|
|
@@ -48,8 +36,8 @@ export function makeInstitutionalMemoryService(deps: InstitutionalMemoryDeps) {
|
|
|
48
36
|
confidence: number
|
|
49
37
|
sampleCount: number
|
|
50
38
|
}) =>
|
|
51
|
-
|
|
52
|
-
|
|
39
|
+
db
|
|
40
|
+
.create(
|
|
53
41
|
TABLES.INSTITUTIONAL_MEMORY,
|
|
54
42
|
{
|
|
55
43
|
organizationId: params.organizationId,
|
|
@@ -59,8 +47,12 @@ export function makeInstitutionalMemoryService(deps: InstitutionalMemoryDeps) {
|
|
|
59
47
|
sampleCount: params.sampleCount,
|
|
60
48
|
},
|
|
61
49
|
InstitutionalMemorySchema,
|
|
62
|
-
)
|
|
63
|
-
|
|
50
|
+
)
|
|
51
|
+
.pipe(
|
|
52
|
+
Effect.mapError(
|
|
53
|
+
(cause) => new InstitutionalMemoryServiceError({ message: 'Failed to persist institutional memory.', cause }),
|
|
54
|
+
),
|
|
55
|
+
)
|
|
64
56
|
|
|
65
57
|
const extractPatternsEffect = (params: { organizationId: string; runId: string }) =>
|
|
66
58
|
Effect.gen(function* () {
|
|
@@ -172,20 +164,26 @@ export function makeInstitutionalMemoryService(deps: InstitutionalMemoryDeps) {
|
|
|
172
164
|
const orgRef = ensureRecordId(params.organizationId, TABLES.ORGANIZATION)
|
|
173
165
|
const limit = params.limit ?? 10
|
|
174
166
|
const fetchLimit = Math.max(limit * 3, 30)
|
|
175
|
-
const records = yield*
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
LIMIT $fetchLimit`,
|
|
184
|
-
{ orgId: orgRef, fetchLimit },
|
|
185
|
-
),
|
|
186
|
-
InstitutionalMemorySchema,
|
|
167
|
+
const records = yield* db
|
|
168
|
+
.queryMany(
|
|
169
|
+
new BoundQuery(
|
|
170
|
+
`SELECT * FROM ${TABLES.INSTITUTIONAL_MEMORY}
|
|
171
|
+
WHERE organizationId = $orgId
|
|
172
|
+
ORDER BY confidence DESC, createdAt DESC
|
|
173
|
+
LIMIT $fetchLimit`,
|
|
174
|
+
{ orgId: orgRef, fetchLimit },
|
|
187
175
|
),
|
|
188
|
-
|
|
176
|
+
InstitutionalMemorySchema,
|
|
177
|
+
)
|
|
178
|
+
.pipe(
|
|
179
|
+
Effect.mapError(
|
|
180
|
+
(cause) =>
|
|
181
|
+
new InstitutionalMemoryServiceError({
|
|
182
|
+
message: `Failed to query institutional memory for organization ${params.organizationId}.`,
|
|
183
|
+
cause,
|
|
184
|
+
}),
|
|
185
|
+
),
|
|
186
|
+
)
|
|
189
187
|
|
|
190
188
|
const objectiveTerms = params.objective
|
|
191
189
|
.toLowerCase()
|
|
@@ -3,17 +3,19 @@ import { Cache, Context, Schema, Duration, Effect, Layer } from 'effect'
|
|
|
3
3
|
import { BoundQuery } from 'surrealdb'
|
|
4
4
|
import { z } from 'zod'
|
|
5
5
|
|
|
6
|
+
import { RuntimeBridgeTag } from '../ai-gateway/ai-gateway'
|
|
7
|
+
import type { RuntimeBridge } from '../ai-gateway/ai-gateway'
|
|
6
8
|
import { renderLearnedSkillInstructions } from '../ai/definitions'
|
|
7
9
|
import { serverLogger } from '../config/logger'
|
|
8
10
|
import { ensureRecordId, recordIdToString } from '../db/record-id'
|
|
9
11
|
import type { SurrealDBService } from '../db/service'
|
|
10
12
|
import { TABLES } from '../db/tables'
|
|
11
|
-
import {
|
|
13
|
+
import { ERROR_TAGS } from '../effect/errors'
|
|
12
14
|
import { DatabaseServiceTag, RuntimeConfigServiceTag } from '../effect/services'
|
|
13
15
|
import { ProviderEmbeddings } from '../embeddings/provider'
|
|
14
16
|
import { sha256HexFromParts } from '../utils/crypto'
|
|
15
17
|
import { nowDate } from '../utils/date-time'
|
|
16
|
-
import {
|
|
18
|
+
import { BackgroundWorkServiceTag } from './background-work.service'
|
|
17
19
|
|
|
18
20
|
const PROMOTION_MIN_USES = 5
|
|
19
21
|
const PROMOTION_MIN_SUCCESS_RATE = 0.6
|
|
@@ -56,25 +58,18 @@ const SearchResultRowSchema = z.object({
|
|
|
56
58
|
similarity: z.number(),
|
|
57
59
|
})
|
|
58
60
|
|
|
59
|
-
class LearnedSkillServiceError extends Schema.TaggedErrorClass<LearnedSkillServiceError>()(
|
|
60
|
-
|
|
61
|
-
cause: Schema.Defect,
|
|
62
|
-
|
|
61
|
+
class LearnedSkillServiceError extends Schema.TaggedErrorClass<LearnedSkillServiceError>()(
|
|
62
|
+
ERROR_TAGS.LearnedSkillServiceError,
|
|
63
|
+
{ message: Schema.String, cause: Schema.Defect },
|
|
64
|
+
) {}
|
|
63
65
|
|
|
64
66
|
class LearnedSkillNotFoundError extends Schema.TaggedErrorClass<LearnedSkillNotFoundError>()(
|
|
65
|
-
'LearnedSkillNotFoundError',
|
|
67
|
+
'@lota-sdk/core/LearnedSkillNotFoundError',
|
|
66
68
|
{ message: Schema.String },
|
|
67
69
|
) {}
|
|
68
70
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
)
|
|
72
|
-
|
|
73
|
-
function tryLearnedSkillPromise<A>(
|
|
74
|
-
message: string,
|
|
75
|
-
evaluate: () => PromiseLike<A> | Effect.Effect<A, unknown>,
|
|
76
|
-
): Effect.Effect<A, LearnedSkillServiceError> {
|
|
77
|
-
return effectTryLearnedSkillPromise(evaluate, message)
|
|
71
|
+
function toLearnedSkillServiceError(message: string, cause: unknown): LearnedSkillServiceError {
|
|
72
|
+
return new LearnedSkillServiceError({ message, cause })
|
|
78
73
|
}
|
|
79
74
|
|
|
80
75
|
interface CreateLearnedSkillInput {
|
|
@@ -116,13 +111,14 @@ interface RetrieveForTurnParams {
|
|
|
116
111
|
|
|
117
112
|
export function makeLearnedSkillService(
|
|
118
113
|
db: SurrealDBService,
|
|
119
|
-
options: { embeddingModel: string; openRouterApiKey?: string },
|
|
114
|
+
options: { embeddingModel: string; openRouterApiKey?: string; runPromise: RuntimeBridge['runPromise'] },
|
|
120
115
|
skillExistsCache: Cache.Cache<string, boolean, LearnedSkillServiceError>,
|
|
121
|
-
background: Context.Service.Shape<typeof
|
|
116
|
+
background: Context.Service.Shape<typeof BackgroundWorkServiceTag>,
|
|
122
117
|
) {
|
|
123
118
|
const embeddings = new ProviderEmbeddings({
|
|
124
119
|
modelId: options.embeddingModel,
|
|
125
120
|
openRouterApiKey: options.openRouterApiKey,
|
|
121
|
+
runPromise: options.runPromise,
|
|
126
122
|
})
|
|
127
123
|
|
|
128
124
|
const hasSkillsForAgent = (orgId: string, agentId: string) => Cache.get(skillExistsCache, `${orgId}:${agentId}`)
|
|
@@ -159,9 +155,9 @@ export function makeLearnedSkillService(
|
|
|
159
155
|
hash: input.hash,
|
|
160
156
|
}
|
|
161
157
|
|
|
162
|
-
const result = yield*
|
|
163
|
-
|
|
164
|
-
|
|
158
|
+
const result = yield* db
|
|
159
|
+
.create(TABLES.LEARNED_SKILL, data, LearnedSkillRowSchema)
|
|
160
|
+
.pipe(Effect.mapError((cause) => toLearnedSkillServiceError('Failed to create learned skill.', cause)))
|
|
165
161
|
yield* invalidateSkillExistsCache(input.organizationId, input.agentId)
|
|
166
162
|
return result
|
|
167
163
|
})
|
|
@@ -183,9 +179,11 @@ export function makeLearnedSkillService(
|
|
|
183
179
|
if (input.hash !== undefined) data.hash = input.hash
|
|
184
180
|
if (input.supersedes !== undefined) data.supersedes = ensureRecordId(input.supersedes, TABLES.LEARNED_SKILL)
|
|
185
181
|
|
|
186
|
-
const updated = yield*
|
|
187
|
-
|
|
188
|
-
|
|
182
|
+
const updated = yield* db
|
|
183
|
+
.update(TABLES.LEARNED_SKILL, ref, data, LearnedSkillRowSchema)
|
|
184
|
+
.pipe(
|
|
185
|
+
Effect.mapError((cause) => toLearnedSkillServiceError(`Failed to update learned skill ${skillId}.`, cause)),
|
|
186
|
+
)
|
|
189
187
|
if (!updated) {
|
|
190
188
|
return yield* new LearnedSkillNotFoundError({ message: `Learned skill ${skillId} not found` })
|
|
191
189
|
}
|
|
@@ -196,18 +194,20 @@ export function makeLearnedSkillService(
|
|
|
196
194
|
Effect.gen(function* () {
|
|
197
195
|
const ref = ensureRecordId(skillId, TABLES.LEARNED_SKILL)
|
|
198
196
|
const skill = yield* getById(skillId)
|
|
199
|
-
yield*
|
|
200
|
-
|
|
201
|
-
|
|
197
|
+
yield* db
|
|
198
|
+
.update(TABLES.LEARNED_SKILL, ref, { status: 'archived', archivedAt: nowDate() }, LearnedSkillRowSchema)
|
|
199
|
+
.pipe(
|
|
200
|
+
Effect.mapError((cause) => toLearnedSkillServiceError(`Failed to archive learned skill ${skillId}.`, cause)),
|
|
201
|
+
)
|
|
202
202
|
if (skill) {
|
|
203
203
|
yield* invalidateSkillExistsCache(recordIdToString(skill.organizationId), skill.agentId ?? null)
|
|
204
204
|
}
|
|
205
205
|
})
|
|
206
206
|
|
|
207
207
|
const getById = (skillId: string): Effect.Effect<LearnedSkillRow | null, LearnedSkillServiceError> =>
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
208
|
+
db
|
|
209
|
+
.findOne(TABLES.LEARNED_SKILL, { id: ensureRecordId(skillId, TABLES.LEARNED_SKILL) }, LearnedSkillRowSchema)
|
|
210
|
+
.pipe(Effect.mapError((cause) => toLearnedSkillServiceError(`Failed to load learned skill ${skillId}.`, cause)))
|
|
211
211
|
|
|
212
212
|
const searchForTurn = Effect.fn('LearnedSkillService.searchForTurn')(function* (params: RetrieveForTurnParams) {
|
|
213
213
|
const orgRef = ensureRecordId(params.orgId, TABLES.ORGANIZATION)
|
|
@@ -242,22 +242,26 @@ export function makeLearnedSkillService(
|
|
|
242
242
|
return []
|
|
243
243
|
}
|
|
244
244
|
|
|
245
|
-
const queryEmbedding = yield*
|
|
246
|
-
embeddings.embedQuery(params.query),
|
|
247
|
-
|
|
245
|
+
const queryEmbedding = yield* Effect.tryPromise({
|
|
246
|
+
try: () => embeddings.embedQuery(params.query),
|
|
247
|
+
catch: (cause) => toLearnedSkillServiceError('Failed to embed learned skill query.', cause),
|
|
248
|
+
}).pipe(Effect.withSpan('LearnedSkillService.embedQuery'))
|
|
248
249
|
yield* Effect.annotateCurrentSpan('embeddingLength', queryEmbedding.length)
|
|
249
250
|
if (queryEmbedding.length === 0) return []
|
|
250
251
|
|
|
251
|
-
const rows = yield*
|
|
252
|
-
|
|
252
|
+
const rows = yield* db
|
|
253
|
+
.query<unknown>(
|
|
253
254
|
new BoundQuery(sql, {
|
|
254
255
|
organizationId: orgRef,
|
|
255
256
|
embedding: queryEmbedding,
|
|
256
257
|
agentId: params.agentId,
|
|
257
258
|
minConfidence: params.minConfidence,
|
|
258
259
|
}),
|
|
259
|
-
)
|
|
260
|
-
|
|
260
|
+
)
|
|
261
|
+
.pipe(
|
|
262
|
+
Effect.mapError((cause) => toLearnedSkillServiceError('Failed to query learned skills.', cause)),
|
|
263
|
+
Effect.withSpan('LearnedSkillService.queryNearestSkills'),
|
|
264
|
+
)
|
|
261
265
|
const parsedRows = yield* Effect.try({
|
|
262
266
|
try: () => rows.map((row) => SearchResultRowSchema.parse(row)),
|
|
263
267
|
catch: (cause) =>
|
|
@@ -295,24 +299,32 @@ export function makeLearnedSkillService(
|
|
|
295
299
|
const recordUsage = (skillId: string) =>
|
|
296
300
|
Effect.gen(function* () {
|
|
297
301
|
const ref = ensureRecordId(skillId, TABLES.LEARNED_SKILL)
|
|
298
|
-
yield*
|
|
299
|
-
|
|
302
|
+
yield* db
|
|
303
|
+
.query<unknown>(
|
|
300
304
|
new BoundQuery(
|
|
301
305
|
`UPDATE ${TABLES.LEARNED_SKILL} SET usageCount += 1, lastUsedAt = time::now() WHERE id = $id`,
|
|
302
306
|
{ id: ref },
|
|
303
307
|
),
|
|
304
|
-
)
|
|
305
|
-
|
|
308
|
+
)
|
|
309
|
+
.pipe(
|
|
310
|
+
Effect.mapError((cause) =>
|
|
311
|
+
toLearnedSkillServiceError(`Failed to record usage for learned skill ${skillId}.`, cause),
|
|
312
|
+
),
|
|
313
|
+
)
|
|
306
314
|
})
|
|
307
315
|
|
|
308
316
|
const recordSuccess = (skillId: string) =>
|
|
309
317
|
Effect.gen(function* () {
|
|
310
318
|
const ref = ensureRecordId(skillId, TABLES.LEARNED_SKILL)
|
|
311
|
-
yield*
|
|
312
|
-
|
|
319
|
+
yield* db
|
|
320
|
+
.query<unknown>(
|
|
313
321
|
new BoundQuery(`UPDATE ${TABLES.LEARNED_SKILL} SET successCount += 1 WHERE id = $id`, { id: ref }),
|
|
314
|
-
)
|
|
315
|
-
|
|
322
|
+
)
|
|
323
|
+
.pipe(
|
|
324
|
+
Effect.mapError((cause) =>
|
|
325
|
+
toLearnedSkillServiceError(`Failed to record success for learned skill ${skillId}.`, cause),
|
|
326
|
+
),
|
|
327
|
+
)
|
|
316
328
|
})
|
|
317
329
|
|
|
318
330
|
const promoteIfEligible = (skillId: string) =>
|
|
@@ -326,14 +338,16 @@ export function makeLearnedSkillService(
|
|
|
326
338
|
if (successRate < PROMOTION_MIN_SUCCESS_RATE) return false
|
|
327
339
|
|
|
328
340
|
const ref = ensureRecordId(skillId, TABLES.LEARNED_SKILL)
|
|
329
|
-
yield*
|
|
330
|
-
|
|
341
|
+
yield* db
|
|
342
|
+
.update(
|
|
331
343
|
TABLES.LEARNED_SKILL,
|
|
332
344
|
ref,
|
|
333
345
|
{ status: 'verified', confidence: Math.min(skill.confidence + 0.1, 1.0) },
|
|
334
346
|
LearnedSkillRowSchema,
|
|
335
|
-
)
|
|
336
|
-
|
|
347
|
+
)
|
|
348
|
+
.pipe(
|
|
349
|
+
Effect.mapError((cause) => toLearnedSkillServiceError(`Failed to promote learned skill ${skillId}.`, cause)),
|
|
350
|
+
)
|
|
337
351
|
return true
|
|
338
352
|
})
|
|
339
353
|
|
|
@@ -353,14 +367,17 @@ export function makeLearnedSkillService(
|
|
|
353
367
|
LIMIT 1
|
|
354
368
|
`
|
|
355
369
|
|
|
356
|
-
const descEmbedding = yield*
|
|
357
|
-
embeddings.embedQuery(description),
|
|
358
|
-
|
|
370
|
+
const descEmbedding = yield* Effect.tryPromise({
|
|
371
|
+
try: () => embeddings.embedQuery(description),
|
|
372
|
+
catch: (cause) => toLearnedSkillServiceError('Failed to embed learned skill description.', cause),
|
|
373
|
+
})
|
|
359
374
|
if (descEmbedding.length === 0) return null
|
|
360
375
|
|
|
361
|
-
const rows = yield*
|
|
362
|
-
|
|
363
|
-
|
|
376
|
+
const rows = yield* db
|
|
377
|
+
.query<unknown>(new BoundQuery(sql, { organizationId: orgRef, embedding: descEmbedding }))
|
|
378
|
+
.pipe(
|
|
379
|
+
Effect.mapError((cause) => toLearnedSkillServiceError('Failed to query most similar learned skill.', cause)),
|
|
380
|
+
)
|
|
364
381
|
if (rows.length === 0) return null
|
|
365
382
|
return yield* Effect.try({
|
|
366
383
|
try: () => LearnedSkillRowSchema.parse(rows[0]),
|
|
@@ -370,8 +387,8 @@ export function makeLearnedSkillService(
|
|
|
370
387
|
})
|
|
371
388
|
|
|
372
389
|
const listForOrg = (orgId: string) =>
|
|
373
|
-
|
|
374
|
-
|
|
390
|
+
db
|
|
391
|
+
.queryMany(
|
|
375
392
|
new BoundQuery(
|
|
376
393
|
`SELECT *, type::string(id) AS id, type::string(organizationId) AS organizationId
|
|
377
394
|
FROM ${TABLES.LEARNED_SKILL}
|
|
@@ -381,15 +398,19 @@ export function makeLearnedSkillService(
|
|
|
381
398
|
{ organizationId: ensureRecordId(orgId, TABLES.ORGANIZATION) },
|
|
382
399
|
),
|
|
383
400
|
LearnedSkillRowSchema,
|
|
384
|
-
)
|
|
385
|
-
|
|
401
|
+
)
|
|
402
|
+
.pipe(
|
|
403
|
+
Effect.mapError((cause) =>
|
|
404
|
+
toLearnedSkillServiceError(`Failed to list learned skills for organization ${orgId}.`, cause),
|
|
405
|
+
),
|
|
406
|
+
)
|
|
386
407
|
|
|
387
408
|
const findByNameOrTag = (orgId: string, nameOrTag: string) =>
|
|
388
409
|
Effect.gen(function* () {
|
|
389
410
|
const orgRef = ensureRecordId(orgId, TABLES.ORGANIZATION)
|
|
390
411
|
const normalizedRef = nameOrTag.trim().toLowerCase()
|
|
391
|
-
const rows = yield*
|
|
392
|
-
|
|
412
|
+
const rows = yield* db
|
|
413
|
+
.queryMany(
|
|
393
414
|
new BoundQuery(
|
|
394
415
|
`SELECT *, type::string(id) AS id, type::string(organizationId) AS organizationId
|
|
395
416
|
FROM ${TABLES.LEARNED_SKILL}
|
|
@@ -401,16 +422,20 @@ export function makeLearnedSkillService(
|
|
|
401
422
|
{ organizationId: orgRef, nameRef: normalizedRef },
|
|
402
423
|
),
|
|
403
424
|
LearnedSkillRowSchema,
|
|
404
|
-
)
|
|
405
|
-
|
|
425
|
+
)
|
|
426
|
+
.pipe(
|
|
427
|
+
Effect.mapError((cause) =>
|
|
428
|
+
toLearnedSkillServiceError(`Failed to find learned skill by name or tag for org ${orgId}.`, cause),
|
|
429
|
+
),
|
|
430
|
+
)
|
|
406
431
|
return rows[0] ?? null
|
|
407
432
|
})
|
|
408
433
|
|
|
409
434
|
const findByHash = (orgId: string, hash: string): Effect.Effect<LearnedSkillRow | null, LearnedSkillServiceError> =>
|
|
410
435
|
Effect.gen(function* () {
|
|
411
436
|
const orgRef = ensureRecordId(orgId, TABLES.ORGANIZATION)
|
|
412
|
-
const rows = yield*
|
|
413
|
-
|
|
437
|
+
const rows = yield* db
|
|
438
|
+
.queryMany(
|
|
414
439
|
new BoundQuery(
|
|
415
440
|
`SELECT *, type::string(id) AS id, type::string(organizationId) AS organizationId
|
|
416
441
|
FROM ${TABLES.LEARNED_SKILL}
|
|
@@ -421,8 +446,12 @@ export function makeLearnedSkillService(
|
|
|
421
446
|
{ organizationId: orgRef, hash },
|
|
422
447
|
),
|
|
423
448
|
LearnedSkillRowSchema,
|
|
424
|
-
)
|
|
425
|
-
|
|
449
|
+
)
|
|
450
|
+
.pipe(
|
|
451
|
+
Effect.mapError((cause) =>
|
|
452
|
+
toLearnedSkillServiceError(`Failed to find learned skill by hash for org ${orgId}.`, cause),
|
|
453
|
+
),
|
|
454
|
+
)
|
|
426
455
|
return rows[0] ?? null
|
|
427
456
|
})
|
|
428
457
|
|
|
@@ -458,14 +487,15 @@ export const LearnedSkillServiceLive = Layer.effect(
|
|
|
458
487
|
Effect.gen(function* () {
|
|
459
488
|
const db = yield* DatabaseServiceTag
|
|
460
489
|
const runtimeConfig = yield* RuntimeConfigServiceTag
|
|
461
|
-
const background = yield*
|
|
490
|
+
const background = yield* BackgroundWorkServiceTag
|
|
491
|
+
const bridge = yield* RuntimeBridgeTag
|
|
462
492
|
const skillExistsCache = yield* Cache.make({
|
|
463
493
|
lookup: (key: string) =>
|
|
464
494
|
Effect.gen(function* () {
|
|
465
495
|
const [orgId, agentId] = key.split(':', 2) as [string, string]
|
|
466
496
|
const orgRef = ensureRecordId(orgId, TABLES.ORGANIZATION)
|
|
467
|
-
const rows = yield*
|
|
468
|
-
|
|
497
|
+
const rows = yield* db
|
|
498
|
+
.query<{ id: unknown }>(
|
|
469
499
|
new BoundQuery(
|
|
470
500
|
`SELECT id FROM ${TABLES.LEARNED_SKILL}
|
|
471
501
|
WHERE organizationId = $orgRef
|
|
@@ -474,8 +504,12 @@ export const LearnedSkillServiceLive = Layer.effect(
|
|
|
474
504
|
LIMIT 1`,
|
|
475
505
|
{ orgRef, agentId },
|
|
476
506
|
),
|
|
477
|
-
)
|
|
478
|
-
|
|
507
|
+
)
|
|
508
|
+
.pipe(
|
|
509
|
+
Effect.mapError((cause) =>
|
|
510
|
+
toLearnedSkillServiceError('Failed to check learned skill existence cache.', cause),
|
|
511
|
+
),
|
|
512
|
+
)
|
|
479
513
|
return rows.length > 0
|
|
480
514
|
}),
|
|
481
515
|
capacity: 256,
|
|
@@ -486,6 +520,7 @@ export const LearnedSkillServiceLive = Layer.effect(
|
|
|
486
520
|
{
|
|
487
521
|
embeddingModel: runtimeConfig.aiGateway.embeddingModel,
|
|
488
522
|
openRouterApiKey: runtimeConfig.aiGateway.openRouterApiKey,
|
|
523
|
+
runPromise: bridge.runPromise,
|
|
489
524
|
},
|
|
490
525
|
skillExistsCache,
|
|
491
526
|
background,
|
|
@@ -1,27 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Schema } from 'effect'
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
import { ERROR_TAGS } from '../../effect/errors'
|
|
4
|
+
|
|
5
|
+
export class MemoryServiceError extends Schema.TaggedErrorClass<MemoryServiceError>()(ERROR_TAGS.MemoryServiceError, {
|
|
4
6
|
message: Schema.String,
|
|
5
7
|
cause: Schema.Defect,
|
|
6
8
|
}) {}
|
|
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,14 +1,15 @@
|
|
|
1
|
-
import type { Context } from 'effect'
|
|
1
|
+
import type { Context, Effect as EffectType } from 'effect'
|
|
2
2
|
import { Cache, Duration, Effect } from 'effect'
|
|
3
3
|
|
|
4
|
+
import type { AiGatewayModels } from '../../ai-gateway/ai-gateway'
|
|
4
5
|
import { aiLogger } from '../../config/logger'
|
|
5
6
|
import { Memory } from '../../db/memory'
|
|
6
7
|
import type { SurrealDBService } from '../../db/service'
|
|
7
8
|
import type { HelperModelRuntime } from '../../runtime/helper-model'
|
|
8
9
|
import { ORG_SCOPE_PREFIX, scopeId } from '../../runtime/memory/memory-scope'
|
|
9
10
|
import type { ResolvedLotaRuntimeConfig } from '../../runtime/runtime-config'
|
|
10
|
-
import {
|
|
11
|
-
import type {
|
|
11
|
+
import { makeOrgMemoryAgentFactory, ORG_MEMORY_PROMPT } from '../../system-agents/memory.agent'
|
|
12
|
+
import type { BackgroundWorkServiceTag } from '../background-work.service'
|
|
12
13
|
|
|
13
14
|
const MAX_ORG_MEMORY_CLIENTS = 128
|
|
14
15
|
|
|
@@ -16,12 +17,15 @@ interface OrgMemoryDeps {
|
|
|
16
17
|
db: SurrealDBService
|
|
17
18
|
runtimeConfig: ResolvedLotaRuntimeConfig
|
|
18
19
|
helperModelRuntime: HelperModelRuntime
|
|
19
|
-
background: Context.Service.Shape<typeof
|
|
20
|
+
background: Context.Service.Shape<typeof BackgroundWorkServiceTag>
|
|
21
|
+
aiGatewayModels: AiGatewayModels
|
|
22
|
+
runPromise: <A, E>(effect: EffectType.Effect<A, E>) => Promise<A>
|
|
20
23
|
}
|
|
21
24
|
|
|
22
25
|
export type OrgMemoryCache = Cache.Cache<string, Memory>
|
|
23
26
|
|
|
24
27
|
export function makeOrgMemoryCache(deps: OrgMemoryDeps) {
|
|
28
|
+
const createAgent = makeOrgMemoryAgentFactory(deps.aiGatewayModels)
|
|
25
29
|
return Cache.make({
|
|
26
30
|
lookup: (cacheKey: string) =>
|
|
27
31
|
Effect.sync(() => {
|
|
@@ -32,8 +36,9 @@ export function makeOrgMemoryCache(deps: OrgMemoryDeps) {
|
|
|
32
36
|
runtimeConfig: deps.runtimeConfig,
|
|
33
37
|
helperModelRuntime: deps.helperModelRuntime,
|
|
34
38
|
background: deps.background,
|
|
39
|
+
runPromise: deps.runPromise,
|
|
35
40
|
},
|
|
36
|
-
{ createAgent
|
|
41
|
+
{ createAgent },
|
|
37
42
|
{ customPrompt: ORG_MEMORY_PROMPT },
|
|
38
43
|
)
|
|
39
44
|
}),
|