@lota-sdk/core 0.1.14 → 0.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/infrastructure/schema/00_identity.surql +0 -2
- package/infrastructure/schema/01_memory.surql +1 -1
- package/infrastructure/schema/02_execution_plan.surql +62 -1
- package/infrastructure/schema/03_learned_skill.surql +1 -1
- package/infrastructure/schema/06_playbook.surql +25 -0
- package/infrastructure/schema/07_institutional_memory.surql +13 -0
- package/infrastructure/schema/08_quality_metrics.surql +17 -0
- package/package.json +9 -8
- package/src/ai/definitions.ts +80 -2
- package/src/ai/embedding-cache.ts +7 -6
- package/src/ai/index.ts +0 -1
- package/src/bifrost/bifrost.ts +14 -14
- package/src/config/agent-defaults.ts +32 -22
- package/src/config/agent-types.ts +11 -0
- package/src/config/constants.ts +2 -14
- package/src/config/debug-logger.ts +5 -1
- package/src/config/index.ts +3 -0
- package/src/config/logger.ts +7 -9
- package/src/config/model-constants.ts +16 -34
- package/src/config/search.ts +1 -15
- package/src/create-runtime.ts +453 -0
- package/src/db/cursor-pagination.ts +3 -6
- package/src/db/index.ts +2 -0
- package/src/db/memory-store.rows.ts +7 -7
- package/src/db/memory-store.ts +24 -24
- package/src/db/memory.ts +18 -16
- package/src/db/schema-fingerprint.ts +1 -0
- package/src/db/service.ts +193 -122
- package/src/db/startup.ts +9 -13
- package/src/db/surreal-mutation.ts +43 -0
- package/src/db/tables.ts +7 -0
- package/src/db/workstream-message-row.ts +15 -0
- package/src/embeddings/provider.ts +1 -1
- package/src/index.ts +1 -1
- package/src/queues/context-compaction.queue.ts +17 -52
- package/src/queues/delayed-node-promotion.queue.ts +41 -0
- package/src/queues/document-processor.queue.ts +7 -7
- package/src/queues/index.ts +3 -0
- package/src/queues/memory-consolidation.queue.ts +18 -54
- package/src/queues/plan-scheduler.queue.ts +97 -0
- package/src/queues/post-chat-memory.queue.ts +15 -60
- package/src/queues/queue-factory.ts +100 -0
- package/src/queues/recent-activity-title-refinement.queue.ts +15 -54
- package/src/queues/regular-chat-memory-digest.queue.ts +16 -55
- package/src/queues/skill-extraction.queue.ts +15 -50
- package/src/queues/workstream-title-generation.queue.ts +15 -51
- package/src/redis/connection.ts +12 -3
- package/src/redis/index.ts +2 -1
- package/src/redis/org-memory-lock.ts +1 -1
- package/src/redis/redis-lease-lock.ts +41 -8
- package/src/redis/stream-context.ts +11 -0
- package/src/runtime/agent-runtime-policy.ts +106 -21
- package/src/runtime/agent-stream-helpers.ts +2 -1
- package/src/runtime/approval-continuation.ts +12 -6
- package/src/runtime/context-compaction-constants.ts +1 -1
- package/src/runtime/context-compaction-runtime.ts +7 -5
- package/src/runtime/context-compaction.ts +40 -97
- package/src/runtime/execution-plan.ts +23 -19
- package/src/runtime/graph-designer.ts +15 -0
- package/src/runtime/helper-model.ts +10 -196
- package/src/runtime/index.ts +14 -1
- package/src/runtime/llm-content.ts +1 -1
- package/src/runtime/memory-block.ts +11 -12
- package/src/runtime/memory-pipeline.ts +26 -10
- package/src/runtime/plugin-resolution.ts +35 -0
- package/src/runtime/plugin-types.ts +73 -1
- package/src/runtime/retrieval-adapters.ts +1 -1
- package/src/runtime/runtime-config.ts +25 -12
- package/src/runtime/runtime-extensions.ts +91 -15
- package/src/runtime/runtime-worker-registry.ts +6 -0
- package/src/runtime/team-consultation-orchestrator.ts +45 -28
- package/src/runtime/team-consultation-prompts.ts +11 -2
- package/src/runtime/title-helpers.ts +11 -4
- package/src/runtime/workstream-chat-helpers.ts +6 -7
- package/src/runtime/workstream-routing-policy.ts +0 -30
- package/src/runtime/workstream-state.ts +17 -7
- package/src/services/adaptive-playbook.service.ts +152 -0
- package/src/services/agent-executor.service.ts +293 -0
- package/src/services/artifact-provenance.service.ts +172 -0
- package/src/services/attachment.service.ts +7 -12
- package/src/services/context-compaction.service.ts +75 -58
- package/src/services/context-enrichment.service.ts +33 -0
- package/src/services/coordination-registry.service.ts +117 -0
- package/src/services/document-chunk.service.ts +38 -33
- package/src/services/domain-agent-executor.service.ts +71 -0
- package/src/services/execution-plan.service.ts +271 -50
- package/src/services/feedback-loop.service.ts +96 -0
- package/src/services/global-orchestrator.service.ts +148 -0
- package/src/services/index.ts +26 -0
- package/src/services/institutional-memory.service.ts +145 -0
- package/src/services/learned-skill.service.ts +30 -15
- package/src/services/memory-assessment.service.ts +3 -2
- package/src/services/{memory.utils.ts → memory-utils.ts} +4 -13
- package/src/services/memory.service.ts +55 -69
- package/src/services/monitoring-window.service.ts +86 -0
- package/src/services/mutating-approval.service.ts +1 -1
- package/src/services/node-workspace.service.ts +155 -0
- package/src/services/notification.service.ts +39 -0
- package/src/services/organization-member.service.ts +12 -5
- package/src/services/organization.service.ts +5 -5
- package/src/services/ownership-dispatcher.service.ts +403 -0
- package/src/services/plan-approval.service.ts +1 -1
- package/src/services/plan-artifact.service.ts +1 -0
- package/src/services/plan-builder.service.ts +1 -0
- package/src/services/plan-checkpoint.service.ts +30 -2
- package/src/services/plan-compiler.service.ts +5 -0
- package/src/services/plan-coordination.service.ts +152 -0
- package/src/services/plan-cycle.service.ts +284 -0
- package/src/services/plan-deadline.service.ts +287 -0
- package/src/services/plan-executor.service.ts +386 -58
- package/src/services/plan-helpers.ts +15 -0
- package/src/services/plan-run.service.ts +41 -7
- package/src/services/plan-scheduler.service.ts +240 -0
- package/src/services/plan-template.service.ts +117 -0
- package/src/services/plan-validator.service.ts +87 -20
- package/src/services/plan-workspace.service.ts +83 -0
- package/src/services/playbook-registry.service.ts +67 -0
- package/src/services/plugin-executor.service.ts +103 -0
- package/src/services/quality-metrics.service.ts +132 -0
- package/src/services/recent-activity-title.service.ts +3 -10
- package/src/services/recent-activity.service.ts +33 -43
- package/src/services/skill-resolver.service.ts +19 -0
- package/src/services/system-executor.service.ts +105 -0
- package/src/services/workstream-message.service.ts +29 -41
- package/src/services/workstream-plan-registry.service.ts +22 -0
- package/src/services/workstream-title.service.ts +3 -9
- package/src/services/{workstream-turn-preparation.ts → workstream-turn-preparation.service.ts} +428 -373
- package/src/services/workstream-turn.ts +2 -2
- package/src/services/workstream.service.ts +55 -65
- package/src/services/workstream.types.ts +10 -19
- package/src/services/write-intent-validator.service.ts +81 -0
- package/src/storage/attachment-parser.ts +1 -1
- package/src/storage/attachment-storage.service.ts +4 -4
- package/src/storage/{attachments.utils.ts → attachment-utils.ts} +2 -5
- package/src/storage/generated-document-storage.service.ts +3 -2
- package/src/storage/index.ts +2 -2
- package/src/system-agents/{context-compacter.agent.ts → context-compaction.agent.ts} +4 -4
- package/src/system-agents/delegated-agent-factory.ts +5 -2
- package/src/system-agents/index.ts +8 -0
- 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 +1 -1
- package/src/tools/execution-plan.tool.ts +17 -19
- package/src/tools/fetch-webpage.tool.ts +20 -18
- package/src/tools/index.ts +2 -3
- package/src/tools/read-file-parts.tool.ts +1 -1
- package/src/tools/search-web.tool.ts +18 -15
- package/src/tools/{search-tools.ts → search.tool.ts} +1 -1
- package/src/tools/team-think.tool.ts +14 -8
- package/src/tools/{tool-contract.ts → tool-contracts.ts} +9 -2
- package/src/utils/async.ts +3 -2
- package/src/utils/date-time.ts +4 -32
- package/src/utils/env.ts +8 -0
- package/src/utils/errors.ts +47 -0
- package/src/utils/hono-error-handler.ts +1 -2
- package/src/utils/index.ts +19 -2
- package/src/utils/string.ts +128 -1
- package/src/workers/bootstrap.ts +2 -2
- package/src/workers/index.ts +1 -0
- package/src/workers/memory-consolidation.worker.ts +12 -12
- package/src/workers/regular-chat-memory-digest.helpers.ts +2 -7
- package/src/workers/regular-chat-memory-digest.runner.ts +11 -105
- package/src/workers/skill-extraction.runner.ts +8 -102
- package/src/workers/utils/file-section-chunker.ts +6 -3
- package/src/workers/utils/repomix-file-sections.ts +2 -2
- package/src/workers/utils/sandbox-error.ts +11 -2
- package/src/workers/utils/workstream-message-query.ts +97 -0
- package/src/workers/worker-utils.ts +6 -2
- package/src/runtime/retrieval-pipeline.ts +0 -3
- package/src/runtime.ts +0 -387
- package/src/tools/log-hello-world.tool.ts +0 -17
- package/src/utils/error.ts +0 -10
- /package/src/services/{context-compaction-runtime.ts → context-compaction-runtime.singleton.ts} +0 -0
- /package/src/storage/{attachments.types.ts → attachment-types.ts} +0 -0
|
@@ -3,6 +3,8 @@ import type {
|
|
|
3
3
|
CreateExecutionPlanArgs,
|
|
4
4
|
ExecutionPlanToolResultData,
|
|
5
5
|
GetActiveExecutionPlanArgs,
|
|
6
|
+
ListExecutionPlansSummary,
|
|
7
|
+
ListExecutionPlansToolResultData,
|
|
6
8
|
PlanNodeRunRecord,
|
|
7
9
|
PlanNodeSpecRecord,
|
|
8
10
|
PlanRunRecord,
|
|
@@ -22,6 +24,7 @@ import {
|
|
|
22
24
|
} from '@lota-sdk/shared'
|
|
23
25
|
import { RecordId } from 'surrealdb'
|
|
24
26
|
|
|
27
|
+
import { serverLogger } from '../config/logger'
|
|
25
28
|
import type { RecordIdInput } from '../db/record-id'
|
|
26
29
|
import { ensureRecordId, recordIdToString } from '../db/record-id'
|
|
27
30
|
import { databaseService } from '../db/service'
|
|
@@ -29,13 +32,27 @@ import type { DatabaseTransaction } from '../db/service'
|
|
|
29
32
|
import { TABLES } from '../db/tables'
|
|
30
33
|
import { readApprovalContinuationResponse } from '../runtime/approval-continuation'
|
|
31
34
|
import { extractMessageText } from '../runtime/workstream-chat-helpers'
|
|
35
|
+
import { toDatabaseDateTime } from '../utils/date-time'
|
|
36
|
+
import { contextEnrichmentService } from './context-enrichment.service'
|
|
37
|
+
import { ownershipDispatcherService } from './ownership-dispatcher.service'
|
|
32
38
|
import { planBuilderService } from './plan-builder.service'
|
|
33
39
|
import type { CompiledPlanNode } from './plan-compiler.service'
|
|
34
40
|
import { planCompilerService } from './plan-compiler.service'
|
|
35
41
|
import { planExecutorService } from './plan-executor.service'
|
|
36
42
|
import { planRunService } from './plan-run.service'
|
|
43
|
+
import { planSchedulerService } from './plan-scheduler.service'
|
|
37
44
|
import { planValidatorService } from './plan-validator.service'
|
|
38
45
|
|
|
46
|
+
export type ExecutionPlanDispatchMode = 'deferred' | 'stable-boundary'
|
|
47
|
+
|
|
48
|
+
const TOOL_RESULT_SERIALIZE_OPTIONS = {
|
|
49
|
+
includeEvents: true,
|
|
50
|
+
includeArtifacts: true,
|
|
51
|
+
includeApprovals: true,
|
|
52
|
+
includeCheckpoints: true,
|
|
53
|
+
includeValidationIssues: true,
|
|
54
|
+
} as const
|
|
55
|
+
|
|
39
56
|
function buildToolResult(params: {
|
|
40
57
|
action: ExecutionPlanToolResultData['action']
|
|
41
58
|
plan: SerializableExecutionPlan | null
|
|
@@ -68,15 +85,23 @@ function toSpecData(spec: PlanSpecRecord, patch: Partial<PlanSpecRecord> & { rep
|
|
|
68
85
|
schemaRegistry: patch.schemaRegistry ? structuredClone(patch.schemaRegistry) : structuredClone(spec.schemaRegistry),
|
|
69
86
|
edges: patch.edges ? [...patch.edges] : [...spec.edges],
|
|
70
87
|
entryNodeIds: patch.entryNodeIds ? [...patch.entryNodeIds] : [...spec.entryNodeIds],
|
|
88
|
+
executionMode: patch.executionMode ?? spec.executionMode,
|
|
89
|
+
...(patch.schedule !== undefined ? { schedule: patch.schedule } : spec.schedule ? { schedule: spec.schedule } : {}),
|
|
90
|
+
...(patch.dependencies !== undefined
|
|
91
|
+
? { dependencies: patch.dependencies }
|
|
92
|
+
: spec.dependencies
|
|
93
|
+
? { dependencies: spec.dependencies }
|
|
94
|
+
: {}),
|
|
95
|
+
...(spec.contextEnrichments ? { contextEnrichments: spec.contextEnrichments } : {}),
|
|
71
96
|
...(patch.replacedSpecId
|
|
72
97
|
? { replacedSpecId: ensureRecordId(patch.replacedSpecId, TABLES.PLAN_SPEC) }
|
|
73
98
|
: spec.replacedSpecId
|
|
74
99
|
? { replacedSpecId: ensureRecordId(spec.replacedSpecId, TABLES.PLAN_SPEC) }
|
|
75
100
|
: {}),
|
|
76
101
|
...(patch.compiledAt !== undefined
|
|
77
|
-
? { compiledAt: patch.compiledAt }
|
|
102
|
+
? { compiledAt: toDatabaseDateTime(patch.compiledAt) }
|
|
78
103
|
: spec.compiledAt
|
|
79
|
-
? { compiledAt: spec.compiledAt }
|
|
104
|
+
? { compiledAt: toDatabaseDateTime(spec.compiledAt) }
|
|
80
105
|
: {}),
|
|
81
106
|
}
|
|
82
107
|
}
|
|
@@ -89,8 +114,8 @@ type PlanRunUpdate = Omit<
|
|
|
89
114
|
waitingNodeId?: string | null
|
|
90
115
|
replacedRunId?: RecordIdInput | null
|
|
91
116
|
lastCheckpointId?: RecordIdInput | null
|
|
92
|
-
startedAt?: Date | null
|
|
93
|
-
completedAt?: Date | null
|
|
117
|
+
startedAt?: string | Date | null
|
|
118
|
+
completedAt?: string | Date | null
|
|
94
119
|
}
|
|
95
120
|
|
|
96
121
|
function toRunData(run: PlanRunRecord, patch: PlanRunUpdate) {
|
|
@@ -133,16 +158,16 @@ function toRunData(run: PlanRunRecord, patch: PlanRunUpdate) {
|
|
|
133
158
|
...(patch.startedAt === null
|
|
134
159
|
? {}
|
|
135
160
|
: patch.startedAt !== undefined
|
|
136
|
-
? { startedAt: patch.startedAt }
|
|
161
|
+
? { startedAt: toDatabaseDateTime(patch.startedAt) }
|
|
137
162
|
: run.startedAt
|
|
138
|
-
? { startedAt: run.startedAt }
|
|
163
|
+
? { startedAt: toDatabaseDateTime(run.startedAt) }
|
|
139
164
|
: {}),
|
|
140
165
|
...(patch.completedAt === null
|
|
141
166
|
? {}
|
|
142
167
|
: patch.completedAt !== undefined
|
|
143
|
-
? { completedAt: patch.completedAt }
|
|
168
|
+
? { completedAt: toDatabaseDateTime(patch.completedAt) }
|
|
144
169
|
: run.completedAt
|
|
145
|
-
? { completedAt: run.completedAt }
|
|
170
|
+
? { completedAt: toDatabaseDateTime(run.completedAt) }
|
|
146
171
|
: {}),
|
|
147
172
|
}
|
|
148
173
|
}
|
|
@@ -176,54 +201,142 @@ class ExecutionPlanService {
|
|
|
176
201
|
|
|
177
202
|
async getActivePlanForWorkstream(
|
|
178
203
|
workstreamId: RecordIdInput,
|
|
179
|
-
options?: Partial<GetActiveExecutionPlanArgs
|
|
204
|
+
options?: Partial<GetActiveExecutionPlanArgs> & { runId?: RecordIdInput },
|
|
180
205
|
): Promise<SerializableExecutionPlan | null> {
|
|
181
|
-
const
|
|
182
|
-
|
|
206
|
+
const plans = await this.getActivePlansForWorkstream(workstreamId, options)
|
|
207
|
+
return plans[0] ?? null
|
|
208
|
+
}
|
|
183
209
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
210
|
+
async getActivePlansForWorkstream(
|
|
211
|
+
workstreamId: RecordIdInput,
|
|
212
|
+
options?: Partial<GetActiveExecutionPlanArgs> & { runId?: RecordIdInput },
|
|
213
|
+
): Promise<SerializableExecutionPlan[]> {
|
|
214
|
+
if (options?.runId) {
|
|
215
|
+
const run = await planRunService.getRunById(options.runId)
|
|
216
|
+
return [
|
|
217
|
+
await planRunService.toSerializablePlan(run, {
|
|
218
|
+
includeEvents: options.includeEvents,
|
|
219
|
+
includeArtifacts: options.includeArtifacts,
|
|
220
|
+
includeApprovals: options.includeApprovals,
|
|
221
|
+
includeCheckpoints: options.includeCheckpoints,
|
|
222
|
+
includeValidationIssues: options.includeValidationIssues,
|
|
223
|
+
}),
|
|
224
|
+
]
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const runs = await planRunService.getActiveRunRecords(workstreamId)
|
|
228
|
+
if (runs.length === 0) return []
|
|
229
|
+
|
|
230
|
+
return await Promise.all(
|
|
231
|
+
runs.map((run) =>
|
|
232
|
+
planRunService.toSerializablePlan(run, {
|
|
233
|
+
includeEvents: options?.includeEvents,
|
|
234
|
+
includeArtifacts: options?.includeArtifacts,
|
|
235
|
+
includeApprovals: options?.includeApprovals,
|
|
236
|
+
includeCheckpoints: options?.includeCheckpoints,
|
|
237
|
+
includeValidationIssues: options?.includeValidationIssues,
|
|
238
|
+
}),
|
|
239
|
+
),
|
|
240
|
+
)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
async listActivePlanSummaries(workstreamId: RecordIdInput): Promise<ListExecutionPlansToolResultData> {
|
|
244
|
+
const runs = await planRunService.getActiveRunRecords(workstreamId)
|
|
245
|
+
const plans: ListExecutionPlansSummary[] = await Promise.all(
|
|
246
|
+
runs.map(async (run) => {
|
|
247
|
+
const spec = await planRunService.getPlanSpecById(run.planSpecId)
|
|
248
|
+
const nodeRuns = await planRunService.listNodeRuns(run.id)
|
|
249
|
+
return {
|
|
250
|
+
runId: recordIdToString(run.id, TABLES.PLAN_RUN),
|
|
251
|
+
title: spec.title,
|
|
252
|
+
status: run.status,
|
|
253
|
+
objective: spec.objective,
|
|
254
|
+
nodeCount: nodeRuns.length,
|
|
255
|
+
completedCount: nodeRuns.filter((n) => n.status === 'completed' || n.status === 'partial').length,
|
|
256
|
+
failedCount: nodeRuns.filter((n) => n.status === 'failed').length,
|
|
257
|
+
}
|
|
258
|
+
}),
|
|
259
|
+
)
|
|
260
|
+
return { plans, totalCount: plans.length }
|
|
191
261
|
}
|
|
192
262
|
|
|
193
263
|
async getActivePlanToolResult(params: {
|
|
194
264
|
workstreamId: RecordIdInput
|
|
265
|
+
runId?: string
|
|
195
266
|
includeEvents?: boolean
|
|
196
267
|
includeArtifacts?: boolean
|
|
197
268
|
includeApprovals?: boolean
|
|
198
269
|
includeCheckpoints?: boolean
|
|
199
270
|
includeValidationIssues?: boolean
|
|
200
271
|
}): Promise<ExecutionPlanToolResultData> {
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
272
|
+
const serializeOptions = {
|
|
273
|
+
includeEvents: params.includeEvents,
|
|
274
|
+
includeArtifacts: params.includeArtifacts,
|
|
275
|
+
includeApprovals: params.includeApprovals,
|
|
276
|
+
includeCheckpoints: params.includeCheckpoints,
|
|
277
|
+
includeValidationIssues: params.includeValidationIssues,
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (params.runId) {
|
|
281
|
+
const plan = await planRunService
|
|
282
|
+
.getRunById(params.runId)
|
|
283
|
+
.then((run) => planRunService.toSerializablePlan(run, serializeOptions))
|
|
284
|
+
return buildToolResult({ action: 'loaded', plan, message: `Loaded execution run "${plan.title}".` })
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const runs = await planRunService.getActiveRunRecords(params.workstreamId)
|
|
288
|
+
if (runs.length === 0) {
|
|
289
|
+
return buildToolResult({ action: 'none', plan: null, message: 'No active execution run.' })
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const plan = await planRunService.toSerializablePlan(runs[0], serializeOptions)
|
|
293
|
+
const planSummaries = await Promise.all(
|
|
294
|
+
runs.map(async (run) => {
|
|
295
|
+
const spec = await planRunService.getPlanSpecById(run.planSpecId)
|
|
296
|
+
return { runId: recordIdToString(run.id, TABLES.PLAN_RUN), title: spec.title, status: run.status }
|
|
297
|
+
}),
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
return {
|
|
301
|
+
...buildToolResult({
|
|
302
|
+
action: 'loaded',
|
|
303
|
+
plan,
|
|
304
|
+
message:
|
|
305
|
+
runs.length === 1
|
|
306
|
+
? `Loaded execution run "${plan.title}".`
|
|
307
|
+
: `Loaded ${runs.length} active execution runs. Showing most recent: "${plan.title}".`,
|
|
308
|
+
}),
|
|
309
|
+
planCount: runs.length,
|
|
310
|
+
planSummaries,
|
|
311
|
+
}
|
|
207
312
|
}
|
|
208
313
|
|
|
209
314
|
async createPlan(params: {
|
|
210
315
|
organizationId: RecordIdInput
|
|
211
316
|
workstreamId: RecordIdInput
|
|
212
317
|
leadAgentId: string
|
|
318
|
+
dispatchMode: ExecutionPlanDispatchMode
|
|
213
319
|
input: CreateExecutionPlanArgs
|
|
214
320
|
}): Promise<ExecutionPlanToolResultData> {
|
|
215
|
-
const activeRun = await planRunService.getActiveRunRecord(params.workstreamId)
|
|
216
|
-
if (activeRun) {
|
|
217
|
-
throw new Error('An active execution run already exists for this workstream. Replace it instead.')
|
|
218
|
-
}
|
|
219
|
-
|
|
220
321
|
const preparedDraft = planBuilderService.prepareDraft(params.input)
|
|
221
322
|
const validation = planValidatorService.validateDraft(preparedDraft)
|
|
222
323
|
if (validation.blocking.length > 0) {
|
|
223
324
|
throw new Error(`Plan draft failed validation: ${aggregateBlockingIssues(validation.blocking)}`)
|
|
224
325
|
}
|
|
326
|
+
await this.assertDispatchExecutors(preparedDraft)
|
|
225
327
|
const compiled = planCompilerService.compile(preparedDraft)
|
|
226
328
|
|
|
329
|
+
// Context enrichment — best-effort, failures do not block plan creation
|
|
330
|
+
const enrichments = await contextEnrichmentService
|
|
331
|
+
.enrichForPlanCreation({
|
|
332
|
+
objective: compiled.draft.objective,
|
|
333
|
+
organizationId: recordIdToString(params.organizationId, TABLES.ORGANIZATION),
|
|
334
|
+
})
|
|
335
|
+
.catch((error: unknown) => {
|
|
336
|
+
serverLogger.error`Context enrichment failed: ${error instanceof Error ? error.message : String(error)}`
|
|
337
|
+
return []
|
|
338
|
+
})
|
|
339
|
+
|
|
227
340
|
const specId = new RecordId(TABLES.PLAN_SPEC, Bun.randomUUIDv7())
|
|
228
341
|
const runId = new RecordId(TABLES.PLAN_RUN, Bun.randomUUIDv7())
|
|
229
342
|
|
|
@@ -240,8 +353,14 @@ class ExecutionPlanService {
|
|
|
240
353
|
status: 'compiled',
|
|
241
354
|
leadAgentId: params.leadAgentId,
|
|
242
355
|
schemaRegistry: structuredClone(compiled.draft.schemas),
|
|
356
|
+
...(enrichments.length > 0
|
|
357
|
+
? { contextEnrichments: enrichments.map((e) => ({ type: e.domain, content: JSON.stringify(e.data) })) }
|
|
358
|
+
: {}),
|
|
243
359
|
edges: [...compiled.draft.edges],
|
|
244
360
|
entryNodeIds: [...(compiled.draft.entryNodeIds ?? [])],
|
|
361
|
+
executionMode: compiled.draft.executionMode ?? 'linear',
|
|
362
|
+
...(compiled.draft.schedule ? { schedule: compiled.draft.schedule } : {}),
|
|
363
|
+
...(compiled.draft.dependencies ? { dependencies: compiled.draft.dependencies } : {}),
|
|
245
364
|
compiledAt: new Date(),
|
|
246
365
|
})
|
|
247
366
|
.output('after'),
|
|
@@ -332,12 +451,31 @@ class ExecutionPlanService {
|
|
|
332
451
|
PlanEventSchema.parse(checkpointEvent)
|
|
333
452
|
})
|
|
334
453
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
454
|
+
// Create a plan-level schedule record if the draft specifies a schedule
|
|
455
|
+
if (compiled.draft.schedule) {
|
|
456
|
+
const schedule = await planSchedulerService.createSchedule({
|
|
457
|
+
organizationId: params.organizationId,
|
|
458
|
+
workstreamId: params.workstreamId,
|
|
459
|
+
planSpecId: specId,
|
|
460
|
+
runId,
|
|
461
|
+
scheduleSpec: compiled.draft.schedule,
|
|
462
|
+
})
|
|
463
|
+
|
|
464
|
+
await databaseService.update(
|
|
465
|
+
TABLES.PLAN_RUN,
|
|
466
|
+
ensureRecordId(runId, TABLES.PLAN_RUN),
|
|
467
|
+
{
|
|
468
|
+
scheduleId: ensureRecordId(schedule.id, TABLES.PLAN_SCHEDULE),
|
|
469
|
+
scheduledAt: schedule.nextFireAt ? toDatabaseDateTime(schedule.nextFireAt) : undefined,
|
|
470
|
+
},
|
|
471
|
+
PlanRunSchema,
|
|
472
|
+
)
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
const plan = await this.finalizePlanSnapshot({
|
|
476
|
+
runId,
|
|
477
|
+
emittedBy: params.leadAgentId,
|
|
478
|
+
dispatchMode: params.dispatchMode,
|
|
341
479
|
})
|
|
342
480
|
|
|
343
481
|
return buildToolResult({ action: 'created', plan, message: `Created execution plan "${plan.title}".` })
|
|
@@ -347,14 +485,23 @@ class ExecutionPlanService {
|
|
|
347
485
|
workstreamId: RecordIdInput
|
|
348
486
|
organizationId: RecordIdInput
|
|
349
487
|
leadAgentId: string
|
|
488
|
+
dispatchMode: ExecutionPlanDispatchMode
|
|
350
489
|
input: ReplaceExecutionPlanArgs
|
|
351
490
|
}): Promise<ExecutionPlanToolResultData> {
|
|
352
|
-
const activeRun = await planRunService.
|
|
353
|
-
if (
|
|
491
|
+
const activeRun = await planRunService.getRunById(params.input.runId)
|
|
492
|
+
if (
|
|
493
|
+
recordIdToString(activeRun.workstreamId, TABLES.WORKSTREAM) !==
|
|
494
|
+
recordIdToString(params.workstreamId, TABLES.WORKSTREAM)
|
|
495
|
+
) {
|
|
496
|
+
throw new Error('Execution run belongs to a different workstream.')
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
const activeRuns = await planRunService.getActiveRunRecords(params.workstreamId)
|
|
500
|
+
if (activeRuns.length === 0) {
|
|
354
501
|
throw new Error('No active execution run exists for this workstream.')
|
|
355
502
|
}
|
|
356
|
-
if (recordIdToString(
|
|
357
|
-
throw new Error('Only
|
|
503
|
+
if (!activeRuns.some((run) => recordIdToString(run.id, TABLES.PLAN_RUN) === params.input.runId)) {
|
|
504
|
+
throw new Error('Only an active execution run can be replaced.')
|
|
358
505
|
}
|
|
359
506
|
|
|
360
507
|
const activeSpec = await planRunService.getPlanSpecById(activeRun.planSpecId)
|
|
@@ -365,11 +512,15 @@ class ExecutionPlanService {
|
|
|
365
512
|
edges: params.input.edges,
|
|
366
513
|
entryNodeIds: params.input.entryNodeIds,
|
|
367
514
|
schemas: params.input.schemas,
|
|
515
|
+
executionMode: params.input.executionMode,
|
|
516
|
+
schedule: params.input.schedule,
|
|
517
|
+
dependencies: params.input.dependencies,
|
|
368
518
|
})
|
|
369
519
|
const validation = planValidatorService.validateDraft(preparedDraft)
|
|
370
520
|
if (validation.blocking.length > 0) {
|
|
371
521
|
throw new Error(`Plan draft failed validation: ${aggregateBlockingIssues(validation.blocking)}`)
|
|
372
522
|
}
|
|
523
|
+
await this.assertDispatchExecutors(preparedDraft)
|
|
373
524
|
const compiled = planCompilerService.compile(preparedDraft)
|
|
374
525
|
|
|
375
526
|
const specId = new RecordId(TABLES.PLAN_SPEC, Bun.randomUUIDv7())
|
|
@@ -412,6 +563,9 @@ class ExecutionPlanService {
|
|
|
412
563
|
schemaRegistry: structuredClone(compiled.draft.schemas),
|
|
413
564
|
edges: [...compiled.draft.edges],
|
|
414
565
|
entryNodeIds: [...(compiled.draft.entryNodeIds ?? [])],
|
|
566
|
+
executionMode: compiled.draft.executionMode ?? 'linear',
|
|
567
|
+
...(compiled.draft.schedule ? { schedule: compiled.draft.schedule } : {}),
|
|
568
|
+
...(compiled.draft.dependencies ? { dependencies: compiled.draft.dependencies } : {}),
|
|
415
569
|
replacedSpecId: ensureRecordId(supersededSpec.id, TABLES.PLAN_SPEC),
|
|
416
570
|
compiledAt: new Date(),
|
|
417
571
|
})
|
|
@@ -504,12 +658,10 @@ class ExecutionPlanService {
|
|
|
504
658
|
PlanEventSchema.parse(checkpointEvent)
|
|
505
659
|
})
|
|
506
660
|
|
|
507
|
-
const plan = await
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
includeCheckpoints: true,
|
|
512
|
-
includeValidationIssues: true,
|
|
661
|
+
const plan = await this.finalizePlanSnapshot({
|
|
662
|
+
runId,
|
|
663
|
+
emittedBy: params.leadAgentId,
|
|
664
|
+
dispatchMode: params.dispatchMode,
|
|
513
665
|
})
|
|
514
666
|
|
|
515
667
|
return buildToolResult({ action: 'replaced', plan, message: `Replaced execution plan with "${plan.title}".` })
|
|
@@ -520,13 +672,24 @@ class ExecutionPlanService {
|
|
|
520
672
|
emittedBy: string
|
|
521
673
|
input: SubmitExecutionNodeResultArgs
|
|
522
674
|
}): Promise<ExecutionPlanToolResultData> {
|
|
523
|
-
|
|
675
|
+
const result = await planExecutorService.submitNodeResult({
|
|
524
676
|
workstreamId: params.workstreamId,
|
|
525
677
|
runId: params.input.runId,
|
|
526
678
|
nodeId: params.input.nodeId,
|
|
527
679
|
emittedBy: params.emittedBy,
|
|
528
680
|
result: params.input.result,
|
|
529
681
|
})
|
|
682
|
+
const plan = await ownershipDispatcherService.dispatchRunToStableBoundary({
|
|
683
|
+
runId: params.input.runId,
|
|
684
|
+
emittedBy: params.emittedBy,
|
|
685
|
+
})
|
|
686
|
+
|
|
687
|
+
return buildToolResult({
|
|
688
|
+
action: result.action,
|
|
689
|
+
plan,
|
|
690
|
+
message: result.message ?? `Submitted result for node "${params.input.nodeId}".`,
|
|
691
|
+
changedNodeId: result.changedNodeId ?? undefined,
|
|
692
|
+
})
|
|
530
693
|
}
|
|
531
694
|
|
|
532
695
|
async resumeRun(params: {
|
|
@@ -534,11 +697,22 @@ class ExecutionPlanService {
|
|
|
534
697
|
emittedBy: string
|
|
535
698
|
input: ResumeExecutionPlanRunArgs
|
|
536
699
|
}): Promise<ExecutionPlanToolResultData> {
|
|
537
|
-
|
|
700
|
+
const result = await planExecutorService.resumeRun({
|
|
538
701
|
workstreamId: params.workstreamId,
|
|
539
702
|
runId: params.input.runId,
|
|
540
703
|
emittedBy: params.emittedBy,
|
|
541
704
|
})
|
|
705
|
+
const plan = await ownershipDispatcherService.dispatchRunToStableBoundary({
|
|
706
|
+
runId: params.input.runId,
|
|
707
|
+
emittedBy: params.emittedBy,
|
|
708
|
+
})
|
|
709
|
+
|
|
710
|
+
return buildToolResult({
|
|
711
|
+
action: result.action,
|
|
712
|
+
plan,
|
|
713
|
+
message: result.message ?? `Resumed execution run "${params.input.runId}".`,
|
|
714
|
+
changedNodeId: result.changedNodeId ?? undefined,
|
|
715
|
+
})
|
|
542
716
|
}
|
|
543
717
|
|
|
544
718
|
async applyApprovalResponseFromMessages(params: {
|
|
@@ -549,12 +723,18 @@ class ExecutionPlanService {
|
|
|
549
723
|
const approvalResponse = buildApprovalResponseFromMessages(params.approvalMessages)
|
|
550
724
|
if (!approvalResponse) return null
|
|
551
725
|
|
|
552
|
-
|
|
726
|
+
const run = await planRunService.getActiveRunRecord(params.workstreamId)
|
|
727
|
+
if (!run) return null
|
|
728
|
+
|
|
729
|
+
const plan = await planExecutorService.submitHumanNodeResponse({
|
|
553
730
|
workstreamId: params.workstreamId,
|
|
554
731
|
approvalId: approvalResponse.approvalId,
|
|
555
732
|
respondedBy: params.respondedBy,
|
|
556
733
|
response: approvalResponse.response,
|
|
557
734
|
})
|
|
735
|
+
if (!plan) return null
|
|
736
|
+
|
|
737
|
+
return ownershipDispatcherService.dispatchRunToStableBoundary({ runId: run.id, emittedBy: params.respondedBy })
|
|
558
738
|
}
|
|
559
739
|
|
|
560
740
|
async respondToApproval(params: {
|
|
@@ -562,13 +742,19 @@ class ExecutionPlanService {
|
|
|
562
742
|
emittedBy: string
|
|
563
743
|
input: { approvalId: string; response: Record<string, unknown>; approvalMessageId?: string }
|
|
564
744
|
}): Promise<SerializableExecutionPlan | null> {
|
|
565
|
-
|
|
745
|
+
const run = await planRunService.getActiveRunRecord(params.workstreamId)
|
|
746
|
+
if (!run) return null
|
|
747
|
+
|
|
748
|
+
const plan = await planExecutorService.submitHumanNodeResponse({
|
|
566
749
|
workstreamId: params.workstreamId,
|
|
567
750
|
approvalId: params.input.approvalId,
|
|
568
751
|
respondedBy: params.emittedBy,
|
|
569
752
|
response: params.input.response,
|
|
570
753
|
approvalMessageId: params.input.approvalMessageId,
|
|
571
754
|
})
|
|
755
|
+
if (!plan) return null
|
|
756
|
+
|
|
757
|
+
return ownershipDispatcherService.dispatchRunToStableBoundary({ runId: run.id, emittedBy: params.emittedBy })
|
|
572
758
|
}
|
|
573
759
|
|
|
574
760
|
async applyHumanInputFromUserMessage(params: {
|
|
@@ -597,12 +783,38 @@ class ExecutionPlanService {
|
|
|
597
783
|
approved: nodeSpec.type === 'human-decision' ? true : undefined,
|
|
598
784
|
} satisfies Record<string, unknown>
|
|
599
785
|
|
|
600
|
-
|
|
786
|
+
const plan = await planExecutorService.submitHumanNodeResponse({
|
|
601
787
|
workstreamId: params.workstreamId,
|
|
602
788
|
respondedBy: params.respondedBy,
|
|
603
789
|
response,
|
|
604
790
|
approvalMessageId: params.message.id,
|
|
605
791
|
})
|
|
792
|
+
if (!plan) return null
|
|
793
|
+
|
|
794
|
+
return ownershipDispatcherService.dispatchRunToStableBoundary({ runId: run.id, emittedBy: params.respondedBy })
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
private async assertDispatchExecutors(preparedDraft: Parameters<typeof planValidatorService.validateDraft>[0]) {
|
|
798
|
+
const issues = ownershipDispatcherService.validateDraftExecutors(preparedDraft)
|
|
799
|
+
if (issues.length > 0) {
|
|
800
|
+
throw new Error(`Plan draft failed validation: ${aggregateBlockingIssues(issues)}`)
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
private async finalizePlanSnapshot(params: {
|
|
805
|
+
runId: RecordIdInput
|
|
806
|
+
emittedBy: string
|
|
807
|
+
dispatchMode: ExecutionPlanDispatchMode
|
|
808
|
+
}): Promise<SerializableExecutionPlan> {
|
|
809
|
+
if (params.dispatchMode === 'stable-boundary') {
|
|
810
|
+
return ownershipDispatcherService.dispatchRunToStableBoundary({
|
|
811
|
+
runId: params.runId,
|
|
812
|
+
emittedBy: params.emittedBy,
|
|
813
|
+
})
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
const run = await planRunService.getRunById(params.runId)
|
|
817
|
+
return planRunService.toSerializablePlan(run, TOOL_RESULT_SERIALIZE_OPTIONS)
|
|
606
818
|
}
|
|
607
819
|
|
|
608
820
|
private async createNodeSpecs(
|
|
@@ -612,6 +824,7 @@ class ExecutionPlanService {
|
|
|
612
824
|
): Promise<PlanNodeSpecRecord[]> {
|
|
613
825
|
const createdRecords: PlanNodeSpecRecord[] = []
|
|
614
826
|
|
|
827
|
+
// Sequential: SurrealDB transactions require ordered operations
|
|
615
828
|
for (const compiledNode of nodes) {
|
|
616
829
|
const nodeSpecId = new RecordId(TABLES.PLAN_NODE_SPEC, Bun.randomUUIDv7())
|
|
617
830
|
const created = await tx
|
|
@@ -639,6 +852,13 @@ class ExecutionPlanService {
|
|
|
639
852
|
attachmentPolicy: compiledNode.node.contextPolicy.attachmentPolicy,
|
|
640
853
|
webPolicy: compiledNode.node.contextPolicy.webPolicy,
|
|
641
854
|
},
|
|
855
|
+
...(compiledNode.node.schedule ? { schedule: compiledNode.node.schedule } : {}),
|
|
856
|
+
...(compiledNode.node.deadline ? { deadline: compiledNode.node.deadline } : {}),
|
|
857
|
+
...(compiledNode.node.monitoringConfig ? { monitoringConfig: compiledNode.node.monitoringConfig } : {}),
|
|
858
|
+
...(compiledNode.node.delayAfterPredecessorMs
|
|
859
|
+
? { delayAfterPredecessorMs: compiledNode.node.delayAfterPredecessorMs }
|
|
860
|
+
: {}),
|
|
861
|
+
...(compiledNode.node.deliberationConfig ? { deliberationConfig: compiledNode.node.deliberationConfig } : {}),
|
|
642
862
|
upstreamNodeIds: [...compiledNode.upstreamNodeIds],
|
|
643
863
|
downstreamNodeIds: [...compiledNode.downstreamNodeIds],
|
|
644
864
|
})
|
|
@@ -657,6 +877,7 @@ class ExecutionPlanService {
|
|
|
657
877
|
nodeSpecs: PlanNodeSpecRecord[],
|
|
658
878
|
): Promise<PlanNodeRunRecord[]> {
|
|
659
879
|
const createdNodeRuns: PlanNodeRunRecord[] = []
|
|
880
|
+
// Sequential: SurrealDB transactions require ordered operations
|
|
660
881
|
for (const nodeSpec of nodeSpecs) {
|
|
661
882
|
const nodeRunId = new RecordId(TABLES.PLAN_NODE_RUN, Bun.randomUUIDv7())
|
|
662
883
|
const created = await tx
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { Recommendation } from '@lota-sdk/shared'
|
|
2
|
+
|
|
3
|
+
import { ensureRecordId, recordIdToString } from '../db/record-id'
|
|
4
|
+
import { TABLES } from '../db/tables'
|
|
5
|
+
import { toIsoDateTimeString } from '../utils/date-time'
|
|
6
|
+
import { planRunService } from './plan-run.service'
|
|
7
|
+
|
|
8
|
+
class FeedbackLoopService {
|
|
9
|
+
async analyzeOutcomes(params: { runId: string; organizationId: string }): Promise<Recommendation[]> {
|
|
10
|
+
const run = await planRunService.getRunById(params.runId)
|
|
11
|
+
const nodeRuns = await planRunService.listNodeRuns(run.id)
|
|
12
|
+
const attempts = await planRunService.listAttempts(run.id)
|
|
13
|
+
const nodeSpecs = await planRunService.listNodeSpecs(ensureRecordId(run.planSpecId, TABLES.PLAN_SPEC))
|
|
14
|
+
const nodeSpecsByNodeId = new Map(nodeSpecs.map((ns) => [ns.nodeId, ns]))
|
|
15
|
+
|
|
16
|
+
const recommendations: Recommendation[] = []
|
|
17
|
+
|
|
18
|
+
for (const nodeRun of nodeRuns) {
|
|
19
|
+
const nodeSpec = nodeSpecsByNodeId.get(nodeRun.nodeId)
|
|
20
|
+
if (!nodeSpec) continue
|
|
21
|
+
|
|
22
|
+
const nodeAttempts = attempts.filter((a) => a.nodeId === nodeRun.nodeId)
|
|
23
|
+
const failedAttempts = nodeAttempts.filter((a) => a.status === 'failed')
|
|
24
|
+
|
|
25
|
+
if (failedAttempts.length >= 2) {
|
|
26
|
+
recommendations.push({
|
|
27
|
+
type: 'warning',
|
|
28
|
+
target: 'node',
|
|
29
|
+
targetId: nodeRun.nodeId,
|
|
30
|
+
description: `Node "${nodeSpec.label}" failed ${failedAttempts.length} times before ${nodeRun.status === 'completed' || nodeRun.status === 'partial' ? 'succeeding' : 'giving up'}. Consider adjusting retry policy or node instructions.`,
|
|
31
|
+
evidence: failedAttempts.map((a) => ({
|
|
32
|
+
sourceType: 'metric',
|
|
33
|
+
sourceId: recordIdToString(a.id, TABLES.PLAN_NODE_ATTEMPT),
|
|
34
|
+
summary: `Attempt failed with class: ${a.failureClass ?? 'unknown'}`,
|
|
35
|
+
confidence: 0.9,
|
|
36
|
+
})),
|
|
37
|
+
confidence: Math.min(0.5 + failedAttempts.length * 0.15, 0.95),
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const completedNodeRuns = nodeRuns.filter(
|
|
43
|
+
(nr) => nr.startedAt && nr.completedAt && (nr.status === 'completed' || nr.status === 'partial'),
|
|
44
|
+
)
|
|
45
|
+
if (completedNodeRuns.length >= 2) {
|
|
46
|
+
const durations = completedNodeRuns.map((nr) => {
|
|
47
|
+
const start = new Date(toIsoDateTimeString(nr.startedAt)).getTime()
|
|
48
|
+
const end = new Date(toIsoDateTimeString(nr.completedAt)).getTime()
|
|
49
|
+
return { nodeId: nr.nodeId, durationMs: end - start }
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
const avgDuration = durations.reduce((sum, d) => sum + d.durationMs, 0) / durations.length
|
|
53
|
+
|
|
54
|
+
for (const d of durations) {
|
|
55
|
+
if (d.durationMs > avgDuration * 2.5 && d.durationMs > 5000) {
|
|
56
|
+
const nodeSpec = nodeSpecsByNodeId.get(d.nodeId)
|
|
57
|
+
recommendations.push({
|
|
58
|
+
type: 'optimization',
|
|
59
|
+
target: 'node',
|
|
60
|
+
targetId: d.nodeId,
|
|
61
|
+
description: `Node "${nodeSpec?.label ?? d.nodeId}" took ${Math.round(d.durationMs / 1000)}s, which is ${Math.round((d.durationMs / avgDuration) * 10) / 10}x the average. Consider splitting or optimizing.`,
|
|
62
|
+
evidence: [
|
|
63
|
+
{
|
|
64
|
+
sourceType: 'metric',
|
|
65
|
+
sourceId: d.nodeId,
|
|
66
|
+
summary: `Execution time: ${d.durationMs}ms vs average ${Math.round(avgDuration)}ms`,
|
|
67
|
+
confidence: 0.85,
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
confidence: 0.7,
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const skippedNodes = nodeRuns.filter((nr) => nr.status === 'skipped')
|
|
77
|
+
if (skippedNodes.length > 0 && skippedNodes.length >= nodeRuns.length * 0.3) {
|
|
78
|
+
recommendations.push({
|
|
79
|
+
type: 'pattern',
|
|
80
|
+
target: 'plan',
|
|
81
|
+
description: `${skippedNodes.length} of ${nodeRuns.length} nodes were skipped. The plan may have overly broad conditional branches.`,
|
|
82
|
+
evidence: skippedNodes.map((nr) => ({
|
|
83
|
+
sourceType: 'pattern',
|
|
84
|
+
sourceId: nr.nodeId,
|
|
85
|
+
summary: `Node "${nodeSpecsByNodeId.get(nr.nodeId)?.label ?? nr.nodeId}" was skipped`,
|
|
86
|
+
confidence: 0.8,
|
|
87
|
+
})),
|
|
88
|
+
confidence: 0.65,
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return recommendations
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export const feedbackLoopService = new FeedbackLoopService()
|