@lota-sdk/core 0.4.8 → 0.4.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +11 -12
- package/src/ai/embedding-cache.ts +96 -22
- package/src/ai-gateway/ai-gateway.ts +766 -223
- package/src/config/agent-defaults.ts +189 -75
- package/src/config/agent-types.ts +54 -4
- package/src/config/background-processing.ts +1 -1
- package/src/config/constants.ts +8 -2
- package/src/config/index.ts +0 -1
- package/src/config/logger.ts +299 -19
- package/src/config/thread-defaults.ts +40 -20
- package/src/create-runtime.ts +200 -449
- package/src/db/base.service.ts +52 -28
- package/src/db/cursor-pagination.ts +71 -30
- package/src/db/memory-query-builder.ts +2 -1
- package/src/db/memory-store.helpers.ts +4 -7
- package/src/db/memory-store.ts +868 -601
- package/src/db/memory.ts +396 -280
- package/src/db/record-id.ts +32 -10
- package/src/db/schema-fingerprint.ts +30 -12
- package/src/db/service-normalization.ts +288 -0
- package/src/db/service.ts +912 -779
- package/src/db/startup.ts +153 -68
- package/src/db/transaction-conflict.ts +15 -0
- package/src/effect/awaitable-effect.ts +96 -0
- package/src/effect/errors.ts +121 -0
- package/src/effect/helpers.ts +123 -0
- package/src/effect/index.ts +24 -0
- package/src/effect/layers.ts +238 -0
- package/src/effect/runtime-ref.ts +25 -0
- package/src/effect/runtime.ts +46 -0
- package/src/effect/services.ts +61 -0
- package/src/effect/zod.ts +43 -0
- package/src/embeddings/provider.ts +128 -83
- package/src/index.ts +48 -1
- package/src/openrouter/direct-provider.ts +11 -35
- package/src/queues/autonomous-job.queue.ts +117 -73
- package/src/queues/context-compaction.queue.ts +50 -17
- package/src/queues/delayed-node-promotion.queue.ts +46 -17
- package/src/queues/document-processor.queue.ts +52 -77
- package/src/queues/memory-consolidation.queue.ts +47 -32
- package/src/queues/organization-learning.queue.ts +26 -4
- package/src/queues/plan-agent-heartbeat.queue.ts +71 -24
- package/src/queues/plan-scheduler.queue.ts +97 -33
- package/src/queues/post-chat-memory.queue.ts +56 -26
- package/src/queues/queue-factory.ts +227 -59
- package/src/queues/standalone-worker.ts +39 -0
- package/src/queues/title-generation.queue.ts +45 -11
- package/src/redis/connection.ts +182 -113
- package/src/redis/index.ts +6 -8
- package/src/redis/org-memory-lock.ts +60 -27
- package/src/redis/redis-lease-lock.ts +200 -121
- package/src/redis/runtime-connection.ts +20 -0
- package/src/redis/stream-context.ts +92 -46
- package/src/runtime/agent-identity-overrides.ts +2 -2
- package/src/runtime/agent-runtime-policy.ts +5 -2
- package/src/runtime/agent-stream-helpers.ts +24 -9
- package/src/runtime/chat-run-orchestration.ts +102 -19
- package/src/runtime/chat-run-registry.ts +36 -2
- package/src/runtime/context-compaction/context-compaction-runtime.ts +107 -0
- package/src/runtime/{context-compaction.ts → context-compaction/context-compaction.ts} +161 -94
- package/src/runtime/domain-layer.ts +192 -0
- package/src/runtime/execution-plan-visibility.ts +2 -2
- package/src/runtime/execution-plan.ts +42 -15
- package/src/runtime/graph-designer.ts +16 -4
- package/src/runtime/helper-model.ts +139 -48
- package/src/runtime/index.ts +7 -8
- package/src/runtime/indexed-repositories-policy.ts +3 -3
- package/src/runtime/{memory-block.ts → memory/memory-block.ts} +50 -36
- package/src/runtime/{memory-digest-policy.ts → memory/memory-digest-policy.ts} +1 -1
- package/src/runtime/{memory-pipeline.ts → memory/memory-pipeline.ts} +54 -67
- package/src/runtime/{memory-prompts-fact.ts → memory/memory-prompts-fact.ts} +2 -2
- package/src/runtime/memory/memory-scope.ts +53 -0
- package/src/runtime/plugin-resolution.ts +124 -25
- package/src/runtime/plugin-types.ts +9 -1
- package/src/runtime/post-turn-side-effects.ts +177 -130
- package/src/runtime/retrieval-adapters.ts +40 -6
- package/src/runtime/runtime-accessors.ts +92 -0
- package/src/runtime/runtime-config.ts +150 -61
- package/src/runtime/runtime-extensions.ts +23 -25
- package/src/runtime/runtime-lifecycle.ts +124 -0
- package/src/runtime/runtime-services.ts +386 -0
- package/src/runtime/runtime-token.ts +47 -0
- package/src/runtime/social-chat/social-chat-agent-runner.ts +159 -0
- package/src/runtime/{social-chat-history.ts → social-chat/social-chat-history.ts} +51 -20
- package/src/runtime/social-chat/social-chat.ts +630 -0
- package/src/runtime/specialist-runner.ts +36 -10
- package/src/runtime/team-consultation/team-consultation-orchestrator.ts +433 -0
- package/src/runtime/{team-consultation-prompts.ts → team-consultation/team-consultation-prompts.ts} +6 -2
- package/src/runtime/thread-chat-helpers.ts +2 -2
- package/src/runtime/thread-plan-turn.ts +2 -1
- package/src/runtime/thread-turn-context.ts +183 -111
- package/src/runtime/turn-lifecycle.ts +93 -27
- package/src/services/agent-activity.service.ts +287 -203
- package/src/services/agent-executor.service.ts +253 -149
- package/src/services/artifact.service.ts +231 -149
- package/src/services/attachment.service.ts +171 -115
- package/src/services/autonomous-job.service.ts +890 -491
- package/src/services/background-work.service.ts +54 -0
- package/src/services/chat-run-registry.service.ts +13 -1
- package/src/services/context-compaction.service.ts +136 -86
- package/src/services/document-chunk.service.ts +151 -88
- package/src/services/execution-plan/execution-plan-approval.ts +26 -0
- package/src/services/execution-plan/execution-plan-context.ts +29 -0
- package/src/services/execution-plan/execution-plan-graph.ts +278 -0
- package/src/services/execution-plan/execution-plan-schedule.ts +84 -0
- package/src/services/execution-plan/execution-plan-spec.ts +75 -0
- package/src/services/execution-plan/execution-plan.service.ts +1041 -0
- package/src/services/feedback-loop.service.ts +132 -76
- package/src/services/global-orchestrator.service.ts +101 -168
- package/src/services/graph-full-routing.ts +193 -0
- package/src/services/index.ts +19 -21
- package/src/services/institutional-memory.service.ts +213 -125
- package/src/services/learned-skill.service.ts +368 -260
- package/src/services/memory/memory-conversation.ts +95 -0
- package/src/services/memory/memory-errors.ts +27 -0
- package/src/services/memory/memory-org-memory.ts +50 -0
- package/src/services/memory/memory-preseeded.ts +86 -0
- package/src/services/memory/memory-rerank.ts +297 -0
- package/src/services/{memory-utils.ts → memory/memory-utils.ts} +6 -5
- package/src/services/memory/memory.service.ts +674 -0
- package/src/services/memory/rerank.service.ts +201 -0
- package/src/services/monitoring-window.service.ts +92 -70
- package/src/services/mutating-approval.service.ts +62 -53
- package/src/services/node-workspace.service.ts +141 -98
- package/src/services/notification.service.ts +29 -16
- package/src/services/organization-member.service.ts +120 -66
- package/src/services/organization.service.ts +153 -77
- package/src/services/ownership-dispatcher.service.ts +456 -263
- package/src/services/plan/plan-agent-heartbeat.service.ts +234 -0
- package/src/services/plan/plan-agent-query.service.ts +322 -0
- package/src/services/{plan-approval.service.ts → plan/plan-approval.service.ts} +45 -22
- package/src/services/plan/plan-artifact.service.ts +60 -0
- package/src/services/plan/plan-builder.service.ts +76 -0
- package/src/services/plan/plan-checkpoint.service.ts +103 -0
- package/src/services/{plan-compiler.service.ts → plan/plan-compiler.service.ts} +26 -9
- package/src/services/plan/plan-completion-side-effects.ts +169 -0
- package/src/services/plan/plan-coordination.service.ts +181 -0
- package/src/services/plan/plan-cycle.service.ts +405 -0
- package/src/services/plan/plan-deadline.service.ts +533 -0
- package/src/services/plan/plan-event-delivery.service.ts +266 -0
- package/src/services/plan/plan-executor-context.ts +35 -0
- package/src/services/plan/plan-executor-graph.ts +522 -0
- package/src/services/plan/plan-executor-helpers.ts +307 -0
- package/src/services/plan/plan-executor-persistence.ts +209 -0
- package/src/services/plan/plan-executor.service.ts +1737 -0
- package/src/services/{plan-helpers.ts → plan/plan-helpers.ts} +1 -1
- package/src/services/{plan-run-data.ts → plan/plan-run-data.ts} +4 -4
- package/src/services/plan/plan-run-serialization.ts +15 -0
- package/src/services/plan/plan-run.service.ts +637 -0
- package/src/services/plan/plan-scheduler.service.ts +379 -0
- package/src/services/plan/plan-template.service.ts +224 -0
- package/src/services/plan/plan-transaction-events.ts +36 -0
- package/src/services/plan/plan-validator.service.ts +907 -0
- package/src/services/plan/plan-workspace.service.ts +131 -0
- package/src/services/plugin-executor.service.ts +102 -68
- package/src/services/quality-metrics.service.ts +112 -94
- package/src/services/queue-job.service.ts +288 -231
- package/src/services/recent-activity-title.service.ts +73 -36
- package/src/services/recent-activity.service.ts +274 -259
- package/src/services/skill-resolver.service.ts +38 -12
- package/src/services/social-chat-history.service.ts +190 -122
- package/src/services/system-executor.service.ts +96 -61
- package/src/services/thread/thread-active-run.ts +203 -0
- package/src/services/thread/thread-bootstrap.ts +385 -0
- package/src/services/thread/thread-listing.ts +199 -0
- package/src/services/thread/thread-memory-block.ts +130 -0
- package/src/services/thread/thread-message.service.ts +379 -0
- package/src/services/thread/thread-record-store.ts +155 -0
- package/src/services/thread/thread-title.service.ts +74 -0
- package/src/services/thread/thread-turn-execution.ts +280 -0
- package/src/services/thread/thread-turn-message-context.ts +73 -0
- package/src/services/thread/thread-turn-preparation.service.ts +1148 -0
- package/src/services/thread/thread-turn-streaming.ts +403 -0
- package/src/services/thread/thread-turn-tracing.ts +35 -0
- package/src/services/thread/thread-turn.ts +376 -0
- package/src/services/thread/thread.service.ts +344 -0
- package/src/services/user.service.ts +82 -32
- package/src/services/write-intent-validator.service.ts +63 -51
- package/src/storage/attachment-parser.ts +69 -27
- package/src/storage/attachment-storage.service.ts +334 -275
- package/src/storage/generated-document-storage.service.ts +66 -34
- package/src/system-agents/agent-result.ts +3 -1
- package/src/system-agents/context-compaction.agent.ts +3 -3
- package/src/system-agents/delegated-agent-factory.ts +159 -90
- package/src/system-agents/helper-agent-options.ts +1 -1
- package/src/system-agents/memory-reranker.agent.ts +3 -3
- package/src/system-agents/memory.agent.ts +3 -3
- package/src/system-agents/recent-activity-title-refiner.agent.ts +3 -3
- package/src/system-agents/regular-chat-memory-digest.agent.ts +3 -3
- package/src/system-agents/skill-extractor.agent.ts +3 -3
- package/src/system-agents/skill-manager.agent.ts +3 -3
- package/src/system-agents/thread-router.agent.ts +157 -113
- package/src/system-agents/title-generator.agent.ts +3 -3
- package/src/tools/execution-plan.tool.ts +241 -171
- package/src/tools/fetch-webpage.tool.ts +29 -18
- package/src/tools/firecrawl-client.ts +26 -6
- package/src/tools/index.ts +1 -0
- package/src/tools/memory-block.tool.ts +14 -6
- package/src/tools/plan-approval.tool.ts +57 -47
- package/src/tools/read-file-parts.tool.ts +44 -33
- package/src/tools/remember-memory.tool.ts +65 -45
- package/src/tools/search-web.tool.ts +33 -22
- package/src/tools/search.tool.ts +41 -29
- package/src/tools/team-think.tool.ts +125 -84
- package/src/tools/user-questions.tool.ts +4 -3
- package/src/tools/web-tool-shared.ts +6 -0
- package/src/utils/async.ts +25 -22
- package/src/utils/crypto.ts +21 -0
- package/src/utils/date-time.ts +40 -1
- package/src/utils/errors.ts +111 -20
- package/src/utils/hono-error-handler.ts +24 -39
- package/src/utils/index.ts +2 -1
- package/src/utils/null-proto-record.ts +41 -0
- package/src/utils/sse-keepalive.ts +124 -21
- package/src/workers/bootstrap.ts +164 -52
- package/src/workers/memory-consolidation.worker.ts +325 -237
- package/src/workers/organization-learning.worker.ts +50 -16
- package/src/workers/regular-chat-memory-digest.helpers.ts +28 -27
- package/src/workers/regular-chat-memory-digest.runner.ts +185 -114
- package/src/workers/skill-extraction.runner.ts +176 -93
- package/src/workers/utils/file-section-chunker.ts +8 -10
- package/src/workers/utils/repo-structure-extractor.ts +349 -260
- package/src/workers/utils/repomix-file-sections.ts +2 -2
- package/src/workers/utils/thread-message-query.ts +97 -38
- package/src/workers/worker-utils.ts +74 -31
- package/src/config/debug-logger.ts +0 -47
- package/src/config/search.ts +0 -3
- package/src/redis/connection-accessor.ts +0 -26
- package/src/runtime/agent-types.ts +0 -1
- package/src/runtime/context-compaction-runtime.ts +0 -87
- package/src/runtime/memory-scope.ts +0 -43
- package/src/runtime/social-chat-agent-runner.ts +0 -118
- package/src/runtime/social-chat.ts +0 -516
- package/src/runtime/team-consultation-orchestrator.ts +0 -272
- package/src/services/adaptive-playbook.service.ts +0 -152
- package/src/services/artifact-provenance.service.ts +0 -172
- package/src/services/chat-attachments.service.ts +0 -17
- package/src/services/context-compaction-runtime.singleton.ts +0 -13
- package/src/services/execution-plan.service.ts +0 -1118
- package/src/services/memory.service.ts +0 -914
- package/src/services/plan-agent-heartbeat.service.ts +0 -136
- package/src/services/plan-agent-query.service.ts +0 -267
- package/src/services/plan-artifact.service.ts +0 -50
- package/src/services/plan-builder.service.ts +0 -67
- package/src/services/plan-checkpoint.service.ts +0 -81
- package/src/services/plan-completion-side-effects.ts +0 -80
- package/src/services/plan-coordination.service.ts +0 -157
- package/src/services/plan-cycle.service.ts +0 -284
- package/src/services/plan-deadline.service.ts +0 -430
- package/src/services/plan-event-delivery.service.ts +0 -166
- package/src/services/plan-executor.service.ts +0 -1950
- package/src/services/plan-run.service.ts +0 -515
- package/src/services/plan-scheduler.service.ts +0 -240
- package/src/services/plan-template.service.ts +0 -177
- package/src/services/plan-validator.service.ts +0 -818
- package/src/services/plan-workspace.service.ts +0 -83
- package/src/services/rerank.service.ts +0 -156
- package/src/services/thread-message.service.ts +0 -275
- package/src/services/thread-plan-registry.service.ts +0 -22
- package/src/services/thread-title.service.ts +0 -39
- package/src/services/thread-turn-preparation.service.ts +0 -1147
- package/src/services/thread-turn.ts +0 -172
- package/src/services/thread.service.ts +0 -869
- package/src/utils/env.ts +0 -8
- /package/src/runtime/{context-compaction-constants.ts → context-compaction/context-compaction-constants.ts} +0 -0
- /package/src/runtime/{memory-format.ts → memory/memory-format.ts} +0 -0
- /package/src/runtime/{memory-prompts-parse.ts → memory/memory-prompts-parse.ts} +0 -0
- /package/src/runtime/{memory-prompts-update.ts → memory/memory-prompts-update.ts} +0 -0
- /package/src/runtime/{social-chat-prompts.ts → social-chat/social-chat-prompts.ts} +0 -0
- /package/src/services/{plan-node-spec.ts → plan/plan-node-spec.ts} +0 -0
- /package/src/services/{thread-constants.ts → thread/thread-constants.ts} +0 -0
- /package/src/services/{thread.types.ts → thread/thread.types.ts} +0 -0
|
@@ -1,28 +1,25 @@
|
|
|
1
1
|
import { recordIdStringSchema } from '@lota-sdk/shared'
|
|
2
|
+
import { Cache, Context, Schema, Duration, Effect, Layer } from 'effect'
|
|
2
3
|
import { BoundQuery } from 'surrealdb'
|
|
3
4
|
import { z } from 'zod'
|
|
4
5
|
|
|
5
6
|
import { renderLearnedSkillInstructions } from '../ai/definitions'
|
|
6
|
-
import { lotaDebugLogger } from '../config/debug-logger'
|
|
7
7
|
import { serverLogger } from '../config/logger'
|
|
8
8
|
import { ensureRecordId, recordIdToString } from '../db/record-id'
|
|
9
|
-
import {
|
|
9
|
+
import type { SurrealDBService } from '../db/service'
|
|
10
10
|
import { TABLES } from '../db/tables'
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
import { makeEffectTryPromiseWithMessage } from '../effect/helpers'
|
|
12
|
+
import { DatabaseServiceTag, RuntimeConfigServiceTag } from '../effect/services'
|
|
13
|
+
import { ProviderEmbeddings } from '../embeddings/provider'
|
|
14
|
+
import { sha256HexFromParts } from '../utils/crypto'
|
|
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
|
|
18
20
|
|
|
19
21
|
const ACTIVE_SKILL_FILTER = "AND status IN ['learned', 'verified', 'promoted'] AND archivedAt IS NONE"
|
|
20
22
|
const SKILL_EXISTS_TTL_SECONDS = 120
|
|
21
|
-
const SKILL_EXISTS_KEY_PREFIX = 'skill-exists'
|
|
22
|
-
|
|
23
|
-
function skillExistsKey(orgId: string, agentId: string): string {
|
|
24
|
-
return `${SKILL_EXISTS_KEY_PREFIX}:${orgId}:${agentId}`
|
|
25
|
-
}
|
|
26
23
|
|
|
27
24
|
const LearnedSkillRowSchema = z.object({
|
|
28
25
|
id: recordIdStringSchema,
|
|
@@ -59,7 +56,26 @@ const SearchResultRowSchema = z.object({
|
|
|
59
56
|
similarity: z.number(),
|
|
60
57
|
})
|
|
61
58
|
|
|
62
|
-
|
|
59
|
+
class LearnedSkillServiceError extends Schema.TaggedErrorClass<LearnedSkillServiceError>()('LearnedSkillServiceError', {
|
|
60
|
+
message: Schema.String,
|
|
61
|
+
cause: Schema.Defect,
|
|
62
|
+
}) {}
|
|
63
|
+
|
|
64
|
+
class LearnedSkillNotFoundError extends Schema.TaggedErrorClass<LearnedSkillNotFoundError>()(
|
|
65
|
+
'LearnedSkillNotFoundError',
|
|
66
|
+
{ message: Schema.String },
|
|
67
|
+
) {}
|
|
68
|
+
|
|
69
|
+
const effectTryLearnedSkillPromise = makeEffectTryPromiseWithMessage(
|
|
70
|
+
(message, cause) => new LearnedSkillServiceError({ message, cause }),
|
|
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)
|
|
78
|
+
}
|
|
63
79
|
|
|
64
80
|
interface CreateLearnedSkillInput {
|
|
65
81
|
name: string
|
|
@@ -98,124 +114,102 @@ interface RetrieveForTurnParams {
|
|
|
98
114
|
minConfidence: number
|
|
99
115
|
}
|
|
100
116
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
)
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
117
|
+
export function makeLearnedSkillService(
|
|
118
|
+
db: SurrealDBService,
|
|
119
|
+
options: { embeddingModel: string; openRouterApiKey?: string },
|
|
120
|
+
skillExistsCache: Cache.Cache<string, boolean, LearnedSkillServiceError>,
|
|
121
|
+
background: Context.Service.Shape<typeof BackgroundWorkService>,
|
|
122
|
+
) {
|
|
123
|
+
const embeddings = new ProviderEmbeddings({
|
|
124
|
+
modelId: options.embeddingModel,
|
|
125
|
+
openRouterApiKey: options.openRouterApiKey,
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
const hasSkillsForAgent = (orgId: string, agentId: string) => Cache.get(skillExistsCache, `${orgId}:${agentId}`)
|
|
129
|
+
|
|
130
|
+
const invalidateSkillExistsCache = (orgId: string, agentId: string | null) =>
|
|
131
|
+
Effect.gen(function* () {
|
|
132
|
+
const keys = yield* Cache.keys(skillExistsCache)
|
|
133
|
+
const orgPrefix = `${orgId}:`
|
|
134
|
+
const matchingKeys = [...keys].filter((key) => key.startsWith(orgPrefix))
|
|
135
|
+
if (matchingKeys.length > 0) {
|
|
136
|
+
yield* Effect.forEach(matchingKeys, (key) => Cache.invalidate(skillExistsCache, key))
|
|
137
|
+
}
|
|
138
|
+
if (agentId) {
|
|
139
|
+
yield* Cache.set(skillExistsCache, `${orgId}:${agentId}`, true)
|
|
140
|
+
}
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
const create = (input: CreateLearnedSkillInput) =>
|
|
144
|
+
Effect.gen(function* () {
|
|
145
|
+
const orgRef = ensureRecordId(input.organizationId, TABLES.ORGANIZATION)
|
|
146
|
+
|
|
147
|
+
const data: Record<string, unknown> = {
|
|
148
|
+
name: input.name,
|
|
149
|
+
description: input.description,
|
|
150
|
+
instructions: input.instructions,
|
|
151
|
+
triggers: input.triggers,
|
|
152
|
+
tags: input.tags,
|
|
153
|
+
examples: input.examples,
|
|
154
|
+
sourceType: input.sourceType,
|
|
155
|
+
organizationId: orgRef,
|
|
156
|
+
agentId: input.agentId,
|
|
157
|
+
confidence: input.confidence,
|
|
158
|
+
embedding: input.embedding,
|
|
159
|
+
hash: input.hash,
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const result = yield* tryLearnedSkillPromise('Failed to create learned skill.', () =>
|
|
163
|
+
db.create(TABLES.LEARNED_SKILL, data, LearnedSkillRowSchema),
|
|
164
|
+
)
|
|
165
|
+
yield* invalidateSkillExistsCache(input.organizationId, input.agentId)
|
|
166
|
+
return result
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
const update = (skillId: string, input: UpdateLearnedSkillInput) =>
|
|
170
|
+
Effect.gen(function* () {
|
|
171
|
+
const ref = ensureRecordId(skillId, TABLES.LEARNED_SKILL)
|
|
172
|
+
const data: Record<string, unknown> = {}
|
|
173
|
+
|
|
174
|
+
if (input.name !== undefined) data.name = input.name
|
|
175
|
+
if (input.description !== undefined) data.description = input.description
|
|
176
|
+
if (input.instructions !== undefined) data.instructions = input.instructions
|
|
177
|
+
if (input.triggers !== undefined) data.triggers = input.triggers
|
|
178
|
+
if (input.tags !== undefined) data.tags = input.tags
|
|
179
|
+
if (input.examples !== undefined) data.examples = input.examples
|
|
180
|
+
if (input.confidence !== undefined) data.confidence = input.confidence
|
|
181
|
+
if (input.version !== undefined) data.version = input.version
|
|
182
|
+
if (input.embedding !== undefined) data.embedding = input.embedding
|
|
183
|
+
if (input.hash !== undefined) data.hash = input.hash
|
|
184
|
+
if (input.supersedes !== undefined) data.supersedes = ensureRecordId(input.supersedes, TABLES.LEARNED_SKILL)
|
|
185
|
+
|
|
186
|
+
const updated = yield* tryLearnedSkillPromise(`Failed to update learned skill ${skillId}.`, () =>
|
|
187
|
+
db.update(TABLES.LEARNED_SKILL, ref, data, LearnedSkillRowSchema),
|
|
188
|
+
)
|
|
189
|
+
if (!updated) {
|
|
190
|
+
return yield* new LearnedSkillNotFoundError({ message: `Learned skill ${skillId} not found` })
|
|
191
|
+
}
|
|
192
|
+
return updated
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
const archive = (skillId: string) =>
|
|
196
|
+
Effect.gen(function* () {
|
|
197
|
+
const ref = ensureRecordId(skillId, TABLES.LEARNED_SKILL)
|
|
198
|
+
const skill = yield* getById(skillId)
|
|
199
|
+
yield* tryLearnedSkillPromise(`Failed to archive learned skill ${skillId}.`, () =>
|
|
200
|
+
db.update(TABLES.LEARNED_SKILL, ref, { status: 'archived', archivedAt: nowDate() }, LearnedSkillRowSchema),
|
|
201
|
+
)
|
|
202
|
+
if (skill) {
|
|
203
|
+
yield* invalidateSkillExistsCache(recordIdToString(skill.organizationId), skill.agentId ?? null)
|
|
204
|
+
}
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
const getById = (skillId: string): Effect.Effect<LearnedSkillRow | null, LearnedSkillServiceError> =>
|
|
208
|
+
tryLearnedSkillPromise(`Failed to load learned skill ${skillId}.`, () =>
|
|
209
|
+
db.findOne(TABLES.LEARNED_SKILL, { id: ensureRecordId(skillId, TABLES.LEARNED_SKILL) }, LearnedSkillRowSchema),
|
|
185
210
|
)
|
|
186
211
|
|
|
187
|
-
|
|
188
|
-
await redis.set(key, exists ? '1' : '0', 'EX', SKILL_EXISTS_TTL_SECONDS)
|
|
189
|
-
return exists
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
private async invalidateSkillExistsCache(orgId: string, agentId: string | null): Promise<void> {
|
|
193
|
-
const redis = getRedisConnection()
|
|
194
|
-
const pattern = `${SKILL_EXISTS_KEY_PREFIX}:${orgId}:*`
|
|
195
|
-
const keys = await redis.keys(pattern)
|
|
196
|
-
if (keys.length > 0) {
|
|
197
|
-
await redis.del(...keys)
|
|
198
|
-
}
|
|
199
|
-
// Also set the specific key if we know a skill was just created
|
|
200
|
-
if (agentId) {
|
|
201
|
-
await redis.set(skillExistsKey(orgId, agentId), '1', 'EX', SKILL_EXISTS_TTL_SECONDS)
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
async searchForTurn(params: RetrieveForTurnParams): Promise<SearchResultRow[]> {
|
|
206
|
-
const timer = lotaDebugLogger.timer('learned-skills')
|
|
207
|
-
|
|
208
|
-
const hasSkills = await this.hasSkillsForAgent(params.orgId, params.agentId)
|
|
209
|
-
if (!hasSkills) {
|
|
210
|
-
lotaDebugLogger.step('learned-skills: skipped — no skills for org+agent')
|
|
211
|
-
return []
|
|
212
|
-
}
|
|
213
|
-
timer.step('has-skills-check')
|
|
214
|
-
|
|
215
|
-
const queryEmbedding = await embeddings.embedQuery(params.query)
|
|
216
|
-
timer.step('embed-query')
|
|
217
|
-
if (queryEmbedding.length === 0) return []
|
|
218
|
-
|
|
212
|
+
const searchForTurn = Effect.fn('LearnedSkillService.searchForTurn')(function* (params: RetrieveForTurnParams) {
|
|
219
213
|
const orgRef = ensureRecordId(params.orgId, TABLES.ORGANIZATION)
|
|
220
214
|
const sql = `
|
|
221
215
|
SELECT
|
|
@@ -234,153 +228,267 @@ class LearnedSkillService {
|
|
|
234
228
|
ORDER BY similarity DESC
|
|
235
229
|
`
|
|
236
230
|
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
organizationId: orgRef,
|
|
240
|
-
embedding: queryEmbedding,
|
|
241
|
-
agentId: params.agentId,
|
|
242
|
-
minConfidence: params.minConfidence,
|
|
243
|
-
}),
|
|
231
|
+
const hasSkills = yield* hasSkillsForAgent(params.orgId, params.agentId).pipe(
|
|
232
|
+
Effect.withSpan('LearnedSkillService.checkSkillsAvailability'),
|
|
244
233
|
)
|
|
245
|
-
|
|
234
|
+
yield* Effect.annotateCurrentSpan({
|
|
235
|
+
orgId: params.orgId,
|
|
236
|
+
agentId: params.agentId,
|
|
237
|
+
limit: params.limit,
|
|
238
|
+
minConfidence: params.minConfidence,
|
|
239
|
+
skillsAvailable: hasSkills,
|
|
240
|
+
})
|
|
241
|
+
if (!hasSkills) {
|
|
242
|
+
return []
|
|
243
|
+
}
|
|
246
244
|
|
|
247
|
-
|
|
248
|
-
|
|
245
|
+
const queryEmbedding = yield* tryLearnedSkillPromise('Failed to embed learned skill query.', () =>
|
|
246
|
+
embeddings.embedQuery(params.query),
|
|
247
|
+
).pipe(Effect.withSpan('LearnedSkillService.embedQuery'))
|
|
248
|
+
yield* Effect.annotateCurrentSpan('embeddingLength', queryEmbedding.length)
|
|
249
|
+
if (queryEmbedding.length === 0) return []
|
|
249
250
|
|
|
250
|
-
|
|
251
|
-
|
|
251
|
+
const rows = yield* tryLearnedSkillPromise('Failed to query learned skills.', () =>
|
|
252
|
+
db.query<unknown>(
|
|
253
|
+
new BoundQuery(sql, {
|
|
254
|
+
organizationId: orgRef,
|
|
255
|
+
embedding: queryEmbedding,
|
|
256
|
+
agentId: params.agentId,
|
|
257
|
+
minConfidence: params.minConfidence,
|
|
258
|
+
}),
|
|
259
|
+
),
|
|
260
|
+
).pipe(Effect.withSpan('LearnedSkillService.queryNearestSkills'))
|
|
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)
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
const retrieveForTurn = Effect.fn('LearnedSkillService.retrieveForTurn')(function* (params: RetrieveForTurnParams) {
|
|
270
|
+
const results = yield* searchForTurn(params)
|
|
252
271
|
if (results.length === 0) return undefined
|
|
253
272
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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
|
+
}),
|
|
282
|
+
),
|
|
283
|
+
),
|
|
284
|
+
{ discard: true },
|
|
285
|
+
),
|
|
286
|
+
'learned-skill.recordUsage',
|
|
287
|
+
)
|
|
259
288
|
|
|
260
289
|
const section = renderLearnedSkillInstructions(
|
|
261
290
|
results.map((row) => ({ name: row.name, instructions: row.instructions })),
|
|
262
291
|
)
|
|
263
292
|
return section || undefined
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
const recordUsage = (skillId: string) =>
|
|
296
|
+
Effect.gen(function* () {
|
|
297
|
+
const ref = ensureRecordId(skillId, TABLES.LEARNED_SKILL)
|
|
298
|
+
yield* tryLearnedSkillPromise(`Failed to record usage for learned skill ${skillId}.`, () =>
|
|
299
|
+
db.query<unknown>(
|
|
300
|
+
new BoundQuery(
|
|
301
|
+
`UPDATE ${TABLES.LEARNED_SKILL} SET usageCount += 1, lastUsedAt = time::now() WHERE id = $id`,
|
|
302
|
+
{ id: ref },
|
|
303
|
+
),
|
|
304
|
+
),
|
|
305
|
+
)
|
|
306
|
+
})
|
|
307
|
+
|
|
308
|
+
const recordSuccess = (skillId: string) =>
|
|
309
|
+
Effect.gen(function* () {
|
|
310
|
+
const ref = ensureRecordId(skillId, TABLES.LEARNED_SKILL)
|
|
311
|
+
yield* tryLearnedSkillPromise(`Failed to record success for learned skill ${skillId}.`, () =>
|
|
312
|
+
db.query<unknown>(
|
|
313
|
+
new BoundQuery(`UPDATE ${TABLES.LEARNED_SKILL} SET successCount += 1 WHERE id = $id`, { id: ref }),
|
|
314
|
+
),
|
|
315
|
+
)
|
|
316
|
+
})
|
|
317
|
+
|
|
318
|
+
const promoteIfEligible = (skillId: string) =>
|
|
319
|
+
Effect.gen(function* () {
|
|
320
|
+
const skill = yield* getById(skillId)
|
|
321
|
+
if (!skill) return false
|
|
322
|
+
if (skill.status !== 'learned') return false
|
|
323
|
+
if (skill.usageCount < PROMOTION_MIN_USES) return false
|
|
324
|
+
|
|
325
|
+
const successRate = skill.successCount / skill.usageCount
|
|
326
|
+
if (successRate < PROMOTION_MIN_SUCCESS_RATE) return false
|
|
327
|
+
|
|
328
|
+
const ref = ensureRecordId(skillId, TABLES.LEARNED_SKILL)
|
|
329
|
+
yield* tryLearnedSkillPromise(`Failed to promote learned skill ${skillId}.`, () =>
|
|
330
|
+
db.update(
|
|
331
|
+
TABLES.LEARNED_SKILL,
|
|
332
|
+
ref,
|
|
333
|
+
{ status: 'verified', confidence: Math.min(skill.confidence + 0.1, 1.0) },
|
|
334
|
+
LearnedSkillRowSchema,
|
|
335
|
+
),
|
|
336
|
+
)
|
|
337
|
+
return true
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
const findMostSimilar = (orgId: string, description: string) =>
|
|
341
|
+
Effect.gen(function* () {
|
|
342
|
+
const orgRef = ensureRecordId(orgId, TABLES.ORGANIZATION)
|
|
343
|
+
const sql = `
|
|
344
|
+
SELECT *,
|
|
345
|
+
type::string(id) AS id,
|
|
346
|
+
type::string(organizationId) AS organizationId,
|
|
347
|
+
vector::similarity::cosine(embedding, $embedding) AS similarity
|
|
348
|
+
FROM ${TABLES.LEARNED_SKILL}
|
|
349
|
+
WHERE organizationId = $organizationId
|
|
350
|
+
${ACTIVE_SKILL_FILTER}
|
|
351
|
+
AND embedding <|3|> $embedding
|
|
352
|
+
ORDER BY similarity DESC
|
|
353
|
+
LIMIT 1
|
|
354
|
+
`
|
|
355
|
+
|
|
356
|
+
const descEmbedding = yield* tryLearnedSkillPromise('Failed to embed learned skill description.', () =>
|
|
357
|
+
embeddings.embedQuery(description),
|
|
358
|
+
)
|
|
359
|
+
if (descEmbedding.length === 0) return null
|
|
360
|
+
|
|
361
|
+
const rows = yield* tryLearnedSkillPromise('Failed to query most similar learned skill.', () =>
|
|
362
|
+
db.query<unknown>(new BoundQuery(sql, { organizationId: orgRef, embedding: descEmbedding })),
|
|
363
|
+
)
|
|
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
|
+
})
|
|
370
|
+
})
|
|
371
|
+
|
|
372
|
+
const listForOrg = (orgId: string) =>
|
|
373
|
+
tryLearnedSkillPromise(`Failed to list learned skills for organization ${orgId}.`, () =>
|
|
374
|
+
db.queryMany(
|
|
375
|
+
new BoundQuery(
|
|
376
|
+
`SELECT *, type::string(id) AS id, type::string(organizationId) AS organizationId
|
|
377
|
+
FROM ${TABLES.LEARNED_SKILL}
|
|
378
|
+
WHERE organizationId = $organizationId
|
|
379
|
+
${ACTIVE_SKILL_FILTER}
|
|
380
|
+
ORDER BY createdAt DESC`,
|
|
381
|
+
{ organizationId: ensureRecordId(orgId, TABLES.ORGANIZATION) },
|
|
382
|
+
),
|
|
383
|
+
LearnedSkillRowSchema,
|
|
337
384
|
),
|
|
338
|
-
LearnedSkillRowSchema,
|
|
339
385
|
)
|
|
340
|
-
}
|
|
341
386
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
387
|
+
const findByNameOrTag = (orgId: string, nameOrTag: string) =>
|
|
388
|
+
Effect.gen(function* () {
|
|
389
|
+
const orgRef = ensureRecordId(orgId, TABLES.ORGANIZATION)
|
|
390
|
+
const normalizedRef = nameOrTag.trim().toLowerCase()
|
|
391
|
+
const rows = yield* tryLearnedSkillPromise(`Failed to find learned skill by name or tag for org ${orgId}.`, () =>
|
|
392
|
+
db.queryMany(
|
|
393
|
+
new BoundQuery(
|
|
394
|
+
`SELECT *, type::string(id) AS id, type::string(organizationId) AS organizationId
|
|
395
|
+
FROM ${TABLES.LEARNED_SKILL}
|
|
396
|
+
WHERE organizationId = $organizationId
|
|
397
|
+
${ACTIVE_SKILL_FILTER}
|
|
398
|
+
AND (string::lowercase(name) = $nameRef OR $nameRef IN tags)
|
|
399
|
+
ORDER BY confidence DESC
|
|
400
|
+
LIMIT 1`,
|
|
401
|
+
{ organizationId: orgRef, nameRef: normalizedRef },
|
|
402
|
+
),
|
|
403
|
+
LearnedSkillRowSchema,
|
|
404
|
+
),
|
|
405
|
+
)
|
|
406
|
+
return rows[0] ?? null
|
|
407
|
+
})
|
|
408
|
+
|
|
409
|
+
const findByHash = (orgId: string, hash: string): Effect.Effect<LearnedSkillRow | null, LearnedSkillServiceError> =>
|
|
410
|
+
Effect.gen(function* () {
|
|
411
|
+
const orgRef = ensureRecordId(orgId, TABLES.ORGANIZATION)
|
|
412
|
+
const rows = yield* tryLearnedSkillPromise(`Failed to find learned skill by hash for org ${orgId}.`, () =>
|
|
413
|
+
db.queryMany(
|
|
414
|
+
new BoundQuery(
|
|
415
|
+
`SELECT *, type::string(id) AS id, type::string(organizationId) AS organizationId
|
|
416
|
+
FROM ${TABLES.LEARNED_SKILL}
|
|
417
|
+
WHERE organizationId = $organizationId
|
|
418
|
+
AND hash = $hash
|
|
419
|
+
${ACTIVE_SKILL_FILTER}
|
|
420
|
+
LIMIT 1`,
|
|
421
|
+
{ organizationId: orgRef, hash },
|
|
422
|
+
),
|
|
423
|
+
LearnedSkillRowSchema,
|
|
424
|
+
),
|
|
425
|
+
)
|
|
426
|
+
return rows[0] ?? null
|
|
427
|
+
})
|
|
428
|
+
|
|
429
|
+
function generateHash(description: string, instructions: string): string {
|
|
430
|
+
return sha256HexFromParts([description.trim().toLowerCase(), instructions.trim().toLowerCase()])
|
|
347
431
|
}
|
|
348
432
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
)
|
|
365
|
-
return rows[0] ?? null
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
async findByHash(orgId: string, hash: string): Promise<LearnedSkillRow | null> {
|
|
369
|
-
const orgRef = ensureRecordId(orgId, TABLES.ORGANIZATION)
|
|
370
|
-
const rows = await databaseService.queryMany(
|
|
371
|
-
new BoundQuery(
|
|
372
|
-
`SELECT *, type::string(id) AS id, type::string(organizationId) AS organizationId
|
|
373
|
-
FROM ${TABLES.LEARNED_SKILL}
|
|
374
|
-
WHERE organizationId = $organizationId
|
|
375
|
-
AND hash = $hash
|
|
376
|
-
${ACTIVE_SKILL_FILTER}
|
|
377
|
-
LIMIT 1`,
|
|
378
|
-
{ organizationId: orgRef, hash },
|
|
379
|
-
),
|
|
380
|
-
LearnedSkillRowSchema,
|
|
381
|
-
)
|
|
382
|
-
return rows[0] ?? null
|
|
433
|
+
return {
|
|
434
|
+
create,
|
|
435
|
+
update,
|
|
436
|
+
archive,
|
|
437
|
+
getById,
|
|
438
|
+
searchForTurn,
|
|
439
|
+
retrieveForTurn,
|
|
440
|
+
recordUsage,
|
|
441
|
+
recordSuccess,
|
|
442
|
+
promoteIfEligible,
|
|
443
|
+
findMostSimilar,
|
|
444
|
+
listForOrg,
|
|
445
|
+
generateHash,
|
|
446
|
+
findByNameOrTag,
|
|
447
|
+
findByHash,
|
|
383
448
|
}
|
|
384
449
|
}
|
|
385
450
|
|
|
386
|
-
export
|
|
451
|
+
export class LearnedSkillServiceTag extends Context.Service<
|
|
452
|
+
LearnedSkillServiceTag,
|
|
453
|
+
ReturnType<typeof makeLearnedSkillService>
|
|
454
|
+
>()('@lota-sdk/core/LearnedSkillService') {}
|
|
455
|
+
|
|
456
|
+
export const LearnedSkillServiceLive = Layer.effect(
|
|
457
|
+
LearnedSkillServiceTag,
|
|
458
|
+
Effect.gen(function* () {
|
|
459
|
+
const db = yield* DatabaseServiceTag
|
|
460
|
+
const runtimeConfig = yield* RuntimeConfigServiceTag
|
|
461
|
+
const background = yield* BackgroundWorkService
|
|
462
|
+
const skillExistsCache = yield* Cache.make({
|
|
463
|
+
lookup: (key: string) =>
|
|
464
|
+
Effect.gen(function* () {
|
|
465
|
+
const [orgId, agentId] = key.split(':', 2) as [string, string]
|
|
466
|
+
const orgRef = ensureRecordId(orgId, TABLES.ORGANIZATION)
|
|
467
|
+
const rows = yield* tryLearnedSkillPromise('Failed to check learned skill existence cache.', () =>
|
|
468
|
+
db.query<{ id: unknown }>(
|
|
469
|
+
new BoundQuery(
|
|
470
|
+
`SELECT id FROM ${TABLES.LEARNED_SKILL}
|
|
471
|
+
WHERE organizationId = $orgRef
|
|
472
|
+
${ACTIVE_SKILL_FILTER}
|
|
473
|
+
AND (agentId IS NONE OR agentId = $agentId)
|
|
474
|
+
LIMIT 1`,
|
|
475
|
+
{ orgRef, agentId },
|
|
476
|
+
),
|
|
477
|
+
),
|
|
478
|
+
)
|
|
479
|
+
return rows.length > 0
|
|
480
|
+
}),
|
|
481
|
+
capacity: 256,
|
|
482
|
+
timeToLive: Duration.seconds(SKILL_EXISTS_TTL_SECONDS),
|
|
483
|
+
})
|
|
484
|
+
return makeLearnedSkillService(
|
|
485
|
+
db,
|
|
486
|
+
{
|
|
487
|
+
embeddingModel: runtimeConfig.aiGateway.embeddingModel,
|
|
488
|
+
openRouterApiKey: runtimeConfig.aiGateway.openRouterApiKey,
|
|
489
|
+
},
|
|
490
|
+
skillExistsCache,
|
|
491
|
+
background,
|
|
492
|
+
)
|
|
493
|
+
}),
|
|
494
|
+
)
|