@swarmclawai/swarmclaw 1.2.3 → 1.2.5

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 (273) hide show
  1. package/README.md +20 -0
  2. package/bin/daemon-cmd.js +169 -0
  3. package/bin/server-cmd.js +3 -0
  4. package/bin/swarmclaw.js +11 -0
  5. package/package.json +17 -16
  6. package/src/app/api/agents/[id]/clone/route.ts +3 -32
  7. package/src/app/api/agents/[id]/route.ts +6 -158
  8. package/src/app/api/agents/[id]/status/route.ts +2 -3
  9. package/src/app/api/agents/[id]/thread/route.ts +4 -17
  10. package/src/app/api/agents/bulk/route.ts +5 -47
  11. package/src/app/api/agents/route.ts +5 -119
  12. package/src/app/api/agents/trash/route.ts +13 -24
  13. package/src/app/api/auth/route.ts +3 -9
  14. package/src/app/api/autonomy/estop/route.ts +5 -5
  15. package/src/app/api/chatrooms/[id]/chat/route.ts +11 -5
  16. package/src/app/api/chatrooms/[id]/route.ts +23 -2
  17. package/src/app/api/chatrooms/route.ts +13 -2
  18. package/src/app/api/chats/[id]/clear/route.ts +2 -13
  19. package/src/app/api/chats/[id]/deploy/route.ts +2 -3
  20. package/src/app/api/chats/[id]/edit-resend/route.ts +7 -13
  21. package/src/app/api/chats/[id]/mailbox/route.ts +6 -8
  22. package/src/app/api/chats/[id]/queue/route.ts +17 -64
  23. package/src/app/api/chats/[id]/retry/route.ts +4 -22
  24. package/src/app/api/chats/[id]/route.ts +10 -138
  25. package/src/app/api/chats/heartbeat/route.ts +2 -1
  26. package/src/app/api/chats/migrate-messages/route.ts +7 -0
  27. package/src/app/api/chats/route.ts +13 -134
  28. package/src/app/api/connectors/[id]/access/route.ts +12 -229
  29. package/src/app/api/connectors/[id]/doctor/route.ts +1 -1
  30. package/src/app/api/connectors/[id]/health/route.ts +12 -39
  31. package/src/app/api/connectors/[id]/route.ts +14 -122
  32. package/src/app/api/connectors/[id]/webhook/route.ts +1 -1
  33. package/src/app/api/connectors/doctor/route.ts +1 -1
  34. package/src/app/api/connectors/route.ts +12 -70
  35. package/src/app/api/credentials/[id]/route.ts +2 -4
  36. package/src/app/api/credentials/route.ts +10 -19
  37. package/src/app/api/daemon/health-check/route.ts +3 -4
  38. package/src/app/api/daemon/route.ts +10 -8
  39. package/src/app/api/documents/route.ts +11 -10
  40. package/src/app/api/external-agents/route.ts +3 -3
  41. package/src/app/api/gateways/[id]/health/route.ts +2 -3
  42. package/src/app/api/gateways/[id]/route.ts +7 -122
  43. package/src/app/api/gateways/route.ts +3 -103
  44. package/src/app/api/mcp-servers/[id]/tools/route.ts +5 -5
  45. package/src/app/api/openclaw/dashboard-url/route.ts +8 -16
  46. package/src/app/api/openclaw/directory/route.ts +2 -2
  47. package/src/app/api/openclaw/history/route.ts +3 -5
  48. package/src/app/api/providers/[id]/models/route.test.ts +60 -0
  49. package/src/app/api/providers/[id]/models/route.ts +33 -1
  50. package/src/app/api/providers/[id]/route.test.ts +49 -0
  51. package/src/app/api/providers/[id]/route.ts +30 -1
  52. package/src/app/api/providers/ollama/route.ts +6 -5
  53. package/src/app/api/schedules/[id]/route.ts +14 -108
  54. package/src/app/api/schedules/[id]/run/route.ts +6 -67
  55. package/src/app/api/schedules/route.ts +9 -51
  56. package/src/app/api/settings/route.ts +4 -3
  57. package/src/app/api/setup/check-provider/route.ts +15 -1
  58. package/src/app/api/setup/openclaw-device/route.ts +2 -2
  59. package/src/app/api/system/status/route.ts +2 -2
  60. package/src/app/api/tasks/[id]/route.ts +16 -202
  61. package/src/app/api/tasks/bulk/route.ts +5 -86
  62. package/src/app/api/tasks/metrics/route.ts +2 -1
  63. package/src/app/api/tasks/route.ts +11 -171
  64. package/src/app/api/upload/route.ts +1 -1
  65. package/src/app/api/uploads/[filename]/route.ts +1 -1
  66. package/src/app/api/uploads/route.ts +1 -1
  67. package/src/app/api/webhooks/[id]/history/route.ts +2 -2
  68. package/src/app/layout.tsx +9 -6
  69. package/src/app/protocols/page.tsx +71 -89
  70. package/src/app/tasks/page.tsx +32 -32
  71. package/src/cli/index.js +1 -0
  72. package/src/cli/spec.js +1 -0
  73. package/src/components/agents/agent-sheet.tsx +51 -25
  74. package/src/components/agents/inspector-panel.tsx +15 -4
  75. package/src/components/auth/setup-wizard/index.tsx +27 -18
  76. package/src/components/auth/setup-wizard/shared.tsx +2 -2
  77. package/src/components/auth/setup-wizard/step-agents.tsx +51 -38
  78. package/src/components/auth/setup-wizard/step-connect.tsx +48 -17
  79. package/src/components/auth/setup-wizard/types.ts +6 -4
  80. package/src/components/auth/setup-wizard/utils.test.ts +38 -8
  81. package/src/components/auth/setup-wizard/utils.ts +14 -8
  82. package/src/components/chatrooms/chatroom-sheet.tsx +16 -276
  83. package/src/components/connectors/connector-list.tsx +26 -40
  84. package/src/components/connectors/connector-sheet.tsx +95 -149
  85. package/src/components/gateways/gateway-sheet.tsx +61 -110
  86. package/src/components/layout/live-query-sync.tsx +121 -0
  87. package/src/components/protocols/structured-session-launcher.tsx +24 -45
  88. package/src/components/providers/app-query-provider.tsx +17 -0
  89. package/src/components/providers/provider-list.tsx +150 -77
  90. package/src/components/providers/provider-sheet.tsx +102 -77
  91. package/src/components/shared/model-combobox.tsx +5 -4
  92. package/src/components/skills/skill-list.tsx +5 -18
  93. package/src/components/skills/skill-sheet.tsx +21 -20
  94. package/src/components/skills/skills-workspace.tsx +48 -87
  95. package/src/components/tasks/task-card.tsx +20 -13
  96. package/src/components/tasks/task-column.tsx +22 -7
  97. package/src/components/tasks/task-list.tsx +8 -11
  98. package/src/components/tasks/task-sheet.tsx +111 -103
  99. package/src/features/agents/queries.ts +20 -0
  100. package/src/features/chatrooms/queries.ts +20 -0
  101. package/src/features/chats/queries.ts +27 -0
  102. package/src/features/connectors/queries.ts +145 -0
  103. package/src/features/credentials/queries.ts +37 -0
  104. package/src/features/extensions/queries.ts +26 -0
  105. package/src/features/external-agents/queries.ts +36 -0
  106. package/src/features/gateways/queries.ts +274 -0
  107. package/src/features/missions/queries.ts +23 -0
  108. package/src/features/projects/queries.ts +20 -0
  109. package/src/features/protocols/queries.ts +149 -0
  110. package/src/features/providers/queries.ts +142 -0
  111. package/src/features/settings/queries.ts +20 -0
  112. package/src/features/skills/queries.ts +182 -0
  113. package/src/features/tasks/queries.ts +189 -0
  114. package/src/hooks/use-ws.ts +3 -2
  115. package/src/lib/agent-provider-options.test.ts +152 -0
  116. package/src/lib/agent-provider-options.ts +84 -0
  117. package/src/lib/app/api-client.ts +2 -2
  118. package/src/lib/providers/index.test.ts +78 -0
  119. package/src/lib/providers/index.ts +13 -10
  120. package/src/lib/query/client.ts +17 -0
  121. package/src/lib/server/agents/agent-runtime-config.ts +6 -6
  122. package/src/lib/server/agents/agent-service.ts +429 -0
  123. package/src/lib/server/agents/agent-thread-session.ts +6 -5
  124. package/src/lib/server/agents/autonomy-contract.ts +1 -4
  125. package/src/lib/server/agents/delegation-advisory.test.ts +206 -0
  126. package/src/lib/server/agents/delegation-advisory.ts +251 -0
  127. package/src/lib/server/agents/main-agent-loop.ts +98 -40
  128. package/src/lib/server/agents/subagent-runtime.ts +12 -0
  129. package/src/lib/server/autonomy/supervisor-reflection.test.ts +20 -1
  130. package/src/lib/server/autonomy/supervisor-reflection.ts +39 -19
  131. package/src/lib/server/build-llm.ts +7 -15
  132. package/src/lib/server/capability-router.test.ts +70 -1
  133. package/src/lib/server/capability-router.ts +24 -99
  134. package/src/lib/server/chat-execution/chat-execution-utils.ts +0 -15
  135. package/src/lib/server/chat-execution/chat-streaming-utils.ts +2 -4
  136. package/src/lib/server/chat-execution/chat-turn-finalization.ts +77 -12
  137. package/src/lib/server/chat-execution/chat-turn-partial-persistence.ts +4 -4
  138. package/src/lib/server/chat-execution/chat-turn-preflight.ts +2 -2
  139. package/src/lib/server/chat-execution/chat-turn-preparation.ts +41 -17
  140. package/src/lib/server/chat-execution/chat-turn-stream-execution.ts +4 -2
  141. package/src/lib/server/chat-execution/chat-turn-tool-routing.test.ts +45 -0
  142. package/src/lib/server/chat-execution/chat-turn-tool-routing.ts +48 -17
  143. package/src/lib/server/chat-execution/continuation-evaluator.ts +4 -1
  144. package/src/lib/server/chat-execution/direct-memory-intent.test.ts +9 -0
  145. package/src/lib/server/chat-execution/direct-memory-intent.ts +12 -2
  146. package/src/lib/server/chat-execution/message-classifier.test.ts +35 -23
  147. package/src/lib/server/chat-execution/message-classifier.ts +74 -32
  148. package/src/lib/server/chat-execution/prompt-builder.test.ts +29 -0
  149. package/src/lib/server/chat-execution/prompt-builder.ts +37 -2
  150. package/src/lib/server/chat-execution/prompt-sections.test.ts +56 -0
  151. package/src/lib/server/chat-execution/prompt-sections.ts +193 -0
  152. package/src/lib/server/chat-execution/stream-agent-chat.ts +63 -7
  153. package/src/lib/server/chat-execution/stream-continuation.test.ts +36 -0
  154. package/src/lib/server/chat-execution/stream-continuation.ts +28 -13
  155. package/src/lib/server/chatrooms/chatroom-agent-signals.ts +26 -18
  156. package/src/lib/server/chatrooms/chatroom-helpers.ts +19 -18
  157. package/src/lib/server/chatrooms/chatroom-repository.ts +16 -0
  158. package/src/lib/server/chatrooms/chatroom-routing.test.ts +96 -0
  159. package/src/lib/server/chatrooms/chatroom-routing.ts +207 -53
  160. package/src/lib/server/chatrooms/mailbox-utils.ts +4 -2
  161. package/src/lib/server/chatrooms/session-mailbox.ts +50 -40
  162. package/src/lib/server/chats/chat-session-service.ts +410 -0
  163. package/src/lib/server/connectors/access.ts +1 -1
  164. package/src/lib/server/connectors/commands.ts +7 -6
  165. package/src/lib/server/connectors/connector-inbound.ts +14 -7
  166. package/src/lib/server/connectors/connector-outbound.ts +16 -11
  167. package/src/lib/server/connectors/connector-service.ts +453 -0
  168. package/src/lib/server/connectors/delivery.ts +17 -12
  169. package/src/lib/server/connectors/inbound-audio-transcription.ts +5 -14
  170. package/src/lib/server/connectors/media.ts +1 -1
  171. package/src/lib/server/connectors/response-media.ts +1 -1
  172. package/src/lib/server/connectors/session-consolidation.ts +11 -7
  173. package/src/lib/server/connectors/session.ts +9 -7
  174. package/src/lib/server/connectors/voice-note.ts +2 -1
  175. package/src/lib/server/context-manager.ts +20 -1
  176. package/src/lib/server/cost.ts +2 -3
  177. package/src/lib/server/credentials/credential-repository.ts +43 -4
  178. package/src/lib/server/credentials/credential-service.ts +112 -0
  179. package/src/lib/server/daemon/admin-metadata.ts +64 -0
  180. package/src/lib/server/daemon/controller.ts +577 -0
  181. package/src/lib/server/daemon/daemon-runtime.ts +352 -0
  182. package/src/lib/server/daemon/daemon-status-repository.ts +63 -0
  183. package/src/lib/server/daemon/types.ts +101 -0
  184. package/src/lib/server/embeddings.ts +3 -9
  185. package/src/lib/server/eval/agent-regression.ts +3 -2
  186. package/src/lib/server/eval/runner.ts +2 -2
  187. package/src/lib/server/execution-brief.test.ts +167 -0
  188. package/src/lib/server/execution-brief.ts +295 -0
  189. package/src/lib/server/execution-engine/chat-turn.ts +9 -0
  190. package/src/lib/server/execution-engine/import-boundary.test.ts +44 -0
  191. package/src/lib/server/execution-engine/index.ts +35 -0
  192. package/src/lib/server/execution-engine/task-attempt.ts +303 -0
  193. package/src/lib/server/execution-engine/types.ts +33 -0
  194. package/src/lib/server/gateways/gateway-profile-repository.ts +47 -3
  195. package/src/lib/server/gateways/gateway-profile-service.ts +200 -0
  196. package/src/lib/server/memory/session-archive-memory.ts +12 -10
  197. package/src/lib/server/messages/message-repository.ts +330 -0
  198. package/src/lib/server/missions/mission-service/core.ts +8 -6
  199. package/src/lib/server/openclaw/agent-resolver.ts +2 -3
  200. package/src/lib/server/openclaw/doctor.ts +1 -1
  201. package/src/lib/server/openclaw/gateway.test.ts +10 -1
  202. package/src/lib/server/openclaw/gateway.ts +5 -14
  203. package/src/lib/server/openclaw/health.ts +3 -11
  204. package/src/lib/server/openclaw/sync.ts +8 -6
  205. package/src/lib/server/persistence/storage-context.ts +3 -0
  206. package/src/lib/server/protocols/protocol-agent-turn.ts +25 -17
  207. package/src/lib/server/protocols/protocol-normalization.ts +1 -1
  208. package/src/lib/server/protocols/protocol-queries.ts +13 -7
  209. package/src/lib/server/protocols/protocol-run-lifecycle.ts +16 -20
  210. package/src/lib/server/protocols/protocol-run-repository.ts +81 -0
  211. package/src/lib/server/protocols/protocol-step-processors.ts +23 -31
  212. package/src/lib/server/protocols/protocol-swarm.ts +8 -8
  213. package/src/lib/server/protocols/protocol-template-repository.ts +42 -0
  214. package/src/lib/server/protocols/protocol-templates.ts +4 -2
  215. package/src/lib/server/protocols/protocol-types.ts +10 -7
  216. package/src/lib/server/provider-endpoint.ts +7 -12
  217. package/src/lib/server/provider-model-discovery.ts +2 -11
  218. package/src/lib/server/query-expansion.ts +5 -6
  219. package/src/lib/server/run-context.test.ts +365 -0
  220. package/src/lib/server/run-context.ts +367 -0
  221. package/src/lib/server/runtime/heartbeat-service.ts +7 -5
  222. package/src/lib/server/runtime/queue/core.ts +61 -190
  223. package/src/lib/server/runtime/run-ledger.ts +8 -0
  224. package/src/lib/server/runtime/session-run-manager/drain.ts +2 -2
  225. package/src/lib/server/runtime/session-run-manager/enqueue.ts +6 -0
  226. package/src/lib/server/runtime/session-run-manager/state.ts +4 -0
  227. package/src/lib/server/schedules/schedule-route-service.ts +230 -0
  228. package/src/lib/server/service-result.ts +16 -0
  229. package/src/lib/server/session-note.ts +2 -3
  230. package/src/lib/server/session-reset-policy.ts +4 -3
  231. package/src/lib/server/session-tools/connector.ts +9 -6
  232. package/src/lib/server/session-tools/context-mgmt.ts +58 -9
  233. package/src/lib/server/session-tools/crud.ts +162 -10
  234. package/src/lib/server/session-tools/delegate.ts +1 -1
  235. package/src/lib/server/session-tools/manage-tasks.test.ts +152 -0
  236. package/src/lib/server/session-tools/memory.ts +6 -4
  237. package/src/lib/server/session-tools/session-info.test.ts +56 -0
  238. package/src/lib/server/session-tools/session-info.ts +119 -12
  239. package/src/lib/server/session-tools/skill-runtime.ts +3 -1
  240. package/src/lib/server/session-tools/skills.ts +15 -15
  241. package/src/lib/server/session-tools/subagent.test.ts +115 -1
  242. package/src/lib/server/session-tools/subagent.ts +125 -7
  243. package/src/lib/server/session-tools/team-context.ts +4 -3
  244. package/src/lib/server/session-tools/wallet.ts +0 -58
  245. package/src/lib/server/sessions/session-lineage.ts +55 -0
  246. package/src/lib/server/sessions/session-repository.ts +2 -2
  247. package/src/lib/server/skills/learned-skills.ts +24 -23
  248. package/src/lib/server/skills/runtime-skill-resolver.ts +2 -1
  249. package/src/lib/server/skills/skill-repository.ts +136 -13
  250. package/src/lib/server/skills/skill-suggestions.ts +25 -28
  251. package/src/lib/server/storage-normalization.test.ts +42 -215
  252. package/src/lib/server/storage-normalization.ts +98 -0
  253. package/src/lib/server/storage.ts +19 -0
  254. package/src/lib/server/structured-extract.ts +3 -14
  255. package/src/lib/server/tasks/task-followups.ts +16 -11
  256. package/src/lib/server/tasks/task-result.test.ts +25 -29
  257. package/src/lib/server/tasks/task-result.ts +5 -9
  258. package/src/lib/server/tasks/task-route-service.ts +449 -0
  259. package/src/lib/server/text-normalization.ts +41 -0
  260. package/src/lib/server/tool-planning.ts +6 -42
  261. package/src/lib/server/upload-path.ts +5 -0
  262. package/src/lib/server/working-state/extraction.ts +614 -0
  263. package/src/lib/server/working-state/normalization.ts +866 -0
  264. package/src/lib/server/working-state/prompt.ts +60 -0
  265. package/src/lib/server/working-state/repository.ts +38 -0
  266. package/src/lib/server/working-state/service.test.ts +253 -0
  267. package/src/lib/server/working-state/service.ts +293 -0
  268. package/src/lib/validation/schemas.ts +1 -0
  269. package/src/lib/ws-client.ts +3 -3
  270. package/src/stores/slices/task-slice.ts +1 -4
  271. package/src/stores/use-chatroom-store.ts +2 -2
  272. package/src/types/index.ts +288 -22
  273. package/src/views/settings/section-providers.tsx +2 -2
@@ -0,0 +1,29 @@
1
+ import assert from 'node:assert/strict'
2
+ import { describe, it } from 'node:test'
3
+
4
+ import { buildAgenticExecutionPolicy } from '@/lib/server/chat-execution/prompt-builder'
5
+
6
+ describe('buildAgenticExecutionPolicy', () => {
7
+ it('adds a routing matrix that teaches session introspection, durable tracking, and direct routing', () => {
8
+ const prompt = buildAgenticExecutionPolicy({
9
+ enabledExtensions: ['memory', 'manage_sessions', 'manage_tasks', 'manage_skills', 'spawn_subagent'],
10
+ loopMode: 'bounded',
11
+ heartbeatPrompt: 'HEARTBEAT',
12
+ heartbeatIntervalSec: 120,
13
+ userMessage: 'Figure out what tools you have, then continue the task.',
14
+ history: [],
15
+ mode: 'minimal',
16
+ })
17
+
18
+ assert.ok(prompt.includes('## Routing Matrix'))
19
+ assert.ok(prompt.includes('Current-thread facts already visible in this chat'))
20
+ assert.ok(prompt.includes('`memory_search`'))
21
+ assert.ok(prompt.includes('`sessions_tool` action `identity`'))
22
+ assert.ok(prompt.includes('`sessions_tool` action `history`'))
23
+ assert.ok(prompt.includes('`manage_tasks`'))
24
+ assert.ok(prompt.includes('`manage_skills`'))
25
+ assert.ok(prompt.includes('delegate or spawn a subagent'))
26
+ assert.ok(prompt.includes('use the concrete tool now'))
27
+ assert.ok(prompt.includes('prefer the direct `manage_*` tool'))
28
+ })
29
+ })
@@ -202,10 +202,11 @@ export function shouldForceAttachmentFollowthrough(params: {
202
202
  enabledExtensions: string[]
203
203
  hasToolCalls: boolean
204
204
  hasAttachmentContext: boolean
205
+ classification?: MessageClassification | null
205
206
  }): boolean {
206
207
  if (!params.hasAttachmentContext) return false
207
208
  if (params.hasToolCalls) return false
208
- const decision = routeTaskIntent(params.userMessage, params.enabledExtensions, null)
209
+ const decision = routeTaskIntent(params.userMessage, params.enabledExtensions, null, params.classification ?? null)
209
210
  if (decision.intent !== 'research' && decision.intent !== 'browsing') return false
210
211
  return decision.preferredTools.some((toolName) => extensionIdMatches(params.enabledExtensions, toolName))
211
212
  }
@@ -326,6 +327,13 @@ export function buildAgenticExecutionPolicy(opts: {
326
327
  const extensionLines = isMinimal ? [] : buildExtensionCapabilityLines(opts.enabledExtensions, { delegationEnabled: opts.delegationEnabled, agentId: opts.agentId })
327
328
  const toolDisciplineLines = buildToolSection(opts.enabledExtensions)
328
329
  const hasMemoryTools = opts.enabledExtensions.some((toolId) => (canonicalizeExtensionId(toolId) || toolId) === 'memory')
330
+ const hasManageSessions = opts.enabledExtensions.some((toolId) => (canonicalizeExtensionId(toolId) || toolId) === 'manage_sessions')
331
+ const hasManageTasks = opts.enabledExtensions.some((toolId) => (canonicalizeExtensionId(toolId) || toolId) === 'manage_tasks')
332
+ const hasManageSkills = opts.enabledExtensions.some((toolId) => (canonicalizeExtensionId(toolId) || toolId) === 'manage_skills')
333
+ const hasDelegationTools = opts.enabledExtensions.some((toolId) => {
334
+ const canonical = canonicalizeExtensionId(toolId) || toolId
335
+ return canonical === 'delegate' || canonical === 'spawn_subagent'
336
+ })
329
337
 
330
338
  const parts: string[] = []
331
339
 
@@ -351,6 +359,33 @@ export function buildAgenticExecutionPolicy(opts: {
351
359
  : 'Loop: BOUNDED — execute multiple steps but finish within recursion budget.',
352
360
  )
353
361
 
362
+ if (hasTooling) {
363
+ parts.push(
364
+ '## Routing Matrix',
365
+ 'Current-thread facts already visible in this chat: answer directly from the thread before using tools.',
366
+ hasMemoryTools
367
+ ? 'Facts from previous conversations: start with `memory_search`, then `memory_get` only for a targeted follow-up read.'
368
+ : 'Facts from previous conversations: rely on the visible thread only and state when memory tools are unavailable.',
369
+ hasManageSessions
370
+ ? 'Harness/session context, lineage, project attachment, or enabled-tool questions: use `sessions_tool` action `identity`.'
371
+ : 'Harness/session introspection is limited here; rely on the runtime orientation block and visible context.',
372
+ hasManageSessions
373
+ ? 'Earlier messages from this same session that are not already visible in the thread: use `sessions_tool` action `history`.'
374
+ : 'Do not claim hidden session history is checked when `sessions_tool` is unavailable.',
375
+ hasManageTasks
376
+ ? 'Durable backlog or resumable progress tracking: use `manage_tasks` for multi-turn work, delegation, or explicit task-board requests.'
377
+ : 'Do not create pseudo-task workflows in prose when task tooling is unavailable.',
378
+ hasManageSkills
379
+ ? 'Missing capability, workflow, or environment setup blocker: use `manage_skills` before repeating generic exploration.'
380
+ : 'If a capability is genuinely missing, say so plainly instead of pretending a skill install happened.',
381
+ hasDelegationTools
382
+ ? 'Multi-step specialist work: delegate or spawn a subagent instead of doing the whole chain yourself.'
383
+ : 'If delegation tools are unavailable, execute directly with the tools you do have.',
384
+ 'For direct reversible execution, use the concrete tool now instead of creating a task or stopping at advice.',
385
+ 'When both `manage_platform` and a direct `manage_*` tool are available, prefer the direct `manage_*` tool.',
386
+ )
387
+ }
388
+
354
389
  // Sections skipped in minimal mode
355
390
  if (!isMinimal) {
356
391
  if (hasMemoryTools) {
@@ -374,7 +409,7 @@ export function buildAgenticExecutionPolicy(opts: {
374
409
  'Prefer `use_skill` action `run` for executable skills and `use_skill` action `load` only when the skill is guidance-only.',
375
410
  )
376
411
  }
377
- if (opts.enabledExtensions.some((toolId) => (canonicalizeExtensionId(toolId) || toolId) === 'manage_skills')) {
412
+ if (hasManageSkills) {
378
413
  parts.push(
379
414
  '## Skill Resolution',
380
415
  'When you are blocked on a missing capability, binary, or environment setup, call `manage_skills` before repeating generic exploration.',
@@ -1,5 +1,8 @@
1
1
  import assert from 'node:assert/strict'
2
2
  import { after, before, describe, it } from 'node:test'
3
+ import fs from 'node:fs'
4
+ import os from 'node:os'
5
+ import path from 'node:path'
3
6
 
4
7
  let mod: typeof import('@/lib/server/chat-execution/prompt-sections')
5
8
 
@@ -86,6 +89,59 @@ describe('prompt-sections', () => {
86
89
  })
87
90
  })
88
91
 
92
+ describe('buildRuntimeOrientationSection', () => {
93
+ it('includes delegated lineage, workspace markers, project context, and routing guidance', () => {
94
+ const cwd = fs.mkdtempSync(path.join(os.tmpdir(), 'swarmclaw-runtime-orientation-'))
95
+ try {
96
+ fs.writeFileSync(path.join(cwd, 'AGENTS.md'), '# Agent notes')
97
+ const result = mod.buildRuntimeOrientationSection({
98
+ session: {
99
+ id: 'child-session',
100
+ cwd,
101
+ provider: 'openai',
102
+ model: 'gpt-5',
103
+ parentSessionId: 'parent-session',
104
+ agentId: 'agent-1',
105
+ } as never,
106
+ promptMode: 'minimal',
107
+ sessionExtensions: ['files', 'manage_sessions', 'codex_cli'],
108
+ toolPolicy: {
109
+ mode: 'balanced',
110
+ requestedExtensions: ['files', 'manage_sessions', 'codex_cli', 'manage_secrets'],
111
+ enabledExtensions: ['files', 'manage_sessions', 'codex_cli'],
112
+ blockedExtensions: [{ tool: 'manage_secrets', reason: 'blocked by policy', source: 'policy' }],
113
+ },
114
+ agent: {
115
+ id: 'agent-1',
116
+ name: 'Builder',
117
+ delegationTargetMode: 'selected',
118
+ delegationTargetAgentIds: ['qa-1', 'ops-1'],
119
+ } as never,
120
+ activeProjectContext: {
121
+ projectId: 'project-1',
122
+ project: { name: 'Northstar' },
123
+ projectRoot: '/workspace/projects/project-1',
124
+ } as never,
125
+ rootSessionId: 'root-session',
126
+ })
127
+
128
+ assert.ok(result.includes('## Runtime Orientation'))
129
+ assert.ok(result.includes('delegated_child'))
130
+ assert.ok(result.includes('prompt=minimal'))
131
+ assert.ok(result.includes('root=root-session'))
132
+ assert.ok(result.includes('Workspace markers: AGENTS.md'))
133
+ assert.ok(result.includes('Active project: Northstar'))
134
+ assert.ok(result.includes('`manage_sessions`'))
135
+ assert.ok(result.includes('`codex_cli`'))
136
+ assert.ok(result.includes('Policy blocked:'))
137
+ assert.ok(result.includes('sessions_tool'))
138
+ assert.ok(result.includes('use `manage_platform` only as fallback'))
139
+ } finally {
140
+ fs.rmSync(cwd, { recursive: true, force: true })
141
+ }
142
+ })
143
+ })
144
+
89
145
  // ---- buildProjectSection ----
90
146
  describe('buildProjectSection', () => {
91
147
  it('returns null for minimal mode', () => {
@@ -6,13 +6,24 @@
6
6
  * The main prompt assembly in stream-agent-chat.ts composes these declaratively.
7
7
  */
8
8
 
9
+ import fs from 'node:fs'
10
+ import path from 'node:path'
9
11
  import type { Session, Agent } from '@/types'
12
+ import type { PromptMode } from '@/lib/server/chat-execution/prompt-mode'
13
+ import type { MessageClassification } from '@/lib/server/chat-execution/message-classifier'
10
14
  import type { ActiveProjectContext } from '@/lib/server/project-context'
15
+ import type { SessionToolPolicyDecision } from '@/lib/server/tool-capability-policy'
11
16
  import { buildIdentityContinuityContext } from '@/lib/server/identity-continuity'
17
+ import {
18
+ buildDelegationTaskProfile,
19
+ formatDelegationRationale,
20
+ resolveDelegationAdvisory,
21
+ } from '@/lib/server/agents/delegation-advisory'
12
22
  import { getAgent, listAgents } from '@/lib/server/agents/agent-repository'
13
23
  import { loadSkills } from '@/lib/server/skills/skill-repository'
14
24
  import { buildRuntimeSkillPromptBlocks, resolveRuntimeSkills } from '@/lib/server/skills/runtime-skill-resolver'
15
25
  import { resolveTeam } from '@/lib/server/agents/team-resolution'
26
+ import { canonicalizeExtensionId } from '@/lib/server/tool-aliases'
16
27
 
17
28
  // ---------------------------------------------------------------------------
18
29
  // Identity: agent name, description, continuity, soul, systemPrompt, skills
@@ -89,6 +100,148 @@ export function buildThinkingSection(
89
100
  return text ? `## Reasoning Depth\n${text}` : null
90
101
  }
91
102
 
103
+ // ---------------------------------------------------------------------------
104
+ // Runtime Orientation
105
+ // ---------------------------------------------------------------------------
106
+
107
+ const WORKSPACE_MARKER_FILES = [
108
+ 'AGENTS.md',
109
+ 'BOOTSTRAP.md',
110
+ 'HEARTBEAT.md',
111
+ 'IDENTITY.md',
112
+ 'TOOLS.md',
113
+ 'USER.md',
114
+ ]
115
+
116
+ function normalizeRuntimeExtensionId(extensionId: string): string {
117
+ const normalized = extensionId.trim().toLowerCase()
118
+ if (!normalized) return ''
119
+ if (normalized === 'delegate_to_claude_code' || normalized === 'claude_code') return 'claude_code'
120
+ if (normalized === 'delegate_to_codex_cli' || normalized === 'codex_cli') return 'codex_cli'
121
+ if (normalized === 'delegate_to_opencode_cli' || normalized === 'opencode_cli') return 'opencode_cli'
122
+ if (normalized === 'delegate_to_gemini_cli' || normalized === 'gemini_cli') return 'gemini_cli'
123
+ if (['session_info', 'sessions_tool', 'whoami_tool', 'search_history_tool'].includes(normalized)) return 'manage_sessions'
124
+ return canonicalizeExtensionId(normalized)
125
+ }
126
+
127
+ function canonicalizeEnabledExtensions(enabledExtensions: string[]): string[] {
128
+ const seen = new Set<string>()
129
+ const values: string[] = []
130
+ for (const extensionId of enabledExtensions) {
131
+ const normalized = normalizeRuntimeExtensionId(extensionId)
132
+ if (!normalized || seen.has(normalized)) continue
133
+ seen.add(normalized)
134
+ values.push(normalized)
135
+ }
136
+ return values
137
+ }
138
+
139
+ function formatInlineCodeList(values: string[], maxItems = 8): string {
140
+ if (values.length === 0) return '(none)'
141
+ const head = values.slice(0, maxItems).map((value) => `\`${value}\``)
142
+ if (values.length > maxItems) head.push(`... +${values.length - maxItems} more`)
143
+ return head.join(', ')
144
+ }
145
+
146
+ function formatPolicyBlocks(blocked: SessionToolPolicyDecision['blockedExtensions'], maxItems = 3): string {
147
+ const lines = blocked
148
+ .slice(0, maxItems)
149
+ .map((entry) => `\`${canonicalizeExtensionId(entry.tool)}\` (${entry.reason})`)
150
+ if (blocked.length > maxItems) lines.push(`... +${blocked.length - maxItems} more`)
151
+ return lines.join(', ')
152
+ }
153
+
154
+ function collectWorkspaceMarkers(cwd: string | null | undefined): string[] {
155
+ if (typeof cwd !== 'string' || !cwd.trim()) return []
156
+ return WORKSPACE_MARKER_FILES.filter((filename) => {
157
+ try {
158
+ return fs.existsSync(path.join(cwd, filename))
159
+ } catch {
160
+ return false
161
+ }
162
+ })
163
+ }
164
+
165
+ function resolveRuntimeSessionKind(params: {
166
+ session: Session
167
+ isHeartbeat?: boolean
168
+ isConnectorSession?: boolean
169
+ }): string {
170
+ if (params.isHeartbeat) return 'heartbeat'
171
+ if (params.isConnectorSession) return 'connector'
172
+ if (params.session.parentSessionId) return 'delegated_child'
173
+ return 'root_chat'
174
+ }
175
+
176
+ export function buildRuntimeOrientationSection(params: {
177
+ session: Session
178
+ promptMode: PromptMode
179
+ sessionExtensions: string[]
180
+ toolPolicy?: SessionToolPolicyDecision | null
181
+ agent?: Agent | null
182
+ activeProjectContext?: ActiveProjectContext | null
183
+ rootSessionId?: string | null
184
+ isHeartbeat?: boolean
185
+ isConnectorSession?: boolean
186
+ }): string {
187
+ const { session, agent } = params
188
+ const activeProjectContext = params.activeProjectContext || null
189
+ const enabledExtensions = canonicalizeEnabledExtensions(params.sessionExtensions)
190
+ const workspaceMarkers = collectWorkspaceMarkers(session.cwd)
191
+ const delegationEnabled = enabledExtensions.some((extensionId) =>
192
+ ['delegate', 'spawn_subagent', 'claude_code', 'codex_cli', 'opencode_cli', 'gemini_cli'].includes(extensionId),
193
+ )
194
+
195
+ const lines = [
196
+ '## Runtime Orientation',
197
+ `Session: ${resolveRuntimeSessionKind({ session, isHeartbeat: params.isHeartbeat, isConnectorSession: params.isConnectorSession })} | prompt=${params.promptMode} | id=${session.id}`,
198
+ session.parentSessionId || params.rootSessionId
199
+ ? `Lineage: parent=${session.parentSessionId || '(none)'} | root=${params.rootSessionId || session.id}`
200
+ : `Lineage: root=${session.id}`,
201
+ `Agent: ${agent?.name || session.agentId || 'Unassigned'}${session.agentId ? ` [id: ${session.agentId}]` : ''}`,
202
+ `Provider/model: ${session.provider} / ${session.model || '(default)'}`,
203
+ `CWD: ${session.cwd || '(none)'}`,
204
+ ]
205
+
206
+ if (workspaceMarkers.length > 0) {
207
+ lines.push(`Workspace markers: ${workspaceMarkers.join(', ')}`)
208
+ }
209
+
210
+ if (activeProjectContext?.projectId) {
211
+ const projectLabel = activeProjectContext.project?.name || activeProjectContext.projectId
212
+ const projectRoot = activeProjectContext.projectRoot ? ` | root=${activeProjectContext.projectRoot}` : ''
213
+ lines.push(`Active project: ${projectLabel}${projectRoot}`)
214
+ }
215
+
216
+ lines.push(`Enabled capabilities now: ${formatInlineCodeList(enabledExtensions)}`)
217
+
218
+ if (params.toolPolicy?.blockedExtensions?.length) {
219
+ lines.push(`Policy blocked: ${formatPolicyBlocks(params.toolPolicy.blockedExtensions)}`)
220
+ }
221
+
222
+ if (delegationEnabled) {
223
+ const delegateMode = agent?.delegationTargetMode === 'selected' ? 'selected' : 'all'
224
+ const targetIds = Array.isArray(agent?.delegationTargetAgentIds)
225
+ ? agent!.delegationTargetAgentIds.filter((value) => typeof value === 'string' && value.trim())
226
+ : []
227
+ lines.push(
228
+ delegateMode === 'selected'
229
+ ? `Delegation: enabled (${delegateMode}) | targets=${targetIds.length ? targetIds.join(', ') : '(none configured)'}`
230
+ : 'Delegation: enabled (all allowed targets)',
231
+ )
232
+ } else {
233
+ lines.push('Delegation: disabled in this runtime')
234
+ }
235
+
236
+ if (enabledExtensions.includes('manage_sessions')) {
237
+ lines.push('Harness inspection: use `sessions_tool` action `identity` for live session/platform context; use action `history` only when you need earlier messages from this same session.')
238
+ }
239
+
240
+ lines.push('Platform routing: prefer direct `manage_*` tools when enabled; use `manage_platform` only as fallback.')
241
+
242
+ return lines.join('\n')
243
+ }
244
+
92
245
  // ---------------------------------------------------------------------------
93
246
  // Workspace Context (async — dynamic import)
94
247
  // ---------------------------------------------------------------------------
@@ -414,6 +567,43 @@ export function buildCoordinatorSection(
414
567
  return lines.join('\n')
415
568
  }
416
569
 
570
+ export function buildDelegationRecommendationSection(params: {
571
+ agent: Agent | null | undefined
572
+ classification?: MessageClassification | null
573
+ }): string | null {
574
+ const agent = params.agent
575
+ if (!agent || agent.delegationEnabled !== true) return null
576
+ const profile = buildDelegationTaskProfile({
577
+ classification: params.classification,
578
+ })
579
+ if (!profile.substantial) return null
580
+
581
+ const advisory = resolveDelegationAdvisory({
582
+ currentAgent: agent,
583
+ agents: listAgents(),
584
+ profile,
585
+ delegationTargetMode: agent.delegationTargetMode === 'selected' ? 'selected' : 'all',
586
+ delegationTargetAgentIds: agent.delegationTargetAgentIds || [],
587
+ })
588
+ if (!advisory.shouldDelegate || !advisory.recommended) return null
589
+
590
+ const recommendation = advisory.recommended
591
+ const workLabel = profile.workType === 'general' ? 'substantial work' : `${profile.workType} work`
592
+ const recommendationLabel = advisory.style === 'managerial'
593
+ ? 'Managerial Delegation Recommendation'
594
+ : 'Delegation Recommendation'
595
+
596
+ return [
597
+ `## ${recommendationLabel}`,
598
+ `This request looks like ${workLabel}.`,
599
+ `Best-fit teammate: **${recommendation.agentName}** [id: ${recommendation.agentId}]`,
600
+ `Why: ${formatDelegationRationale(recommendation)}.`,
601
+ advisory.style === 'managerial'
602
+ ? 'Prefer delegating the execution and keep your direct work to reconnaissance, validation, and synthesis.'
603
+ : 'Prefer delegating the execution and reserve direct tool use for quick lookups or validation.',
604
+ ].join('\n')
605
+ }
606
+
417
607
  // ---------------------------------------------------------------------------
418
608
  // Credential Awareness
419
609
  // ---------------------------------------------------------------------------
@@ -527,3 +717,6 @@ export function buildCliDelegationContext(opts: {
527
717
 
528
718
  return parts.join('\n')
529
719
  }
720
+
721
+ // Re-export RunContext prompt builder from its canonical home
722
+ export { buildRunContextSection } from '@/lib/server/run-context'
@@ -3,7 +3,7 @@ import { HumanMessage, AIMessage } from '@langchain/core/messages'
3
3
  import { createReactAgent } from '@langchain/langgraph/prebuilt'
4
4
  import { MemorySaver } from '@langchain/langgraph'
5
5
  import { DEFAULT_HEARTBEAT_INTERVAL_SEC } from '@/lib/runtime/heartbeat-defaults'
6
- import { getAgent } from '@/lib/server/agents/agent-repository'
6
+ import { getAgent, listAgents } from '@/lib/server/agents/agent-repository'
7
7
  import { buildSessionTools } from '@/lib/server/session-tools'
8
8
  import { buildChatModel } from '@/lib/server/build-llm'
9
9
  import { loadSettings } from '@/lib/server/settings/settings-repository'
@@ -18,6 +18,7 @@ import { truncateToolResultText } from '@/lib/server/chat-execution/tool-result-
18
18
  import {
19
19
  buildIdentitySection,
20
20
  buildThinkingSection,
21
+ buildRuntimeOrientationSection,
21
22
  buildWorkspaceSection,
22
23
  buildAgentAwarenessSection,
23
24
  buildSituationalSection,
@@ -27,13 +28,15 @@ import {
27
28
  buildProactiveMemorySection,
28
29
  buildCoordinatorSection,
29
30
  buildCredentialAwarenessSection,
31
+ buildDelegationRecommendationSection,
32
+ buildRunContextSection,
30
33
  } from '@/lib/server/chat-execution/prompt-sections'
31
34
 
32
35
  import { log } from '@/lib/server/logger'
33
36
  import { logExecution } from '@/lib/server/execution-log'
34
37
  import { buildCurrentDateTimePromptContext } from '@/lib/server/prompt-runtime-context'
35
38
  import { expandExtensionIds } from '@/lib/server/tool-aliases'
36
- import type { Session, Message } from '@/types'
39
+ import type { ExecutionBrief, Session, Message } from '@/types'
37
40
  import { getEnabledCapabilityIds } from '@/lib/capability-selection'
38
41
  import { enqueueSystemEvent } from '@/lib/server/runtime/system-events'
39
42
  import { resolveActiveProjectContext } from '@/lib/server/project-context'
@@ -89,6 +92,7 @@ import {
89
92
  DEFAULT_PROMPT_BUDGET,
90
93
  MINIMAL_PROMPT_BUDGET,
91
94
  } from '@/lib/server/chat-execution/prompt-budget'
95
+ import { resolveSessionLineageIds } from '@/lib/server/sessions/session-lineage'
92
96
  import { IterationTimers } from '@/lib/server/chat-execution/iteration-timers'
93
97
  import { processIterationEvents } from '@/lib/server/chat-execution/iteration-event-handler'
94
98
  import { evaluateContinuation } from '@/lib/server/chat-execution/continuation-evaluator'
@@ -101,6 +105,11 @@ import {
101
105
  isResearchSynthesis as classifiedIsResearchSynthesis,
102
106
  type MessageClassification,
103
107
  } from '@/lib/server/chat-execution/message-classifier'
108
+ import {
109
+ buildDelegationTaskProfile,
110
+ formatDelegationRationale,
111
+ resolveDelegationAdvisory,
112
+ } from '@/lib/server/agents/delegation-advisory'
104
113
 
105
114
  const TAG = 'stream-agent-chat'
106
115
 
@@ -174,6 +183,7 @@ interface StreamAgentChatOpts {
174
183
  attachedFiles?: string[]
175
184
  apiKey: string | null
176
185
  systemPrompt?: string
186
+ executionBrief?: ExecutionBrief | null
177
187
  extraSystemContext?: string[]
178
188
  write: (data: string) => void
179
189
  history: Message[]
@@ -213,7 +223,7 @@ export async function streamAgentChat(opts: StreamAgentChatOpts): Promise<Stream
213
223
 
214
224
  async function streamAgentChatCore(opts: StreamAgentChatOpts): Promise<StreamAgentChatResult> {
215
225
  const startTs = Date.now()
216
- const { session, message, imagePath, imageUrl, attachedFiles, apiKey, systemPrompt, extraSystemContext, write, history, fallbackCredentialIds, signal } = opts
226
+ const { session, message, imagePath, imageUrl, attachedFiles, apiKey, systemPrompt, executionBrief, extraSystemContext, write, history, fallbackCredentialIds, signal } = opts
217
227
  const isHeartbeat = isHeartbeatSource(opts.source)
218
228
  const promptMode: PromptMode = opts.promptMode ?? resolvePromptMode(session)
219
229
  const isMinimalPrompt = promptMode === 'minimal'
@@ -221,7 +231,7 @@ async function streamAgentChatCore(opts: StreamAgentChatOpts): Promise<StreamAge
221
231
  const rawExtensions = getEnabledCapabilityIds(session)
222
232
  const hasShellCapability = rawExtensions.some((toolId) => ['shell', 'execute_command'].includes(String(toolId)))
223
233
  const extensionManager = getExtensionManager()
224
- const sessionExtensions = expandExtensionIds([
234
+ const requestedExtensions = expandExtensionIds([
225
235
  ...rawExtensions,
226
236
  ...(hasShellCapability ? ['process'] : []),
227
237
  ]).filter((id) => !extensionManager.isExplicitlyDisabled(id))
@@ -249,10 +259,15 @@ async function streamAgentChatCore(opts: StreamAgentChatOpts): Promise<StreamAge
249
259
 
250
260
  // Build agent prompt
251
261
  const settings = loadSettings()
262
+ const toolPolicy = resolveSessionToolPolicy(requestedExtensions, settings)
263
+ const blockedExtensionIds = new Set(expandExtensionIds(toolPolicy.blockedExtensions.map((entry) => entry.tool)))
264
+ const sessionExtensions = expandExtensionIds(toolPolicy.enabledExtensions)
265
+ .filter((id) => !blockedExtensionIds.has(id))
266
+ .filter((id) => !extensionManager.isExplicitlyDisabled(id))
252
267
  const requestedToolPreflightResponse = resolveRequestedToolPreflightResponse({
253
268
  message,
254
269
  enabledExtensions: sessionExtensions,
255
- toolPolicy: resolveSessionToolPolicy(sessionExtensions, settings),
270
+ toolPolicy,
256
271
  appSettings: settings,
257
272
  internal: false,
258
273
  source: 'chat',
@@ -296,6 +311,7 @@ async function streamAgentChatCore(opts: StreamAgentChatOpts): Promise<StreamAge
296
311
  // -------------------------------------------------------------------------
297
312
  const promptParts: string[] = []
298
313
  const hasProvidedSystemPrompt = typeof systemPrompt === 'string' && systemPrompt.trim().length > 0
314
+ const hasCanonicalExecutionBrief = Boolean(executionBrief)
299
315
  const currentThreadRecallRequest = isCurrentThreadRecallRequest(message)
300
316
  const hasAttachmentContext = Boolean(
301
317
  imagePath
@@ -347,18 +363,37 @@ async function streamAgentChatCore(opts: StreamAgentChatOpts): Promise<StreamAge
347
363
  // Composable prompt sections — each builder returns string | null (or string[])
348
364
  const thinkingBlock = buildThinkingSection(agentThinkingLevel, isMinimalPrompt)
349
365
  if (thinkingBlock) promptParts.push(thinkingBlock)
366
+ const { rootSessionId } = resolveSessionLineageIds(session)
350
367
 
351
368
  // Async sections — run concurrently where possible
352
- const [workspaceBlock, awarenessBlock, situationalBlock, extensionAuditBlock] = await Promise.all([
369
+ const [runtimeOrientationBlock, workspaceBlock, awarenessBlock, situationalBlock, extensionAuditBlock] = await Promise.all([
370
+ buildRuntimeOrientationSection({
371
+ session,
372
+ promptMode,
373
+ sessionExtensions,
374
+ toolPolicy,
375
+ agent: sessionAgent,
376
+ activeProjectContext,
377
+ rootSessionId,
378
+ isHeartbeat,
379
+ isConnectorSession,
380
+ }),
353
381
  !hasProvidedSystemPrompt ? buildWorkspaceSection(session, isMinimalPrompt, agentHeartbeatEnabled) : null,
354
382
  buildAgentAwarenessSection(session, sessionExtensions, isMinimalPrompt),
355
383
  buildSituationalSection(session, isMinimalPrompt),
356
384
  buildExtensionAccessAuditSection(sessionExtensions, agentMcpDisabledTools, isMinimalPrompt),
357
385
  ])
386
+ if (runtimeOrientationBlock) promptParts.push(runtimeOrientationBlock)
358
387
  if (workspaceBlock) promptParts.push(workspaceBlock)
359
388
  if (awarenessBlock) promptParts.push(awarenessBlock)
360
389
  if (situationalBlock) promptParts.push(situationalBlock)
361
390
 
391
+ // RunContext — structured working memory that survives compaction
392
+ if (!hasProvidedSystemPrompt && !hasCanonicalExecutionBrief) {
393
+ const runContextBlock = buildRunContextSection(session.runContext, isMinimalPrompt)
394
+ if (runContextBlock) promptParts.push(runContextBlock)
395
+ }
396
+
362
397
  // Extra system context — always included (caller-provided context is always relevant)
363
398
  if (Array.isArray(extraSystemContext)) {
364
399
  for (const block of extraSystemContext) {
@@ -393,6 +428,22 @@ async function streamAgentChatCore(opts: StreamAgentChatOpts): Promise<StreamAge
393
428
 
394
429
  // Await classification before building the agentic execution policy
395
430
  const classification = await classificationPromise
431
+ const delegationAdvisory = sessionAgent && agentDelegationEnabled
432
+ ? resolveDelegationAdvisory({
433
+ currentAgent: sessionAgent,
434
+ agents: listAgents(),
435
+ profile: buildDelegationTaskProfile({ classification }),
436
+ delegationTargetMode: agentDelegationTargetMode,
437
+ delegationTargetAgentIds: agentDelegationTargetAgentIds,
438
+ })
439
+ : null
440
+ const delegationRecommendationBlock = !isMinimalPrompt
441
+ ? buildDelegationRecommendationSection({
442
+ agent: sessionAgent,
443
+ classification,
444
+ })
445
+ : null
446
+ if (delegationRecommendationBlock) promptParts.push(delegationRecommendationBlock)
396
447
 
397
448
  promptParts.push(
398
449
  buildAgenticExecutionPolicy({
@@ -813,7 +864,7 @@ async function streamAgentChatCore(opts: StreamAgentChatOpts): Promise<StreamAge
813
864
  // -------------------------------------------------------------------------
814
865
  const state = new ChatTurnState()
815
866
  const limits = new ContinuationLimits(isConnectorSession, isHeartbeat)
816
- const routingDecision = routeTaskIntent(message, sessionExtensions, null)
867
+ const routingDecision = routeTaskIntent(message, sessionExtensions, null, classification)
817
868
  const explicitRequiredToolNames = getExplicitRequiredToolNames(message, sessionExtensions)
818
869
 
819
870
  const boundedExternalExecutionTask = classifiedHasTransactionalWalletIntent(classification, message)
@@ -1071,6 +1122,8 @@ async function streamAgentChatCore(opts: StreamAgentChatOpts): Promise<StreamAge
1071
1122
  likelyResearchSynthesisTask,
1072
1123
  abortControllerAborted: abortController.signal.aborted,
1073
1124
  classification,
1125
+ delegationEnabled: agentDelegationEnabled,
1126
+ delegationPreferenceActive: delegationAdvisory?.shouldDelegate === true,
1074
1127
  })
1075
1128
  shouldContinue = decision.type
1076
1129
  if (decision.requiredToolReminderNames.length > 0) {
@@ -1123,6 +1176,9 @@ async function streamAgentChatCore(opts: StreamAgentChatOpts): Promise<StreamAge
1123
1176
  cwd: session.cwd,
1124
1177
  frequencyLimitedToolName,
1125
1178
  sessionExtensions,
1179
+ isCoordinatorAgent,
1180
+ recommendedDelegateName: delegationAdvisory?.recommended?.agentName || null,
1181
+ delegationRationale: formatDelegationRationale(delegationAdvisory?.recommended),
1126
1182
  })
1127
1183
 
1128
1184
  if (continuationPrompt) {
@@ -290,5 +290,41 @@ describe('stream-continuation', () => {
290
290
  assert.equal(typeof prompt, 'string')
291
291
  assert.ok(prompt!.includes('memory write'))
292
292
  })
293
+
294
+ it('renders a coordinator delegation nudge with the recommended delegate', () => {
295
+ const prompt = mod.buildContinuationPrompt({
296
+ type: 'coordinator_delegation_nudge',
297
+ message: 'test',
298
+ fullText: '',
299
+ toolEvents: [],
300
+ requiredToolReminderNames: [],
301
+ isCoordinatorAgent: true,
302
+ recommendedDelegateName: 'Builder',
303
+ delegationRationale: 'capability match: coding; worker role fits execution-heavy work',
304
+ })
305
+
306
+ assert.ok(prompt)
307
+ assert.ok(prompt!.includes('Builder'))
308
+ assert.ok(prompt!.includes('orchestrate'))
309
+ assert.ok(prompt!.includes('Reason: capability match: coding; worker role fits execution-heavy work.'))
310
+ })
311
+
312
+ it('renders an advisory delegation nudge for non-coordinator agents', () => {
313
+ const prompt = mod.buildContinuationPrompt({
314
+ type: 'coordinator_delegation_nudge',
315
+ message: 'test',
316
+ fullText: '',
317
+ toolEvents: [],
318
+ requiredToolReminderNames: [],
319
+ isCoordinatorAgent: false,
320
+ recommendedDelegateName: 'Reviewer',
321
+ delegationRationale: 'capability match: review; currently idle',
322
+ })
323
+
324
+ assert.ok(prompt)
325
+ assert.ok(prompt!.includes('materially better fit'))
326
+ assert.ok(prompt!.includes('Reviewer'))
327
+ assert.ok(prompt!.includes('reconnaissance, validation, or synthesis'))
328
+ })
293
329
  })
294
330
  })