@lota-sdk/core 0.1.15 → 0.1.17

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.
Files changed (159) hide show
  1. package/infrastructure/schema/00_identity.surql +0 -2
  2. package/infrastructure/schema/01_memory.surql +1 -1
  3. package/infrastructure/schema/02_execution_plan.surql +62 -1
  4. package/infrastructure/schema/03_learned_skill.surql +1 -1
  5. package/infrastructure/schema/06_playbook.surql +25 -0
  6. package/infrastructure/schema/07_institutional_memory.surql +13 -0
  7. package/infrastructure/schema/08_quality_metrics.surql +17 -0
  8. package/package.json +12 -8
  9. package/src/ai/definitions.ts +81 -3
  10. package/src/ai/embedding-cache.ts +2 -4
  11. package/src/ai/index.ts +0 -2
  12. package/src/bifrost/bifrost.ts +2 -7
  13. package/src/bifrost/cache-headers.ts +8 -0
  14. package/src/bifrost/index.ts +1 -0
  15. package/src/config/agent-defaults.ts +31 -21
  16. package/src/config/agent-types.ts +11 -0
  17. package/src/config/constants.ts +2 -14
  18. package/src/config/debug-logger.ts +5 -1
  19. package/src/config/index.ts +3 -0
  20. package/src/config/model-constants.ts +16 -34
  21. package/src/config/search.ts +1 -15
  22. package/src/create-runtime.ts +269 -178
  23. package/src/db/cursor-pagination.ts +3 -6
  24. package/src/db/index.ts +2 -0
  25. package/src/db/memory-store.helpers.ts +1 -3
  26. package/src/db/memory-store.rows.ts +7 -7
  27. package/src/db/memory-store.ts +14 -18
  28. package/src/db/memory.ts +13 -13
  29. package/src/db/schema-fingerprint.ts +1 -3
  30. package/src/db/service.ts +153 -79
  31. package/src/db/startup.ts +6 -10
  32. package/src/db/surreal-mutation.ts +43 -0
  33. package/src/db/tables.ts +7 -0
  34. package/src/db/workstream-message-row.ts +15 -0
  35. package/src/embeddings/provider.ts +1 -1
  36. package/src/queues/context-compaction.queue.ts +15 -46
  37. package/src/queues/delayed-node-promotion.queue.ts +41 -0
  38. package/src/queues/document-processor.queue.ts +2 -4
  39. package/src/queues/index.ts +3 -0
  40. package/src/queues/memory-consolidation.queue.ts +16 -51
  41. package/src/queues/plan-scheduler.queue.ts +97 -0
  42. package/src/queues/post-chat-memory.queue.ts +20 -55
  43. package/src/queues/queue-factory.ts +100 -0
  44. package/src/queues/recent-activity-title-refinement.queue.ts +15 -50
  45. package/src/queues/regular-chat-memory-digest.queue.ts +16 -52
  46. package/src/queues/skill-extraction.queue.ts +15 -47
  47. package/src/queues/workstream-title-generation.queue.ts +15 -47
  48. package/src/redis/connection.ts +6 -0
  49. package/src/redis/index.ts +1 -1
  50. package/src/redis/redis-lease-lock.ts +1 -2
  51. package/src/redis/stream-context.ts +11 -0
  52. package/src/runtime/agent-runtime-policy.ts +109 -35
  53. package/src/runtime/approval-continuation.ts +12 -6
  54. package/src/runtime/context-compaction-runtime.ts +1 -1
  55. package/src/runtime/context-compaction.ts +24 -64
  56. package/src/runtime/execution-plan.ts +22 -18
  57. package/src/runtime/graph-designer.ts +15 -0
  58. package/src/runtime/helper-model.ts +9 -197
  59. package/src/runtime/index.ts +3 -1
  60. package/src/runtime/llm-content.ts +1 -1
  61. package/src/runtime/memory-block.ts +9 -11
  62. package/src/runtime/memory-pipeline.ts +6 -9
  63. package/src/runtime/plugin-resolution.ts +35 -0
  64. package/src/runtime/plugin-types.ts +72 -0
  65. package/src/runtime/retrieval-adapters.ts +1 -1
  66. package/src/runtime/runtime-config.ts +111 -14
  67. package/src/runtime/runtime-extensions.ts +2 -3
  68. package/src/runtime/runtime-worker-registry.ts +6 -0
  69. package/src/runtime/social-chat.ts +752 -0
  70. package/src/runtime/team-consultation-orchestrator.ts +45 -32
  71. package/src/runtime/team-consultation-prompts.ts +11 -2
  72. package/src/runtime/title-helpers.ts +2 -4
  73. package/src/runtime/workstream-chat-helpers.ts +1 -1
  74. package/src/services/adaptive-playbook.service.ts +152 -0
  75. package/src/services/agent-executor.service.ts +292 -0
  76. package/src/services/artifact-provenance.service.ts +172 -0
  77. package/src/services/attachment.service.ts +6 -11
  78. package/src/services/context-compaction.service.ts +72 -55
  79. package/src/services/context-enrichment.service.ts +33 -0
  80. package/src/services/coordination-registry.service.ts +117 -0
  81. package/src/services/document-chunk.service.ts +2 -4
  82. package/src/services/domain-agent-executor.service.ts +71 -0
  83. package/src/services/execution-plan.service.ts +269 -50
  84. package/src/services/feedback-loop.service.ts +96 -0
  85. package/src/services/global-orchestrator.service.ts +148 -0
  86. package/src/services/index.ts +27 -0
  87. package/src/services/institutional-memory.service.ts +145 -0
  88. package/src/services/learned-skill.service.ts +24 -5
  89. package/src/services/memory-assessment.service.ts +3 -2
  90. package/src/services/memory-utils.ts +3 -8
  91. package/src/services/memory.service.ts +49 -61
  92. package/src/services/monitoring-window.service.ts +86 -0
  93. package/src/services/mutating-approval.service.ts +1 -1
  94. package/src/services/node-workspace.service.ts +155 -0
  95. package/src/services/notification.service.ts +39 -0
  96. package/src/services/organization-member.service.ts +11 -4
  97. package/src/services/organization.service.ts +5 -5
  98. package/src/services/ownership-dispatcher.service.ts +403 -0
  99. package/src/services/plan-approval.service.ts +1 -1
  100. package/src/services/plan-builder.service.ts +1 -0
  101. package/src/services/plan-checkpoint.service.ts +30 -2
  102. package/src/services/plan-compiler.service.ts +5 -0
  103. package/src/services/plan-coordination.service.ts +152 -0
  104. package/src/services/plan-cycle.service.ts +284 -0
  105. package/src/services/plan-deadline.service.ts +287 -0
  106. package/src/services/plan-executor.service.ts +384 -40
  107. package/src/services/plan-run.service.ts +41 -7
  108. package/src/services/plan-scheduler.service.ts +240 -0
  109. package/src/services/plan-template.service.ts +117 -0
  110. package/src/services/plan-validator.service.ts +84 -2
  111. package/src/services/plan-workspace.service.ts +83 -0
  112. package/src/services/playbook-registry.service.ts +67 -0
  113. package/src/services/plugin-executor.service.ts +103 -0
  114. package/src/services/quality-metrics.service.ts +132 -0
  115. package/src/services/recent-activity.service.ts +28 -34
  116. package/src/services/skill-resolver.service.ts +19 -0
  117. package/src/services/social-chat-history.service.ts +197 -0
  118. package/src/services/system-executor.service.ts +105 -0
  119. package/src/services/workstream-message.service.ts +13 -37
  120. package/src/services/workstream-plan-registry.service.ts +22 -0
  121. package/src/services/workstream-title.service.ts +3 -1
  122. package/src/services/workstream-turn-preparation.service.ts +34 -89
  123. package/src/services/workstream.service.ts +33 -55
  124. package/src/services/workstream.types.ts +9 -9
  125. package/src/services/write-intent-validator.service.ts +81 -0
  126. package/src/storage/attachment-parser.ts +1 -1
  127. package/src/storage/attachment-utils.ts +1 -1
  128. package/src/storage/generated-document-storage.service.ts +3 -2
  129. package/src/system-agents/context-compaction.agent.ts +2 -0
  130. package/src/system-agents/delegated-agent-factory.ts +5 -0
  131. package/src/system-agents/memory-reranker.agent.ts +4 -2
  132. package/src/system-agents/memory.agent.ts +2 -0
  133. package/src/system-agents/recent-activity-title-refiner.agent.ts +2 -0
  134. package/src/system-agents/regular-chat-memory-digest.agent.ts +2 -0
  135. package/src/system-agents/skill-extractor.agent.ts +2 -0
  136. package/src/system-agents/skill-manager.agent.ts +2 -0
  137. package/src/system-agents/title-generator.agent.ts +2 -0
  138. package/src/tools/execution-plan.tool.ts +17 -23
  139. package/src/tools/index.ts +0 -1
  140. package/src/tools/research-topic.tool.ts +2 -0
  141. package/src/tools/team-think.tool.ts +5 -6
  142. package/src/utils/async.ts +2 -1
  143. package/src/utils/date-time.ts +4 -32
  144. package/src/utils/env.ts +8 -0
  145. package/src/utils/errors.ts +42 -10
  146. package/src/utils/index.ts +9 -0
  147. package/src/utils/string.ts +114 -1
  148. package/src/workers/index.ts +1 -0
  149. package/src/workers/regular-chat-memory-digest.helpers.ts +1 -1
  150. package/src/workers/regular-chat-memory-digest.runner.ts +45 -12
  151. package/src/workers/skill-extraction.runner.ts +26 -6
  152. package/src/workers/utils/file-section-chunker.ts +2 -1
  153. package/src/workers/utils/repo-structure-extractor.ts +2 -2
  154. package/src/workers/utils/repomix-file-sections.ts +2 -2
  155. package/src/workers/utils/sandbox-error.ts +11 -2
  156. package/src/workers/utils/workstream-message-query.ts +14 -25
  157. package/src/workers/worker-utils.ts +2 -2
  158. package/src/runtime/workstream-routing-policy.ts +0 -267
  159. package/src/tools/log-hello-world.tool.ts +0 -17
@@ -1,18 +1,14 @@
1
+ import type { ExecutionMode, PlanArtifactSubmission, PlanNodeSpec } from '@lota-sdk/shared'
2
+
1
3
  import { getLeadAgentId } from '../config/agent-defaults'
2
4
  import { resolveOnboardingOwnerAgentId } from '../config/workstream-defaults'
3
5
  import type { ChatMode } from './agent-types'
4
- import { resolveReasoningProfile } from './workstream-routing-policy'
5
- import type { ReasoningProfileName } from './workstream-routing-policy'
6
-
7
6
  export interface AgentRuntimeConfig<TAgent extends string> {
8
7
  id: TAgent
9
8
  displayName: string
10
9
  mode: ChatMode
11
10
  extraInstructions?: string
12
11
  maxSteps: number
13
- reasoningProfile: ReasoningProfileName
14
- toolCallBudget: number
15
- maxInputTokensHint: number
16
12
  }
17
13
 
18
14
  export interface AgentToolPolicy<TSkill extends PropertyKey> {
@@ -31,6 +27,30 @@ export interface AgentToolPolicy<TSkill extends PropertyKey> {
31
27
  includeIndexedRepository: boolean
32
28
  }
33
29
 
30
+ export const OWNERSHIP_DISPATCH_BLOCKED_TOOL_NAMES = Object.freeze([
31
+ 'conversationSearch',
32
+ 'createExecutionPlan',
33
+ 'replaceExecutionPlan',
34
+ 'submitExecutionNodeResult',
35
+ 'listExecutionPlans',
36
+ 'getExecutionPlanDetails',
37
+ 'resumeExecutionPlanRun',
38
+ 'consultSpecialist',
39
+ 'consultTeam',
40
+ 'teamThink',
41
+ ])
42
+
43
+ function buildOwnershipDispatchArtifactPayload(artifacts: PlanArtifactSubmission[]) {
44
+ return artifacts.map((artifact) => ({
45
+ name: artifact.name,
46
+ kind: artifact.kind,
47
+ pointer: artifact.pointer,
48
+ ...(artifact.schemaRef ? { schemaRef: artifact.schemaRef } : {}),
49
+ ...(artifact.description ? { description: artifact.description } : {}),
50
+ ...(artifact.payload !== undefined ? { payload: artifact.payload } : {}),
51
+ }))
52
+ }
53
+
34
54
  export function toChatMode(workstreamMode: 'direct' | 'group'): ChatMode {
35
55
  return workstreamMode === 'direct' ? 'fixedWorkstreamMode' : 'workstreamMode'
36
56
  }
@@ -74,7 +94,6 @@ export function buildAgentRuntimeConfig<TAgent extends string, TSkill extends Pr
74
94
  skills?: TSkill[]
75
95
  onboardingActive: boolean
76
96
  linearInstalled: boolean
77
- reasoningProfile?: ReasoningProfileName
78
97
  systemWorkspaceDetails?: string
79
98
  preSeededMemoriesSection?: string
80
99
  retrievedKnowledgeSection?: string
@@ -89,7 +108,6 @@ export function buildAgentRuntimeConfig<TAgent extends string, TSkill extends Pr
89
108
  buildOnboardingPromptSection: () => string
90
109
  }): AgentRuntimeConfig<TAgent> {
91
110
  const mode = params.mode ?? toChatMode(params.workstreamMode)
92
- const profile = resolveReasoningProfile({ message: '', explicitProfile: params.reasoningProfile ?? 'standard' })
93
111
  const rulesSection = params.buildGlobalRuleInstructionSection()
94
112
  const skillsSection =
95
113
  params.skills && params.skills.length > 0 ? params.buildSkillInstructionSection(params.skills) : ''
@@ -115,10 +133,7 @@ export function buildAgentRuntimeConfig<TAgent extends string, TSkill extends Pr
115
133
  displayName: params.displayNameByAgent[params.agentId] ?? params.agentId,
116
134
  mode,
117
135
  extraInstructions,
118
- maxSteps: profile.maxSteps,
119
- reasoningProfile: profile.name,
120
- toolCallBudget: profile.toolCallBudget,
121
- maxInputTokensHint: profile.maxInputTokensHint,
136
+ maxSteps: 15,
122
137
  }
123
138
  }
124
139
 
@@ -154,43 +169,102 @@ export function buildWorkstreamAgentToolPolicy<TAgent extends string, TSkill ext
154
169
  includeReadFileParts: true,
155
170
  includeInspectWebsite: params.onboardingActive && params.agentId === onboardingOwnerAgentId,
156
171
  includeProceedInOnboarding: params.onboardingActive && params.agentId === onboardingOwnerAgentId,
157
- includeGithubIntegration: params.onboardingActive && params.agentId === onboardingOwnerAgentId,
158
- includeIndexRepositoryByURL: params.onboardingActive && params.agentId === onboardingOwnerAgentId,
172
+ includeGithubIntegration: params.agentId === onboardingOwnerAgentId,
173
+ includeIndexRepositoryByURL: params.agentId === onboardingOwnerAgentId,
159
174
  includeIndexedRepository: params.githubInstalled && params.provideRepoTool,
160
175
  }
161
176
  }
162
177
 
163
- export function buildTeamConsultationAgentToolPolicy<TAgent extends string, TSkill extends PropertyKey>(params: {
164
- agentId: TAgent
165
- blockedSkills: readonly TSkill[]
178
+ export function buildTeamConsultationAgentToolPolicy({
179
+ githubInstalled,
180
+ provideRepoTool,
181
+ blockedToolNames,
182
+ }: {
166
183
  blockedToolNames: readonly string[]
167
184
  githubInstalled: boolean
168
185
  provideRepoTool: boolean
169
- getAgentSkills: (agentId: TAgent, mode: ChatMode) => TSkill[]
170
- }): AgentToolPolicy<TSkill> & { blockedToolNames: Set<string> } {
171
- const skills = resolveActiveAgentSkills({
172
- agentId: params.agentId,
173
- workstreamMode: 'group',
174
- mode: 'fixedWorkstreamMode',
175
- onboardingActive: false,
176
- linearInstalled: false,
177
- getAgentSkills: params.getAgentSkills,
178
- }).filter((skill) => !params.blockedSkills.includes(skill))
179
-
186
+ }): AgentToolPolicy<string> & { blockedToolNames: Set<string> } {
180
187
  return {
181
188
  resolvedMode: 'fixedWorkstreamMode',
182
- skills,
183
- includeMemorySearch: true,
184
- includeConversationSearch: true,
189
+ skills: [],
190
+ includeMemorySearch: false,
191
+ includeConversationSearch: false,
185
192
  includeMemoryRemember: false,
186
- includeOrgActionSearch: true,
193
+ includeOrgActionSearch: false,
187
194
  includeMemoryBlockAppend: false,
188
- includeReadFileParts: true,
195
+ includeReadFileParts: false,
189
196
  includeInspectWebsite: false,
190
197
  includeProceedInOnboarding: false,
191
198
  includeGithubIntegration: false,
192
199
  includeIndexRepositoryByURL: false,
193
- includeIndexedRepository: params.githubInstalled && params.provideRepoTool,
194
- blockedToolNames: new Set(params.blockedToolNames),
200
+ includeIndexedRepository: githubInstalled && provideRepoTool,
201
+ blockedToolNames: new Set(blockedToolNames),
202
+ }
203
+ }
204
+
205
+ export function buildOwnershipDispatchContextSection(params: {
206
+ node: PlanNodeSpec
207
+ resolvedInput: Record<string, unknown>
208
+ inputArtifacts: PlanArtifactSubmission[]
209
+ }): string {
210
+ const payload = {
211
+ node: {
212
+ id: params.node.id,
213
+ label: params.node.label,
214
+ owner: params.node.owner,
215
+ objective: params.node.objective,
216
+ instructions: params.node.instructions,
217
+ outputSchemaRef: params.node.outputSchemaRef ?? null,
218
+ deliverables: params.node.deliverables,
219
+ successCriteria: params.node.successCriteria,
220
+ completionChecks: params.node.completionChecks,
221
+ toolPolicy: params.node.toolPolicy,
222
+ contextPolicy: params.node.contextPolicy,
223
+ },
224
+ resolvedInput: params.resolvedInput,
225
+ inputArtifacts: buildOwnershipDispatchArtifactPayload(params.inputArtifacts),
195
226
  }
227
+
228
+ return [
229
+ '<ownership-dispatch-execution>',
230
+ 'You are executing a single isolated execution-plan node.',
231
+ 'Do not ask the user questions. Do not reference any hidden or prior workstream chat history.',
232
+ 'Use only the provided node context, resolved input, and input artifacts.',
233
+ 'Return only the final structured node result that satisfies the required output contract.',
234
+ JSON.stringify(payload, null, 2),
235
+ '</ownership-dispatch-execution>',
236
+ ].join('\n')
237
+ }
238
+
239
+ export function buildOwnershipDispatchResponseGuard(params: {
240
+ node: PlanNodeSpec
241
+ executionMode?: ExecutionMode
242
+ }): string {
243
+ const mode = params.executionMode ?? 'linear'
244
+
245
+ if (mode === 'linear') {
246
+ return [
247
+ '<ownership-dispatch-result-contract>',
248
+ 'Return a single JSON object with this exact shape:',
249
+ '{"structuredOutput"?: object, "artifacts": Array<{ "name": string, "kind": "json"|"markdown"|"file"|"external-ref"|"record", "pointer": string, "schemaRef"?: string, "description"?: string, "payload"?: object|array }>, "notes"?: string}',
250
+ 'Do not wrap the JSON in markdown or code fences.',
251
+ `Node label: ${params.node.label}`,
252
+ `Required deliverables: ${params.node.deliverables.length > 0 ? params.node.deliverables.map((item) => item.name).join(', ') : 'none'}`,
253
+ '</ownership-dispatch-result-contract>',
254
+ ].join('\n')
255
+ }
256
+
257
+ return [
258
+ '<ownership-dispatch-result-contract>',
259
+ 'Produce outputs by calling the writeIntent tool for each deliverable.',
260
+ `Required deliverables: ${
261
+ params.node.deliverables
262
+ .filter((d) => d.required)
263
+ .map((d) => d.name)
264
+ .join(', ') || 'none'
265
+ }`,
266
+ 'If writeIntent returns validation_failed, correct and re-call.',
267
+ 'After all writes, return a brief summary.',
268
+ '</ownership-dispatch-result-contract>',
269
+ ].join('\n')
196
270
  }
@@ -45,21 +45,27 @@ export function readApprovalContinuationResponse(message: ChatMessage): Approval
45
45
  }
46
46
 
47
47
  export function isApprovalContinuationRequest(messages: ChatMessage[]): boolean {
48
- const lastAssistant = [...messages].reverse().find((message) => message.role === 'assistant')
49
- if (!lastAssistant) return false
48
+ let lastAssistantIndex = -1
49
+ for (let i = messages.length - 1; i >= 0; i--) {
50
+ if (messages[i].role === 'assistant') {
51
+ lastAssistantIndex = i
52
+ break
53
+ }
54
+ }
55
+ if (lastAssistantIndex < 0) return false
50
56
 
51
- const lastMessageIndex = messages.lastIndexOf(lastAssistant)
52
- const hasUserAfter = messages.slice(lastMessageIndex + 1).some((message) => message.role === 'user')
57
+ const hasUserAfter = messages.slice(lastAssistantIndex + 1).some((message) => message.role === 'user')
53
58
  if (hasUserAfter) return false
54
59
 
55
- return hasApprovalRespondedParts(lastAssistant)
60
+ return hasApprovalRespondedParts(messages[lastAssistantIndex])
56
61
  }
57
62
 
58
63
  const PLAN_TOOL_NAMES = new Set([
59
64
  'createExecutionPlan',
60
65
  'replaceExecutionPlan',
61
66
  'submitExecutionNodeResult',
62
- 'getActiveExecutionPlan',
67
+ 'listExecutionPlans',
68
+ 'getExecutionPlanDetails',
63
69
  'resumeExecutionPlanRun',
64
70
  ])
65
71
 
@@ -76,7 +76,7 @@ export function createWiredContextCompactionRuntime(deps: CreateContextCompactio
76
76
  const newEntriesText = params.newEntriesText.trim()
77
77
  if (!previousSummary && !newEntriesText) return ''
78
78
 
79
- return await helperModelRuntime.generateHelperText({
79
+ return helperModelRuntime.generateHelperText({
80
80
  tag: 'memory-block-compaction',
81
81
  createAgent: createContextCompactionAgent,
82
82
  messages: [{ role: 'user', content: buildMemoryBlockCompactionPrompt({ previousSummary, newEntriesText }) }],
@@ -1,8 +1,6 @@
1
- import { createHash, randomUUID } from 'node:crypto'
2
-
3
1
  import type { ChatMessage } from '@lota-sdk/shared'
4
2
 
5
- import { CHARS_PER_TOKEN_ESTIMATE, compactWhitespace, readRecord, readString } from '../utils/string'
3
+ import { CHARS_PER_TOKEN_ESTIMATE, compactWhitespace, readRecord, readString, stringifyUnknown } from '../utils/string'
6
4
  import {
7
5
  COMPACTION_CHUNK_MAX_CHARS,
8
6
  CONTEXT_COMPACTION_INCLUDED_TOOL_NAMES,
@@ -108,24 +106,10 @@ function createStableId(prefix: string, ...parts: Array<string | number | undefi
108
106
  .map((part) => (part === undefined ? '' : String(part)))
109
107
  .map((part) => compactWhitespace(part))
110
108
  .join('|')
111
- const hash = createHash('sha1').update(`${prefix}|${payload}`).digest('hex').slice(0, 20)
109
+ const hash = new Bun.CryptoHasher('sha1').update(`${prefix}|${payload}`).digest('hex').slice(0, 20)
112
110
  return `${prefix}_${hash}`
113
111
  }
114
112
 
115
- function stringifyUnknown(value: unknown): string | null {
116
- if (value === undefined) return null
117
- if (typeof value === 'string') {
118
- const normalized = value.trim()
119
- return normalized.length > 0 ? normalized : null
120
- }
121
-
122
- try {
123
- return JSON.stringify(value)
124
- } catch {
125
- return null
126
- }
127
- }
128
-
129
113
  function appendUnique(values: string[], nextValues: string[]): string[] {
130
114
  const seen = new Set(values.map((value) => compactWhitespace(value).toLowerCase()))
131
115
  const merged = [...values]
@@ -546,7 +530,7 @@ export function createContextCompactionRuntime(
546
530
  options: CreateContextCompactionRuntimeOptions,
547
531
  ): ContextCompactionRuntime {
548
532
  const now = options.now ?? (() => Date.now())
549
- const randomId = options.randomId ?? (() => randomUUID())
533
+ const randomId = options.randomId ?? (() => crypto.randomUUID())
550
534
  const thresholdRatio = options.thresholdRatio ?? CONTEXT_COMPACTION_THRESHOLD_RATIO
551
535
  const outputReserveTokens = options.outputReserveTokens ?? CONTEXT_OUTPUT_RESERVE_TOKENS
552
536
  const safetyMarginTokens = options.safetyMarginTokens ?? CONTEXT_SAFETY_MARGIN_TOKENS
@@ -766,6 +750,24 @@ export function createContextCompactionRuntime(
766
750
  const initialPayload = JSON.stringify([...(summaryPayload ? [summaryPayload] : []), ...remainingMessages])
767
751
  const inputChars = initialPayload.length
768
752
 
753
+ const buildEarlyExitResult = (estimatedTokens: number): CompactHistoryResult => {
754
+ const exitSummaryPayload = buildSyntheticSummaryPayload(summaryText)
755
+ const outputPayload = JSON.stringify([...(exitSummaryPayload ? [exitSummaryPayload] : []), ...remainingMessages])
756
+ return {
757
+ compacted: compactedMessages.length > 0,
758
+ summaryText,
759
+ ...(lastCompactedMessageId ? { lastCompactedMessageId } : {}),
760
+ compactedMessages,
761
+ compactedMessageCount: compactedMessages.length,
762
+ remainingMessageCount: remainingMessages.length,
763
+ estimatedTokens,
764
+ inputChars,
765
+ outputChars: outputPayload.length,
766
+ state,
767
+ stateDelta: mergedDelta,
768
+ }
769
+ }
770
+
769
771
  for (;;) {
770
772
  const assessment = shouldCompactHistory({
771
773
  summaryText,
@@ -774,40 +776,12 @@ export function createContextCompactionRuntime(
774
776
  })
775
777
 
776
778
  if (!assessment.shouldCompact) {
777
- const summaryPayload = buildSyntheticSummaryPayload(summaryText)
778
- const outputPayload = JSON.stringify([...(summaryPayload ? [summaryPayload] : []), ...remainingMessages])
779
- return {
780
- compacted: compactedMessages.length > 0,
781
- summaryText,
782
- ...(lastCompactedMessageId ? { lastCompactedMessageId } : {}),
783
- compactedMessages,
784
- compactedMessageCount: compactedMessages.length,
785
- remainingMessageCount: remainingMessages.length,
786
- estimatedTokens: assessment.estimatedTokens,
787
- inputChars,
788
- outputChars: outputPayload.length,
789
- state,
790
- stateDelta: mergedDelta,
791
- }
779
+ return buildEarlyExitResult(assessment.estimatedTokens)
792
780
  }
793
781
 
794
782
  const boundary = Math.max(0, remainingMessages.length - params.tailMessageCount)
795
783
  if (boundary <= 0) {
796
- const summaryPayload = buildSyntheticSummaryPayload(summaryText)
797
- const outputPayload = JSON.stringify([...(summaryPayload ? [summaryPayload] : []), ...remainingMessages])
798
- return {
799
- compacted: compactedMessages.length > 0,
800
- summaryText,
801
- ...(lastCompactedMessageId ? { lastCompactedMessageId } : {}),
802
- compactedMessages,
803
- compactedMessageCount: compactedMessages.length,
804
- remainingMessageCount: remainingMessages.length,
805
- estimatedTokens: assessment.estimatedTokens,
806
- inputChars,
807
- outputChars: outputPayload.length,
808
- state,
809
- stateDelta: mergedDelta,
810
- }
784
+ return buildEarlyExitResult(assessment.estimatedTokens)
811
785
  }
812
786
 
813
787
  const candidatePrefix = remainingMessages.slice(0, boundary)
@@ -816,21 +790,7 @@ export function createContextCompactionRuntime(
816
790
  const sourceText = toCompactionTranscript(contextMessages)
817
791
 
818
792
  if (!compactWhitespace(sourceText)) {
819
- const summaryPayload = buildSyntheticSummaryPayload(summaryText)
820
- const outputPayload = JSON.stringify([...(summaryPayload ? [summaryPayload] : []), ...remainingMessages])
821
- return {
822
- compacted: compactedMessages.length > 0,
823
- summaryText,
824
- ...(lastCompactedMessageId ? { lastCompactedMessageId } : {}),
825
- compactedMessages,
826
- compactedMessageCount: compactedMessages.length,
827
- remainingMessageCount: remainingMessages.length,
828
- estimatedTokens: assessment.estimatedTokens,
829
- inputChars,
830
- outputChars: outputPayload.length,
831
- state,
832
- stateDelta: mergedDelta,
833
- }
793
+ return buildEarlyExitResult(assessment.estimatedTokens)
834
794
  }
835
795
 
836
796
  let compactionOutput = await compactContextMessages({
@@ -2,16 +2,18 @@ import type { SerializableExecutionPlan } from '@lota-sdk/shared'
2
2
 
3
3
  const EXECUTION_PLAN_AGENT_PROTOCOL_PROMPT = `<execution-plan-protocol>
4
4
  - Before doing multi-step work, create a contract-driven execution plan instead of tracking steps only in prose.
5
+ - A workstream may have multiple active execution plans. Review all plans before creating new ones.
5
6
  - Plans are graph-capable workflow contracts. Every execution node must define objective, instructions, deliverables, success criteria, completion checks, retry policy, failure policy, and tool/context policy.
6
7
  - The runtime executor owns lifecycle truth. Do not claim that a node is complete until submitExecutionNodeResult succeeds.
7
- - Use execution-plan tools to create, replace, inspect, submit node results, and resume runs.
8
- - Treat the active execution run in <execution-plan-state> as authoritative. Do not mutate run or node status in prose.
8
+ - Use execution-plan tools to create, replace, inspect, and resume runs.
9
+ - Visible workstream agents do not manually submit node results; dispatched execution nodes are completed by the runtime executor.
10
+ - Treat the active execution runs in <execution-plan-state> as authoritative. Do not mutate run or node status in prose.
9
11
  - Work only on nodes that are active or explicitly ready for your executor. If a node is awaiting human input or approval, stop and let the runtime resume it.
10
12
  - If the graph, contracts, or success criteria materially change, replace the plan instead of silently drifting.
11
13
  </execution-plan-protocol>`
12
14
 
13
- function formatExecutionPlanForPrompt(plan: SerializableExecutionPlan | null | undefined): string | undefined {
14
- if (!plan) return undefined
15
+ function formatExecutionPlansForPrompt(plans: SerializableExecutionPlan[]): string | undefined {
16
+ if (plans.length === 0) return undefined
15
17
 
16
18
  const payload = {
17
19
  policy: {
@@ -21,46 +23,48 @@ function formatExecutionPlanForPrompt(plan: SerializableExecutionPlan | null | u
21
23
  artifactsAreFirstClassOutputs: true,
22
24
  checkpointRecoveryEnabled: true,
23
25
  },
24
- plan,
26
+ activePlans: plans,
27
+ planCount: plans.length,
25
28
  }
26
29
 
27
30
  return ['<execution-plan-state>', JSON.stringify(payload, null, 2), '</execution-plan-state>'].join('\n')
28
31
  }
29
32
 
30
33
  export function buildExecutionPlanInstructionSections(
31
- plan: SerializableExecutionPlan | null | undefined,
34
+ plans: SerializableExecutionPlan[] | null | undefined,
32
35
  ): string[] | undefined {
36
+ const normalized = plans ?? []
33
37
  const sections = [EXECUTION_PLAN_AGENT_PROTOCOL_PROMPT]
34
- const executionPlanStateSection = formatExecutionPlanForPrompt(plan)
35
- if (executionPlanStateSection) {
36
- sections.push(executionPlanStateSection)
38
+ const stateSection = formatExecutionPlansForPrompt(normalized)
39
+ if (stateSection) {
40
+ sections.push(stateSection)
37
41
  }
38
42
  return sections
39
43
  }
40
44
 
41
45
  export function createExecutionPlanInstructionSectionCache(params: {
42
46
  disabled?: boolean
43
- loadPlan: () => Promise<SerializableExecutionPlan | null | undefined>
47
+ loadPlans: () => Promise<SerializableExecutionPlan[]>
44
48
  }) {
45
- let planPromise: Promise<SerializableExecutionPlan | null | undefined> | null = null
49
+ let plansPromise: Promise<SerializableExecutionPlan[]> | null = null
46
50
  let sectionsPromise: Promise<string[] | undefined> | null = null
47
51
 
48
52
  return {
49
53
  invalidate() {
50
- planPromise = null
54
+ plansPromise = null
51
55
  sectionsPromise = null
52
56
  },
53
- async getPlan(): Promise<SerializableExecutionPlan | null | undefined> {
54
- if (params.disabled) return undefined
57
+ async getPlans(): Promise<SerializableExecutionPlan[]> {
58
+ if (params.disabled) return []
55
59
 
56
- planPromise ??= params.loadPlan()
57
- return await planPromise
60
+ plansPromise ??= params.loadPlans()
61
+ return plansPromise
58
62
  },
59
63
  async getSections(): Promise<string[] | undefined> {
60
64
  if (params.disabled) return undefined
61
65
 
62
- sectionsPromise ??= this.getPlan().then((plan) => buildExecutionPlanInstructionSections(plan))
63
- return await sectionsPromise
66
+ sectionsPromise ??= this.getPlans().then((plans) => buildExecutionPlanInstructionSections(plans))
67
+ return sectionsPromise
64
68
  },
65
69
  }
66
70
  }
@@ -0,0 +1,15 @@
1
+ import type { GraphDesignRequest, GraphDesignResponse } from '@lota-sdk/shared'
2
+
3
+ export interface GraphDesigner {
4
+ designGraph(request: GraphDesignRequest): Promise<GraphDesignResponse>
5
+ }
6
+
7
+ let _graphDesigner: GraphDesigner | null = null
8
+
9
+ export function configureGraphDesigner(designer: GraphDesigner): void {
10
+ _graphDesigner = designer
11
+ }
12
+
13
+ export function getGraphDesigner(): GraphDesigner | null {
14
+ return _graphDesigner
15
+ }