@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,96 +1,152 @@
|
|
|
1
1
|
import type { Recommendation } from '@lota-sdk/shared'
|
|
2
|
+
import { Context, Schema, Effect, Layer } from 'effect'
|
|
2
3
|
|
|
3
4
|
import { ensureRecordId, recordIdToString } from '../db/record-id'
|
|
4
5
|
import { TABLES } from '../db/tables'
|
|
5
|
-
import { toIsoDateTimeString } from '../utils/date-time'
|
|
6
|
-
import {
|
|
6
|
+
import { toIsoDateTimeString, unsafeDateFrom } from '../utils/date-time'
|
|
7
|
+
import type { makePlanRunService } from './plan/plan-run.service'
|
|
8
|
+
import { PlanRunServiceTag } from './plan/plan-run.service'
|
|
7
9
|
|
|
8
|
-
class
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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]))
|
|
10
|
+
class FeedbackLoopServiceError extends Schema.TaggedErrorClass<FeedbackLoopServiceError>()('FeedbackLoopServiceError', {
|
|
11
|
+
message: Schema.String,
|
|
12
|
+
cause: Schema.Defect,
|
|
13
|
+
}) {}
|
|
15
14
|
|
|
16
|
-
|
|
15
|
+
export function makeFeedbackLoopService(planRunService: ReturnType<typeof makePlanRunService>) {
|
|
16
|
+
return {
|
|
17
|
+
analyzeOutcomes(params: { runId: string; organizationId: string }) {
|
|
18
|
+
return Effect.gen(function* () {
|
|
19
|
+
const run = yield* planRunService
|
|
20
|
+
.getRunById(params.runId)
|
|
21
|
+
.pipe(
|
|
22
|
+
Effect.mapError(
|
|
23
|
+
(cause) =>
|
|
24
|
+
new FeedbackLoopServiceError({ message: 'Failed to load plan run for feedback analysis.', cause }),
|
|
25
|
+
),
|
|
26
|
+
)
|
|
27
|
+
const [nodeRuns, attempts, nodeSpecs] = yield* Effect.all([
|
|
28
|
+
planRunService
|
|
29
|
+
.listNodeRuns(run.id)
|
|
30
|
+
.pipe(
|
|
31
|
+
Effect.mapError(
|
|
32
|
+
(cause) =>
|
|
33
|
+
new FeedbackLoopServiceError({ message: 'Failed to load node runs for feedback analysis.', cause }),
|
|
34
|
+
),
|
|
35
|
+
),
|
|
36
|
+
planRunService
|
|
37
|
+
.listAttempts(run.id)
|
|
38
|
+
.pipe(
|
|
39
|
+
Effect.mapError(
|
|
40
|
+
(cause) =>
|
|
41
|
+
new FeedbackLoopServiceError({
|
|
42
|
+
message: 'Failed to load node attempts for feedback analysis.',
|
|
43
|
+
cause,
|
|
44
|
+
}),
|
|
45
|
+
),
|
|
46
|
+
),
|
|
47
|
+
planRunService
|
|
48
|
+
.listNodeSpecs(ensureRecordId(run.planSpecId, TABLES.PLAN_SPEC))
|
|
49
|
+
.pipe(
|
|
50
|
+
Effect.mapError(
|
|
51
|
+
(cause) =>
|
|
52
|
+
new FeedbackLoopServiceError({ message: 'Failed to load node specs for feedback analysis.', cause }),
|
|
53
|
+
),
|
|
54
|
+
),
|
|
55
|
+
])
|
|
17
56
|
|
|
18
|
-
|
|
19
|
-
const nodeSpec = nodeSpecsByNodeId.get(nodeRun.nodeId)
|
|
20
|
-
if (!nodeSpec) continue
|
|
57
|
+
const nodeSpecsByNodeId = new Map(nodeSpecs.map((ns) => [ns.nodeId, ns]))
|
|
21
58
|
|
|
22
|
-
|
|
23
|
-
const failedAttempts = nodeAttempts.filter((a) => a.status === 'failed')
|
|
59
|
+
const recommendations: Recommendation[] = []
|
|
24
60
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
}
|
|
61
|
+
for (const nodeRun of nodeRuns) {
|
|
62
|
+
const nodeSpec = nodeSpecsByNodeId.get(nodeRun.nodeId)
|
|
63
|
+
if (!nodeSpec) continue
|
|
41
64
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
65
|
+
const nodeAttempts = attempts.filter((a) => a.nodeId === nodeRun.nodeId)
|
|
66
|
+
const failedAttempts = nodeAttempts.filter((a) => a.status === 'failed')
|
|
67
|
+
|
|
68
|
+
if (failedAttempts.length >= 2) {
|
|
69
|
+
recommendations.push({
|
|
70
|
+
type: 'warning',
|
|
71
|
+
target: 'node',
|
|
72
|
+
targetId: nodeRun.nodeId,
|
|
73
|
+
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.`,
|
|
74
|
+
evidence: failedAttempts.map((a) => ({
|
|
75
|
+
sourceType: 'metric',
|
|
76
|
+
sourceId: recordIdToString(a.id, TABLES.PLAN_NODE_ATTEMPT),
|
|
77
|
+
summary: `Attempt failed with class: ${a.failureClass ?? 'unknown'}`,
|
|
78
|
+
confidence: 0.9,
|
|
79
|
+
})),
|
|
80
|
+
confidence: Math.min(0.5 + failedAttempts.length * 0.15, 0.95),
|
|
81
|
+
})
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const completedNodeRuns = nodeRuns.filter(
|
|
86
|
+
(nr) => nr.startedAt && nr.completedAt && (nr.status === 'completed' || nr.status === 'partial'),
|
|
87
|
+
)
|
|
88
|
+
if (completedNodeRuns.length >= 2) {
|
|
89
|
+
const durations = completedNodeRuns.map((nr) => {
|
|
90
|
+
const start = unsafeDateFrom(toIsoDateTimeString(nr.startedAt)).getTime()
|
|
91
|
+
const end = unsafeDateFrom(toIsoDateTimeString(nr.completedAt)).getTime()
|
|
92
|
+
return { nodeId: nr.nodeId, durationMs: end - start }
|
|
93
|
+
})
|
|
51
94
|
|
|
52
|
-
|
|
95
|
+
const avgDuration = durations.reduce((sum, d) => sum + d.durationMs, 0) / durations.length
|
|
53
96
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
97
|
+
for (const d of durations) {
|
|
98
|
+
if (d.durationMs > avgDuration * 2.5 && d.durationMs > 5000) {
|
|
99
|
+
const nodeSpec = nodeSpecsByNodeId.get(d.nodeId)
|
|
100
|
+
recommendations.push({
|
|
101
|
+
type: 'optimization',
|
|
102
|
+
target: 'node',
|
|
103
|
+
targetId: d.nodeId,
|
|
104
|
+
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.`,
|
|
105
|
+
evidence: [
|
|
106
|
+
{
|
|
107
|
+
sourceType: 'metric',
|
|
108
|
+
sourceId: d.nodeId,
|
|
109
|
+
summary: `Execution time: ${d.durationMs}ms vs average ${Math.round(avgDuration)}ms`,
|
|
110
|
+
confidence: 0.85,
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
confidence: 0.7,
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const skippedNodes = nodeRuns.filter((nr) => nr.status === 'skipped')
|
|
120
|
+
if (skippedNodes.length > 0 && skippedNodes.length >= nodeRuns.length * 0.3) {
|
|
57
121
|
recommendations.push({
|
|
58
|
-
type: '
|
|
59
|
-
target: '
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
},
|
|
69
|
-
],
|
|
70
|
-
confidence: 0.7,
|
|
122
|
+
type: 'pattern',
|
|
123
|
+
target: 'plan',
|
|
124
|
+
description: `${skippedNodes.length} of ${nodeRuns.length} nodes were skipped. The plan may have overly broad conditional branches.`,
|
|
125
|
+
evidence: skippedNodes.map((nr) => ({
|
|
126
|
+
sourceType: 'pattern',
|
|
127
|
+
sourceId: nr.nodeId,
|
|
128
|
+
summary: `Node "${nodeSpecsByNodeId.get(nr.nodeId)?.label ?? nr.nodeId}" was skipped`,
|
|
129
|
+
confidence: 0.8,
|
|
130
|
+
})),
|
|
131
|
+
confidence: 0.65,
|
|
71
132
|
})
|
|
72
133
|
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
134
|
|
|
76
|
-
|
|
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,
|
|
135
|
+
return recommendations
|
|
89
136
|
})
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
return recommendations
|
|
137
|
+
},
|
|
93
138
|
}
|
|
94
139
|
}
|
|
95
140
|
|
|
96
|
-
export
|
|
141
|
+
export class FeedbackLoopServiceTag extends Context.Service<
|
|
142
|
+
FeedbackLoopServiceTag,
|
|
143
|
+
ReturnType<typeof makeFeedbackLoopService>
|
|
144
|
+
>()('@lota-sdk/core/FeedbackLoopService') {}
|
|
145
|
+
|
|
146
|
+
export const FeedbackLoopServiceLive = Layer.effect(
|
|
147
|
+
FeedbackLoopServiceTag,
|
|
148
|
+
Effect.gen(function* () {
|
|
149
|
+
const planRunService = yield* PlanRunServiceTag
|
|
150
|
+
return makeFeedbackLoopService(planRunService)
|
|
151
|
+
}),
|
|
152
|
+
)
|
|
@@ -1,180 +1,113 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import type {
|
|
2
|
+
ConvergenceState,
|
|
3
|
+
ExecutionMode,
|
|
4
|
+
PlanNodeResultSubmission,
|
|
5
|
+
PlanNodeRunRecord,
|
|
6
|
+
PlanNodeSpecRecord,
|
|
7
|
+
PlanRunRecord,
|
|
8
|
+
PlanSpecRecord,
|
|
9
|
+
} from '@lota-sdk/shared'
|
|
10
|
+
import { Context, Effect, Layer } from 'effect'
|
|
11
|
+
|
|
12
|
+
import { runPromise } from '../effect/runtime'
|
|
13
|
+
import { routeGraphFullEffect } from './graph-full-routing'
|
|
14
|
+
import type { OwnershipDispatcherService } from './ownership-dispatcher.service'
|
|
15
|
+
import { OwnershipDispatcherServiceTag } from './ownership-dispatcher.service'
|
|
16
|
+
import type { makePlanExecutorService } from './plan/plan-executor.service'
|
|
17
|
+
import { PlanExecutorServiceTag } from './plan/plan-executor.service'
|
|
18
|
+
import type { makePlanRunService } from './plan/plan-run.service'
|
|
19
|
+
import { PlanRunServiceTag } from './plan/plan-run.service'
|
|
20
|
+
|
|
21
|
+
function detectConvergence(params: {
|
|
22
|
+
totalNodes: number
|
|
23
|
+
completedNodes: number
|
|
24
|
+
failedNodes: number
|
|
25
|
+
previousCompletedNodes?: number
|
|
26
|
+
previousFailedNodes?: number
|
|
27
|
+
}): ConvergenceState {
|
|
28
|
+
const completionRatio = params.totalNodes > 0 ? params.completedNodes / params.totalNodes : 0
|
|
29
|
+
const failureRatio = params.totalNodes > 0 ? params.failedNodes / params.totalNodes : 0
|
|
30
|
+
|
|
31
|
+
if (params.previousCompletedNodes !== undefined) {
|
|
32
|
+
const completionVelocity = params.completedNodes - params.previousCompletedNodes
|
|
33
|
+
const failureVelocity = params.failedNodes - (params.previousFailedNodes ?? 0)
|
|
34
|
+
|
|
35
|
+
if (completionVelocity > 0 && failureVelocity === 0) return 'converging'
|
|
36
|
+
if (completionVelocity === 0 && failureVelocity === 0) return 'stalled'
|
|
37
|
+
if (failureVelocity > 0) return 'diverging'
|
|
38
|
+
}
|
|
7
39
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
if (ownerType === 'plugin' || ownerType === 'system') return 'external_system_unavailable'
|
|
12
|
-
return 'non_recoverable_logic_error'
|
|
40
|
+
if (completionRatio > 0.5 && failureRatio === 0) return 'converging'
|
|
41
|
+
if (failureRatio > 0.3) return 'diverging'
|
|
42
|
+
return 'progressing'
|
|
13
43
|
}
|
|
14
44
|
|
|
15
|
-
function
|
|
16
|
-
|
|
45
|
+
function decideRerouteAction(params: {
|
|
46
|
+
failedNodeId: string
|
|
47
|
+
retryCount: number
|
|
48
|
+
maxRetries: number
|
|
49
|
+
convergenceState: ConvergenceState
|
|
50
|
+
}): 'retry' | 'skip' | 'abort' {
|
|
51
|
+
if (params.retryCount < params.maxRetries) return 'retry'
|
|
52
|
+
if (params.convergenceState === 'converging') return 'skip'
|
|
53
|
+
if (params.convergenceState === 'diverging') return 'abort'
|
|
54
|
+
return 'skip'
|
|
17
55
|
}
|
|
18
56
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
completedNodes: number
|
|
25
|
-
failedNodes: number
|
|
26
|
-
previousCompletedNodes?: number
|
|
27
|
-
previousFailedNodes?: number
|
|
28
|
-
}): ConvergenceState {
|
|
29
|
-
const completionRatio = params.totalNodes > 0 ? params.completedNodes / params.totalNodes : 0
|
|
30
|
-
const failureRatio = params.totalNodes > 0 ? params.failedNodes / params.totalNodes : 0
|
|
31
|
-
|
|
32
|
-
if (params.previousCompletedNodes !== undefined) {
|
|
33
|
-
const completionVelocity = params.completedNodes - params.previousCompletedNodes
|
|
34
|
-
const failureVelocity = params.failedNodes - (params.previousFailedNodes ?? 0)
|
|
35
|
-
|
|
36
|
-
if (completionVelocity > 0 && failureVelocity === 0) return 'converging'
|
|
37
|
-
if (completionVelocity === 0 && failureVelocity === 0) return 'stalled'
|
|
38
|
-
if (failureVelocity > 0) return 'diverging'
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (completionRatio > 0.5 && failureRatio === 0) return 'converging'
|
|
42
|
-
if (failureRatio > 0.3) return 'diverging'
|
|
43
|
-
return 'progressing'
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
decideRerouteAction(params: {
|
|
47
|
-
failedNodeId: string
|
|
48
|
-
retryCount: number
|
|
49
|
-
maxRetries: number
|
|
50
|
-
convergenceState: ConvergenceState
|
|
51
|
-
}): 'retry' | 'skip' | 'abort' {
|
|
52
|
-
if (params.retryCount < params.maxRetries) return 'retry'
|
|
53
|
-
if (params.convergenceState === 'converging') return 'skip'
|
|
54
|
-
if (params.convergenceState === 'diverging') return 'abort'
|
|
55
|
-
return 'skip'
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
async routeGraphFull(params: { threadId: string; runId: string }): Promise<void> {
|
|
59
|
-
const MAX_ROUNDS = 32
|
|
60
|
-
const STRUCTURAL_TYPES = new Set(['switch', 'join', 'deliberation-fork'])
|
|
61
|
-
|
|
62
|
-
// Dynamic imports to avoid circular dependencies
|
|
63
|
-
const { planRunService } = await import('./plan-run.service')
|
|
64
|
-
const { ownershipDispatcherService } = await import('./ownership-dispatcher.service')
|
|
65
|
-
const { planExecutorService } = await import('./plan-executor.service')
|
|
66
|
-
|
|
67
|
-
let round = 0
|
|
68
|
-
for (; round < MAX_ROUNDS; round++) {
|
|
69
|
-
const run = await planRunService.getRunById(params.runId)
|
|
70
|
-
if (STABLE_RUN_STATUSES.has(run.status)) break
|
|
71
|
-
|
|
72
|
-
const spec = await planRunService.getPlanSpecById(run.planSpecId)
|
|
73
|
-
const nodeSpecs = await planRunService.listNodeSpecs(spec.id)
|
|
74
|
-
const nodeRuns = await planRunService.listNodeRuns(run.id)
|
|
75
|
-
|
|
76
|
-
// Find ready action nodes (not structural, not human — those are handled by syncRunGraph)
|
|
77
|
-
const readyNodes = nodeRuns.filter((nr) => {
|
|
78
|
-
if (nr.status !== 'ready') return false
|
|
79
|
-
const ns = nodeSpecs.find((s) => s.nodeId === nr.nodeId)
|
|
80
|
-
return ns && ns.owner.executorType !== 'user' && !STRUCTURAL_TYPES.has(ns.type)
|
|
81
|
-
})
|
|
82
|
-
if (readyNodes.length === 0) break
|
|
83
|
-
|
|
84
|
-
// Split into silent (dispatch now) and visible (enqueue heartbeat wake)
|
|
85
|
-
const silentNodes = readyNodes.filter((nr) => {
|
|
86
|
-
const ns = nodeSpecs.find((s) => s.nodeId === nr.nodeId)
|
|
87
|
-
return ns && !shouldPlanNodeUseVisibleTurn(spec, ns)
|
|
88
|
-
})
|
|
89
|
-
const visibleNodes = readyNodes.filter((nr) => {
|
|
90
|
-
const ns = nodeSpecs.find((s) => s.nodeId === nr.nodeId)
|
|
91
|
-
return ns && shouldPlanNodeUseVisibleTurn(spec, ns)
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
// Transition all ready nodes to 'running' BEFORE dispatching
|
|
95
|
-
for (const nodeRun of readyNodes) {
|
|
96
|
-
await planExecutorService.transitionNodeToRunning({ runId: params.runId, nodeId: nodeRun.nodeId })
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Enqueue heartbeat wakes for visible agent nodes — they need a streaming turn
|
|
100
|
-
if (visibleNodes.length > 0) {
|
|
101
|
-
const { enqueuePlanAgentHeartbeatWake } = await import('../queues/plan-agent-heartbeat.queue')
|
|
102
|
-
const updatedRunForWake = await planRunService.getRunById(params.runId)
|
|
103
|
-
for (const nodeRun of visibleNodes) {
|
|
104
|
-
const ns = nodeSpecs.find((s) => s.nodeId === nodeRun.nodeId)
|
|
105
|
-
if (!ns || ns.owner.executorType !== 'agent') continue
|
|
106
|
-
await enqueuePlanAgentHeartbeatWake({
|
|
107
|
-
organizationId: recordIdToString(updatedRunForWake.organizationId, TABLES.ORGANIZATION),
|
|
108
|
-
threadId: recordIdToString(updatedRunForWake.threadId, TABLES.THREAD),
|
|
109
|
-
runId: recordIdToString(updatedRunForWake.id, TABLES.PLAN_RUN),
|
|
110
|
-
nodeId: nodeRun.nodeId,
|
|
111
|
-
agentId: ns.owner.ref,
|
|
112
|
-
reason: 'graph-full-visible',
|
|
113
|
-
})
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// If no silent nodes to dispatch, break and let heartbeat handle visible ones
|
|
118
|
-
if (silentNodes.length === 0) break
|
|
119
|
-
|
|
120
|
-
// Re-fetch run after transitions for accurate state in dispatch context
|
|
121
|
-
const updatedRun = await planRunService.getRunById(params.runId)
|
|
122
|
-
|
|
123
|
-
// Dispatch silent nodes in parallel with LINEAR mode override (prevents recursion)
|
|
124
|
-
const results = await Promise.allSettled(
|
|
125
|
-
silentNodes.map(async (nodeRun) => {
|
|
126
|
-
const nodeSpecRecord = nodeSpecs.find((ns) => ns.nodeId === nodeRun.nodeId)
|
|
127
|
-
if (!nodeSpecRecord) {
|
|
128
|
-
throw new Error(`Node spec not found for node "${nodeRun.nodeId}".`)
|
|
129
|
-
}
|
|
130
|
-
// Re-fetch the node run to get the updated 'running' state with resolvedInput
|
|
131
|
-
const updatedNodeRun = await planRunService.getNodeRunByNodeId(updatedRun.id, nodeRun.nodeId)
|
|
132
|
-
const result = await ownershipDispatcherService.dispatchReadyNode({
|
|
133
|
-
run: updatedRun,
|
|
134
|
-
nodeSpecRecord,
|
|
135
|
-
nodeRun: updatedNodeRun,
|
|
136
|
-
spec,
|
|
137
|
-
executionModeOverride: 'linear',
|
|
138
|
-
})
|
|
139
|
-
return { nodeId: nodeRun.nodeId, ownerRef: nodeSpecRecord.owner.ref, result }
|
|
140
|
-
}),
|
|
141
|
-
)
|
|
57
|
+
interface GlobalOrchestratorDeps {
|
|
58
|
+
ownershipDispatcherService: Pick<OwnershipDispatcherService, 'dispatchReadyNode'>
|
|
59
|
+
planExecutorService: ReturnType<typeof makePlanExecutorService>
|
|
60
|
+
planRunService: ReturnType<typeof makePlanRunService>
|
|
61
|
+
}
|
|
142
62
|
|
|
143
|
-
|
|
144
|
-
|
|
63
|
+
type DispatchReadyNodeParams = {
|
|
64
|
+
run: PlanRunRecord
|
|
65
|
+
nodeSpecRecord: PlanNodeSpecRecord
|
|
66
|
+
nodeRun: PlanNodeRunRecord
|
|
67
|
+
spec: PlanSpecRecord
|
|
68
|
+
executionModeOverride?: ExecutionMode
|
|
69
|
+
}
|
|
145
70
|
|
|
146
|
-
|
|
147
|
-
for (let i = 0; i < results.length; i++) {
|
|
148
|
-
const settled = results[i]
|
|
149
|
-
const nodeRun = silentNodes[i]
|
|
150
|
-
const nodeSpecRecord = nodeSpecs.find((ns) => ns.nodeId === nodeRun.nodeId)
|
|
71
|
+
type DispatchReadyNode = (params: DispatchReadyNodeParams) => Effect.Effect<PlanNodeResultSubmission, unknown, never>
|
|
151
72
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
threadId,
|
|
155
|
-
runId,
|
|
156
|
-
nodeId: settled.value.nodeId,
|
|
157
|
-
emittedBy: settled.value.ownerRef,
|
|
158
|
-
result: settled.value.result,
|
|
159
|
-
})
|
|
160
|
-
} else {
|
|
161
|
-
serverLogger.warn`routeGraphFull: dispatch failed for node "${nodeRun.nodeId}": ${settled.reason}`
|
|
162
|
-
await planExecutorService.blockNodeOnDispatchFailure({
|
|
163
|
-
threadId,
|
|
164
|
-
runId,
|
|
165
|
-
nodeId: nodeRun.nodeId,
|
|
166
|
-
emittedBy: nodeSpecRecord?.owner.ref ?? 'unknown',
|
|
167
|
-
message: formatDispatchError(settled.reason),
|
|
168
|
-
failureClass: classifyDispatchFailure(nodeSpecRecord?.owner.executorType ?? 'agent', settled.reason),
|
|
169
|
-
})
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
}
|
|
73
|
+
export function makeGlobalOrchestratorService(deps: GlobalOrchestratorDeps) {
|
|
74
|
+
const dispatchReadyNode: DispatchReadyNode = (params) => deps.ownershipDispatcherService.dispatchReadyNode(params)
|
|
173
75
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
76
|
+
return {
|
|
77
|
+
detectConvergence,
|
|
78
|
+
decideRerouteAction,
|
|
79
|
+
routeGraphFull: (params: { threadId: string; runId: string }) =>
|
|
80
|
+
routeGraphFullEffect(params, {
|
|
81
|
+
dispatchReadyNode,
|
|
82
|
+
planExecutorService: deps.planExecutorService,
|
|
83
|
+
planRunService: deps.planRunService,
|
|
84
|
+
}),
|
|
177
85
|
}
|
|
178
86
|
}
|
|
179
87
|
|
|
180
|
-
export
|
|
88
|
+
export class GlobalOrchestratorServiceTag extends Context.Service<
|
|
89
|
+
GlobalOrchestratorServiceTag,
|
|
90
|
+
ReturnType<typeof makeGlobalOrchestratorService>
|
|
91
|
+
>()('@lota-sdk/core/GlobalOrchestratorService') {}
|
|
92
|
+
|
|
93
|
+
export const GlobalOrchestratorServiceLive = Layer.effect(
|
|
94
|
+
GlobalOrchestratorServiceTag,
|
|
95
|
+
Effect.gen(function* () {
|
|
96
|
+
const ownershipDispatcherService = yield* OwnershipDispatcherServiceTag
|
|
97
|
+
const planExecutorService = yield* PlanExecutorServiceTag
|
|
98
|
+
const planRunService = yield* PlanRunServiceTag
|
|
99
|
+
return makeGlobalOrchestratorService({ ownershipDispatcherService, planExecutorService, planRunService })
|
|
100
|
+
}),
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
const routeGraphFullWithRuntime = Effect.fn('GlobalOrchestrator.routeGraphFullWithRuntime')(function* (params: {
|
|
104
|
+
threadId: string
|
|
105
|
+
runId: string
|
|
106
|
+
}) {
|
|
107
|
+
const globalOrchestratorService = yield* GlobalOrchestratorServiceTag
|
|
108
|
+
return yield* globalOrchestratorService.routeGraphFull(params)
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
export function routeGraphFull(params: { threadId: string; runId: string }): Promise<void> {
|
|
112
|
+
return runPromise(routeGraphFullWithRuntime(params))
|
|
113
|
+
}
|