@exellix/ai-tasks 7.8.0

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 (524) hide show
  1. package/.env.example +3 -0
  2. package/.metadata/instructions-builders/create-expected-output-instructions.md +195 -0
  3. package/.metadata/readme.md +48 -0
  4. package/.metadata/shared/audit.md +29 -0
  5. package/.metadata/skills/professional-answer +32 -0
  6. package/.metadata/skills/professional-answer.compressed.md +11 -0
  7. package/.metadata/skills/professional-answer.instructions +33 -0
  8. package/.metadata/skills/professional-answer.instructions.md +33 -0
  9. package/.metadata/skills/professional-answer.l2.md +28 -0
  10. package/.metadata/skills/professional-answer.md +32 -0
  11. package/.metadata/skills/professional-answer.prompt +1 -0
  12. package/.metadata/skills/professional-answer.prompt.md +1 -0
  13. package/.metadata/skills/professional-decision.md +26 -0
  14. package/BREAKING-CHANGES.md +43 -0
  15. package/CHANGELOG.md +147 -0
  16. package/README.md +2497 -0
  17. package/RUNTASK_REQUEST.md +369 -0
  18. package/dist/activix/activixClient.d.ts +6 -0
  19. package/dist/activix/activixClient.d.ts.map +1 -0
  20. package/dist/activix/activixClient.js +131 -0
  21. package/dist/activix/activixClient.js.map +1 -0
  22. package/dist/activix/getTaskActivities.d.ts +10 -0
  23. package/dist/activix/getTaskActivities.d.ts.map +1 -0
  24. package/dist/activix/getTaskActivities.js +17 -0
  25. package/dist/activix/getTaskActivities.js.map +1 -0
  26. package/dist/activix/phaseTracking.d.ts +17 -0
  27. package/dist/activix/phaseTracking.d.ts.map +1 -0
  28. package/dist/activix/phaseTracking.js +85 -0
  29. package/dist/activix/phaseTracking.js.map +1 -0
  30. package/dist/aiScoping/index.d.ts +6 -0
  31. package/dist/aiScoping/index.d.ts.map +1 -0
  32. package/dist/aiScoping/index.js +4 -0
  33. package/dist/aiScoping/index.js.map +1 -0
  34. package/dist/aiScoping/runAiScoping.d.ts +36 -0
  35. package/dist/aiScoping/runAiScoping.d.ts.map +1 -0
  36. package/dist/aiScoping/runAiScoping.js +100 -0
  37. package/dist/aiScoping/runAiScoping.js.map +1 -0
  38. package/dist/aiScoping/runScopingCall.d.ts +23 -0
  39. package/dist/aiScoping/runScopingCall.d.ts.map +1 -0
  40. package/dist/aiScoping/runScopingCall.js +50 -0
  41. package/dist/aiScoping/runScopingCall.js.map +1 -0
  42. package/dist/aiScoping/validateAiScoping.d.ts +7 -0
  43. package/dist/aiScoping/validateAiScoping.d.ts.map +1 -0
  44. package/dist/aiScoping/validateAiScoping.js +33 -0
  45. package/dist/aiScoping/validateAiScoping.js.map +1 -0
  46. package/dist/aiSkillsUpstreamExports.d.ts +13 -0
  47. package/dist/aiSkillsUpstreamExports.d.ts.map +1 -0
  48. package/dist/aiSkillsUpstreamExports.js +12 -0
  49. package/dist/aiSkillsUpstreamExports.js.map +1 -0
  50. package/dist/analysis/analyzeRunTaskRequest.d.ts +29 -0
  51. package/dist/analysis/analyzeRunTaskRequest.d.ts.map +1 -0
  52. package/dist/analysis/analyzeRunTaskRequest.js +85 -0
  53. package/dist/analysis/analyzeRunTaskRequest.js.map +1 -0
  54. package/dist/analysis/index.d.ts +3 -0
  55. package/dist/analysis/index.d.ts.map +1 -0
  56. package/dist/analysis/index.js +2 -0
  57. package/dist/analysis/index.js.map +1 -0
  58. package/dist/builders/task-request-builder.d.ts +310 -0
  59. package/dist/builders/task-request-builder.d.ts.map +1 -0
  60. package/dist/builders/task-request-builder.js +581 -0
  61. package/dist/builders/task-request-builder.js.map +1 -0
  62. package/dist/compile/compileTaskConfiguration.d.ts +15 -0
  63. package/dist/compile/compileTaskConfiguration.d.ts.map +1 -0
  64. package/dist/compile/compileTaskConfiguration.js +184 -0
  65. package/dist/compile/compileTaskConfiguration.js.map +1 -0
  66. package/dist/compile/index.d.ts +2 -0
  67. package/dist/compile/index.d.ts.map +1 -0
  68. package/dist/compile/index.js +2 -0
  69. package/dist/compile/index.js.map +1 -0
  70. package/dist/core/task-sdk.d.ts +36 -0
  71. package/dist/core/task-sdk.d.ts.map +1 -0
  72. package/dist/core/task-sdk.js +2432 -0
  73. package/dist/core/task-sdk.js.map +1 -0
  74. package/dist/errors/smartInputValidationError.d.ts +39 -0
  75. package/dist/errors/smartInputValidationError.d.ts.map +1 -0
  76. package/dist/errors/smartInputValidationError.js +97 -0
  77. package/dist/errors/smartInputValidationError.js.map +1 -0
  78. package/dist/errors/taskConfigurationCompileError.d.ts +16 -0
  79. package/dist/errors/taskConfigurationCompileError.d.ts.map +1 -0
  80. package/dist/errors/taskConfigurationCompileError.js +20 -0
  81. package/dist/errors/taskConfigurationCompileError.js.map +1 -0
  82. package/dist/execution-strategies/applyExecutionStrategyOutputs.d.ts +17 -0
  83. package/dist/execution-strategies/applyExecutionStrategyOutputs.d.ts.map +1 -0
  84. package/dist/execution-strategies/applyExecutionStrategyOutputs.js +63 -0
  85. package/dist/execution-strategies/applyExecutionStrategyOutputs.js.map +1 -0
  86. package/dist/execution-strategies/constants.d.ts +14 -0
  87. package/dist/execution-strategies/constants.d.ts.map +1 -0
  88. package/dist/execution-strategies/constants.js +13 -0
  89. package/dist/execution-strategies/constants.js.map +1 -0
  90. package/dist/execution-strategies/executionStrategyCatalogMetadata.d.ts +9 -0
  91. package/dist/execution-strategies/executionStrategyCatalogMetadata.d.ts.map +1 -0
  92. package/dist/execution-strategies/executionStrategyCatalogMetadata.js +37 -0
  93. package/dist/execution-strategies/executionStrategyCatalogMetadata.js.map +1 -0
  94. package/dist/execution-strategies/genericExecutionFuncxEnvelope.d.ts +94 -0
  95. package/dist/execution-strategies/genericExecutionFuncxEnvelope.d.ts.map +1 -0
  96. package/dist/execution-strategies/genericExecutionFuncxEnvelope.js +306 -0
  97. package/dist/execution-strategies/genericExecutionFuncxEnvelope.js.map +1 -0
  98. package/dist/execution-strategies/resolveExecutionStrategies.d.ts +14 -0
  99. package/dist/execution-strategies/resolveExecutionStrategies.d.ts.map +1 -0
  100. package/dist/execution-strategies/resolveExecutionStrategies.js +108 -0
  101. package/dist/execution-strategies/resolveExecutionStrategies.js.map +1 -0
  102. package/dist/execution-strategies/runFuncxExecutionStrategy.d.ts +37 -0
  103. package/dist/execution-strategies/runFuncxExecutionStrategy.d.ts.map +1 -0
  104. package/dist/execution-strategies/runFuncxExecutionStrategy.js +72 -0
  105. package/dist/execution-strategies/runFuncxExecutionStrategy.js.map +1 -0
  106. package/dist/index.d.ts +99 -0
  107. package/dist/index.d.ts.map +1 -0
  108. package/dist/index.js +106 -0
  109. package/dist/index.js.map +1 -0
  110. package/dist/internal/resolveLlmCallForXynthesis.d.ts +52 -0
  111. package/dist/internal/resolveLlmCallForXynthesis.d.ts.map +1 -0
  112. package/dist/internal/resolveLlmCallForXynthesis.js +81 -0
  113. package/dist/internal/resolveLlmCallForXynthesis.js.map +1 -0
  114. package/dist/internal/resolveRunTaskRuntimeKnobs.d.ts +19 -0
  115. package/dist/internal/resolveRunTaskRuntimeKnobs.d.ts.map +1 -0
  116. package/dist/internal/resolveRunTaskRuntimeKnobs.js +52 -0
  117. package/dist/internal/resolveRunTaskRuntimeKnobs.js.map +1 -0
  118. package/dist/internal/runPostStepLlmCall.d.ts +52 -0
  119. package/dist/internal/runPostStepLlmCall.d.ts.map +1 -0
  120. package/dist/internal/runPostStepLlmCall.js +170 -0
  121. package/dist/internal/runPostStepLlmCall.js.map +1 -0
  122. package/dist/localTasks/collectEvidence.d.ts +3 -0
  123. package/dist/localTasks/collectEvidence.d.ts.map +1 -0
  124. package/dist/localTasks/collectEvidence.js +364 -0
  125. package/dist/localTasks/collectEvidence.js.map +1 -0
  126. package/dist/localTasks/decideWebScope.d.ts +3 -0
  127. package/dist/localTasks/decideWebScope.d.ts.map +1 -0
  128. package/dist/localTasks/decideWebScope.js +56 -0
  129. package/dist/localTasks/decideWebScope.js.map +1 -0
  130. package/dist/localTasks/index.d.ts +5 -0
  131. package/dist/localTasks/index.d.ts.map +1 -0
  132. package/dist/localTasks/index.js +19 -0
  133. package/dist/localTasks/index.js.map +1 -0
  134. package/dist/localTasks/narrixAssetPlayground.d.ts +13 -0
  135. package/dist/localTasks/narrixAssetPlayground.d.ts.map +1 -0
  136. package/dist/localTasks/narrixAssetPlayground.js +161 -0
  137. package/dist/localTasks/narrixAssetPlayground.js.map +1 -0
  138. package/dist/localTasks/narrixSubnetPlayground.d.ts +14 -0
  139. package/dist/localTasks/narrixSubnetPlayground.d.ts.map +1 -0
  140. package/dist/localTasks/narrixSubnetPlayground.js +168 -0
  141. package/dist/localTasks/narrixSubnetPlayground.js.map +1 -0
  142. package/dist/localTasks/narrixVulnGroupPlayground.d.ts +13 -0
  143. package/dist/localTasks/narrixVulnGroupPlayground.d.ts.map +1 -0
  144. package/dist/localTasks/narrixVulnGroupPlayground.js +161 -0
  145. package/dist/localTasks/narrixVulnGroupPlayground.js.map +1 -0
  146. package/dist/localTasks/narrixVulnInstancePlayground.d.ts +13 -0
  147. package/dist/localTasks/narrixVulnInstancePlayground.d.ts.map +1 -0
  148. package/dist/localTasks/narrixVulnInstancePlayground.js +165 -0
  149. package/dist/localTasks/narrixVulnInstancePlayground.js.map +1 -0
  150. package/dist/localTasks/nodeCallExport.d.ts +6 -0
  151. package/dist/localTasks/nodeCallExport.d.ts.map +1 -0
  152. package/dist/localTasks/nodeCallExport.js +99 -0
  153. package/dist/localTasks/nodeCallExport.js.map +1 -0
  154. package/dist/localTasks/nodeCallExportBatch.d.ts +3 -0
  155. package/dist/localTasks/nodeCallExportBatch.d.ts.map +1 -0
  156. package/dist/localTasks/nodeCallExportBatch.js +52 -0
  157. package/dist/localTasks/nodeCallExportBatch.js.map +1 -0
  158. package/dist/localTasks/normalizeNarrixResult.d.ts +3 -0
  159. package/dist/localTasks/normalizeNarrixResult.d.ts.map +1 -0
  160. package/dist/localTasks/normalizeNarrixResult.js +106 -0
  161. package/dist/localTasks/normalizeNarrixResult.js.map +1 -0
  162. package/dist/localTasks/registry.d.ts +4 -0
  163. package/dist/localTasks/registry.d.ts.map +1 -0
  164. package/dist/localTasks/registry.js +8 -0
  165. package/dist/localTasks/registry.js.map +1 -0
  166. package/dist/localTasks/types.d.ts +28 -0
  167. package/dist/localTasks/types.d.ts.map +1 -0
  168. package/dist/localTasks/types.js +2 -0
  169. package/dist/localTasks/types.js.map +1 -0
  170. package/dist/localTasks/validateInput.d.ts +3 -0
  171. package/dist/localTasks/validateInput.d.ts.map +1 -0
  172. package/dist/localTasks/validateInput.js +66 -0
  173. package/dist/localTasks/validateInput.js.map +1 -0
  174. package/dist/methods/convenience-methods.d.ts +45 -0
  175. package/dist/methods/convenience-methods.d.ts.map +1 -0
  176. package/dist/methods/convenience-methods.js +39 -0
  177. package/dist/methods/convenience-methods.js.map +1 -0
  178. package/dist/narrix/applyNarrixScope.d.ts +10 -0
  179. package/dist/narrix/applyNarrixScope.d.ts.map +1 -0
  180. package/dist/narrix/applyNarrixScope.js +69 -0
  181. package/dist/narrix/applyNarrixScope.js.map +1 -0
  182. package/dist/narrix/buildNarrixAttachment.d.ts +9 -0
  183. package/dist/narrix/buildNarrixAttachment.d.ts.map +1 -0
  184. package/dist/narrix/buildNarrixAttachment.js +29 -0
  185. package/dist/narrix/buildNarrixAttachment.js.map +1 -0
  186. package/dist/narrix/buildWebScopeScopeInput.d.ts +39 -0
  187. package/dist/narrix/buildWebScopeScopeInput.d.ts.map +1 -0
  188. package/dist/narrix/buildWebScopeScopeInput.js +193 -0
  189. package/dist/narrix/buildWebScopeScopeInput.js.map +1 -0
  190. package/dist/narrix/flags.d.ts +4 -0
  191. package/dist/narrix/flags.d.ts.map +1 -0
  192. package/dist/narrix/flags.js +4 -0
  193. package/dist/narrix/flags.js.map +1 -0
  194. package/dist/narrix/index.d.ts +11 -0
  195. package/dist/narrix/index.d.ts.map +1 -0
  196. package/dist/narrix/index.js +14 -0
  197. package/dist/narrix/index.js.map +1 -0
  198. package/dist/narrix/narrixClient.d.ts +9 -0
  199. package/dist/narrix/narrixClient.d.ts.map +1 -0
  200. package/dist/narrix/narrixClient.js +46 -0
  201. package/dist/narrix/narrixClient.js.map +1 -0
  202. package/dist/narrix/narrixContextMarkdown.d.ts +15 -0
  203. package/dist/narrix/narrixContextMarkdown.d.ts.map +1 -0
  204. package/dist/narrix/narrixContextMarkdown.js +98 -0
  205. package/dist/narrix/narrixContextMarkdown.js.map +1 -0
  206. package/dist/narrix/narrixRunnerModule.d.ts +11 -0
  207. package/dist/narrix/narrixRunnerModule.d.ts.map +1 -0
  208. package/dist/narrix/narrixRunnerModule.js +17 -0
  209. package/dist/narrix/narrixRunnerModule.js.map +1 -0
  210. package/dist/narrix/runNarrixForChat.d.ts +3 -0
  211. package/dist/narrix/runNarrixForChat.d.ts.map +1 -0
  212. package/dist/narrix/runNarrixForChat.js +51 -0
  213. package/dist/narrix/runNarrixForChat.js.map +1 -0
  214. package/dist/narrix/runNarrixForDocs.d.ts +3 -0
  215. package/dist/narrix/runNarrixForDocs.d.ts.map +1 -0
  216. package/dist/narrix/runNarrixForDocs.js +50 -0
  217. package/dist/narrix/runNarrixForDocs.js.map +1 -0
  218. package/dist/narrix/runNarrixForRecord.d.ts +3 -0
  219. package/dist/narrix/runNarrixForRecord.d.ts.map +1 -0
  220. package/dist/narrix/runNarrixForRecord.js +47 -0
  221. package/dist/narrix/runNarrixForRecord.js.map +1 -0
  222. package/dist/narrix/runNarrixForText.d.ts +3 -0
  223. package/dist/narrix/runNarrixForText.d.ts.map +1 -0
  224. package/dist/narrix/runNarrixForText.js +49 -0
  225. package/dist/narrix/runNarrixForText.js.map +1 -0
  226. package/dist/narrix/runnerDispatch.d.ts +12 -0
  227. package/dist/narrix/runnerDispatch.d.ts.map +1 -0
  228. package/dist/narrix/runnerDispatch.js +19 -0
  229. package/dist/narrix/runnerDispatch.js.map +1 -0
  230. package/dist/narrix/seedBundleRouting.d.ts +15 -0
  231. package/dist/narrix/seedBundleRouting.d.ts.map +1 -0
  232. package/dist/narrix/seedBundleRouting.js +46 -0
  233. package/dist/narrix/seedBundleRouting.js.map +1 -0
  234. package/dist/narrix/task.d.ts +4 -0
  235. package/dist/narrix/task.d.ts.map +1 -0
  236. package/dist/narrix/task.js +143 -0
  237. package/dist/narrix/task.js.map +1 -0
  238. package/dist/narrix/types.d.ts +104 -0
  239. package/dist/narrix/types.d.ts.map +1 -0
  240. package/dist/narrix/types.js +3 -0
  241. package/dist/narrix/types.js.map +1 -0
  242. package/dist/narrix/webContextMarkdown.d.ts +54 -0
  243. package/dist/narrix/webContextMarkdown.d.ts.map +1 -0
  244. package/dist/narrix/webContextMarkdown.js +206 -0
  245. package/dist/narrix/webContextMarkdown.js.map +1 -0
  246. package/dist/narrix/webScoper.d.ts +43 -0
  247. package/dist/narrix/webScoper.d.ts.map +1 -0
  248. package/dist/narrix/webScoper.js +144 -0
  249. package/dist/narrix/webScoper.js.map +1 -0
  250. package/dist/observability/debugTrace.d.ts +31 -0
  251. package/dist/observability/debugTrace.d.ts.map +1 -0
  252. package/dist/observability/debugTrace.js +117 -0
  253. package/dist/observability/debugTrace.js.map +1 -0
  254. package/dist/observability/extractAiTasksObservability.d.ts +7 -0
  255. package/dist/observability/extractAiTasksObservability.d.ts.map +1 -0
  256. package/dist/observability/extractAiTasksObservability.js +98 -0
  257. package/dist/observability/extractAiTasksObservability.js.map +1 -0
  258. package/dist/observability/graphExecutionRunLogContract.d.ts +19 -0
  259. package/dist/observability/graphExecutionRunLogContract.d.ts.map +1 -0
  260. package/dist/observability/graphExecutionRunLogContract.js +11 -0
  261. package/dist/observability/graphExecutionRunLogContract.js.map +1 -0
  262. package/dist/packaged-tasks-client.d.ts +66 -0
  263. package/dist/packaged-tasks-client.d.ts.map +1 -0
  264. package/dist/packaged-tasks-client.js +100 -0
  265. package/dist/packaged-tasks-client.js.map +1 -0
  266. package/dist/planWebScopeQuestions/index.d.ts +78 -0
  267. package/dist/planWebScopeQuestions/index.d.ts.map +1 -0
  268. package/dist/planWebScopeQuestions/index.js +282 -0
  269. package/dist/planWebScopeQuestions/index.js.map +1 -0
  270. package/dist/planWebScopeQuestions/runResearchPlanQuestionsFuncx.d.ts +18 -0
  271. package/dist/planWebScopeQuestions/runResearchPlanQuestionsFuncx.d.ts.map +1 -0
  272. package/dist/planWebScopeQuestions/runResearchPlanQuestionsFuncx.js +42 -0
  273. package/dist/planWebScopeQuestions/runResearchPlanQuestionsFuncx.js.map +1 -0
  274. package/dist/post-steps/audit/loadAuditTemplates.d.ts +72 -0
  275. package/dist/post-steps/audit/loadAuditTemplates.d.ts.map +1 -0
  276. package/dist/post-steps/audit/loadAuditTemplates.js +62 -0
  277. package/dist/post-steps/audit/loadAuditTemplates.js.map +1 -0
  278. package/dist/post-steps/audit/parseAuditOutput.d.ts +11 -0
  279. package/dist/post-steps/audit/parseAuditOutput.d.ts.map +1 -0
  280. package/dist/post-steps/audit/parseAuditOutput.js +50 -0
  281. package/dist/post-steps/audit/parseAuditOutput.js.map +1 -0
  282. package/dist/post-steps/audit/runAudit.d.ts +22 -0
  283. package/dist/post-steps/audit/runAudit.d.ts.map +1 -0
  284. package/dist/post-steps/audit/runAudit.js +406 -0
  285. package/dist/post-steps/audit/runAudit.js.map +1 -0
  286. package/dist/post-steps/audit/runAuditCall.d.ts +23 -0
  287. package/dist/post-steps/audit/runAuditCall.d.ts.map +1 -0
  288. package/dist/post-steps/audit/runAuditCall.js +32 -0
  289. package/dist/post-steps/audit/runAuditCall.js.map +1 -0
  290. package/dist/post-steps/polish/loadPolishTemplates.d.ts +35 -0
  291. package/dist/post-steps/polish/loadPolishTemplates.d.ts.map +1 -0
  292. package/dist/post-steps/polish/loadPolishTemplates.js +38 -0
  293. package/dist/post-steps/polish/loadPolishTemplates.js.map +1 -0
  294. package/dist/post-steps/polish/parsePolishOutput.d.ts +6 -0
  295. package/dist/post-steps/polish/parsePolishOutput.d.ts.map +1 -0
  296. package/dist/post-steps/polish/parsePolishOutput.js +47 -0
  297. package/dist/post-steps/polish/parsePolishOutput.js.map +1 -0
  298. package/dist/post-steps/polish/runPolish.d.ts +24 -0
  299. package/dist/post-steps/polish/runPolish.d.ts.map +1 -0
  300. package/dist/post-steps/polish/runPolish.js +147 -0
  301. package/dist/post-steps/polish/runPolish.js.map +1 -0
  302. package/dist/post-steps/polish/runPolishCall.d.ts +22 -0
  303. package/dist/post-steps/polish/runPolishCall.d.ts.map +1 -0
  304. package/dist/post-steps/polish/runPolishCall.js +32 -0
  305. package/dist/post-steps/polish/runPolishCall.js.map +1 -0
  306. package/dist/post-steps/resolvePostStepConfig.d.ts +58 -0
  307. package/dist/post-steps/resolvePostStepConfig.d.ts.map +1 -0
  308. package/dist/post-steps/resolvePostStepConfig.js +105 -0
  309. package/dist/post-steps/resolvePostStepConfig.js.map +1 -0
  310. package/dist/rendrixUpstreamExports.d.ts +7 -0
  311. package/dist/rendrixUpstreamExports.d.ts.map +1 -0
  312. package/dist/rendrixUpstreamExports.js +6 -0
  313. package/dist/rendrixUpstreamExports.js.map +1 -0
  314. package/dist/skillCatalogExports.d.ts +8 -0
  315. package/dist/skillCatalogExports.d.ts.map +1 -0
  316. package/dist/skillCatalogExports.js +8 -0
  317. package/dist/skillCatalogExports.js.map +1 -0
  318. package/dist/strategies/direct-execution-strategy.d.ts +31 -0
  319. package/dist/strategies/direct-execution-strategy.d.ts.map +1 -0
  320. package/dist/strategies/direct-execution-strategy.js +107 -0
  321. package/dist/strategies/direct-execution-strategy.js.map +1 -0
  322. package/dist/strategies/execution-strategy.interface.d.ts +31 -0
  323. package/dist/strategies/execution-strategy.interface.d.ts.map +1 -0
  324. package/dist/strategies/execution-strategy.interface.js +2 -0
  325. package/dist/strategies/execution-strategy.interface.js.map +1 -0
  326. package/dist/strategies/index.d.ts +9 -0
  327. package/dist/strategies/index.d.ts.map +1 -0
  328. package/dist/strategies/index.js +8 -0
  329. package/dist/strategies/index.js.map +1 -0
  330. package/dist/strategies/strategy-factory.d.ts +45 -0
  331. package/dist/strategies/strategy-factory.d.ts.map +1 -0
  332. package/dist/strategies/strategy-factory.js +59 -0
  333. package/dist/strategies/strategy-factory.js.map +1 -0
  334. package/dist/synthesis/index.d.ts +9 -0
  335. package/dist/synthesis/index.d.ts.map +1 -0
  336. package/dist/synthesis/index.js +8 -0
  337. package/dist/synthesis/index.js.map +1 -0
  338. package/dist/synthesis/resolveSourceMaterial.d.ts +35 -0
  339. package/dist/synthesis/resolveSourceMaterial.d.ts.map +1 -0
  340. package/dist/synthesis/resolveSourceMaterial.js +152 -0
  341. package/dist/synthesis/resolveSourceMaterial.js.map +1 -0
  342. package/dist/synthesis/runStructuredSynthesisRobust.d.ts +42 -0
  343. package/dist/synthesis/runStructuredSynthesisRobust.d.ts.map +1 -0
  344. package/dist/synthesis/runStructuredSynthesisRobust.js +303 -0
  345. package/dist/synthesis/runStructuredSynthesisRobust.js.map +1 -0
  346. package/dist/task-strategies/buildTaskStrategyCatalogDescriptor.d.ts +19 -0
  347. package/dist/task-strategies/buildTaskStrategyCatalogDescriptor.d.ts.map +1 -0
  348. package/dist/task-strategies/buildTaskStrategyCatalogDescriptor.js +242 -0
  349. package/dist/task-strategies/buildTaskStrategyCatalogDescriptor.js.map +1 -0
  350. package/dist/task-strategies/canonicalInputExecutionStrategies.d.ts +171 -0
  351. package/dist/task-strategies/canonicalInputExecutionStrategies.d.ts.map +1 -0
  352. package/dist/task-strategies/canonicalInputExecutionStrategies.js +117 -0
  353. package/dist/task-strategies/canonicalInputExecutionStrategies.js.map +1 -0
  354. package/dist/task-strategies/canonicalNarrixModes.d.ts +31 -0
  355. package/dist/task-strategies/canonicalNarrixModes.d.ts.map +1 -0
  356. package/dist/task-strategies/canonicalNarrixModes.js +35 -0
  357. package/dist/task-strategies/canonicalNarrixModes.js.map +1 -0
  358. package/dist/task-strategies/canonicalTaskStrategies.d.ts +104 -0
  359. package/dist/task-strategies/canonicalTaskStrategies.d.ts.map +1 -0
  360. package/dist/task-strategies/canonicalTaskStrategies.js +77 -0
  361. package/dist/task-strategies/canonicalTaskStrategies.js.map +1 -0
  362. package/dist/task-strategies/cataloxCatalogViews.d.ts +55 -0
  363. package/dist/task-strategies/cataloxCatalogViews.d.ts.map +1 -0
  364. package/dist/task-strategies/cataloxCatalogViews.js +65 -0
  365. package/dist/task-strategies/cataloxCatalogViews.js.map +1 -0
  366. package/dist/task-strategies/constants.d.ts +49 -0
  367. package/dist/task-strategies/constants.d.ts.map +1 -0
  368. package/dist/task-strategies/constants.js +49 -0
  369. package/dist/task-strategies/constants.js.map +1 -0
  370. package/dist/task-strategies/index.d.ts +22 -0
  371. package/dist/task-strategies/index.d.ts.map +1 -0
  372. package/dist/task-strategies/index.js +13 -0
  373. package/dist/task-strategies/index.js.map +1 -0
  374. package/dist/task-strategies/listAiTaskStrategies.d.ts +43 -0
  375. package/dist/task-strategies/listAiTaskStrategies.d.ts.map +1 -0
  376. package/dist/task-strategies/listAiTaskStrategies.js +74 -0
  377. package/dist/task-strategies/listAiTaskStrategies.js.map +1 -0
  378. package/dist/task-strategies/normalize.d.ts +7 -0
  379. package/dist/task-strategies/normalize.d.ts.map +1 -0
  380. package/dist/task-strategies/normalize.js +44 -0
  381. package/dist/task-strategies/normalize.js.map +1 -0
  382. package/dist/task-strategies/types.d.ts +37 -0
  383. package/dist/task-strategies/types.d.ts.map +1 -0
  384. package/dist/task-strategies/types.js +2 -0
  385. package/dist/task-strategies/types.js.map +1 -0
  386. package/dist/types/decision-contracts.d.ts +31 -0
  387. package/dist/types/decision-contracts.d.ts.map +1 -0
  388. package/dist/types/decision-contracts.js +23 -0
  389. package/dist/types/decision-contracts.js.map +1 -0
  390. package/dist/types/evidence-types.d.ts +108 -0
  391. package/dist/types/evidence-types.d.ts.map +1 -0
  392. package/dist/types/evidence-types.js +9 -0
  393. package/dist/types/evidence-types.js.map +1 -0
  394. package/dist/types/executionType.d.ts +9 -0
  395. package/dist/types/executionType.d.ts.map +1 -0
  396. package/dist/types/executionType.js +8 -0
  397. package/dist/types/executionType.js.map +1 -0
  398. package/dist/types/index.d.ts +28 -0
  399. package/dist/types/index.d.ts.map +1 -0
  400. package/dist/types/index.js +12 -0
  401. package/dist/types/index.js.map +1 -0
  402. package/dist/types/llmCall.d.ts +121 -0
  403. package/dist/types/llmCall.d.ts.map +1 -0
  404. package/dist/types/llmCall.js +39 -0
  405. package/dist/types/llmCall.js.map +1 -0
  406. package/dist/types/task-configuration.d.ts +60 -0
  407. package/dist/types/task-configuration.d.ts.map +1 -0
  408. package/dist/types/task-configuration.js +3 -0
  409. package/dist/types/task-configuration.js.map +1 -0
  410. package/dist/types/task-types.d.ts +887 -0
  411. package/dist/types/task-types.d.ts.map +1 -0
  412. package/dist/types/task-types.js +21 -0
  413. package/dist/types/task-types.js.map +1 -0
  414. package/dist/utilities/runUtility.d.ts +3 -0
  415. package/dist/utilities/runUtility.d.ts.map +1 -0
  416. package/dist/utilities/runUtility.js +204 -0
  417. package/dist/utilities/runUtility.js.map +1 -0
  418. package/dist/utils/assertRequiredRunSkillCorrelation.d.ts +7 -0
  419. package/dist/utils/assertRequiredRunSkillCorrelation.d.ts.map +1 -0
  420. package/dist/utils/assertRequiredRunSkillCorrelation.js +17 -0
  421. package/dist/utils/assertRequiredRunSkillCorrelation.js.map +1 -0
  422. package/dist/utils/assertValidSmartInputConfig.d.ts +5 -0
  423. package/dist/utils/assertValidSmartInputConfig.d.ts.map +1 -0
  424. package/dist/utils/assertValidSmartInputConfig.js +71 -0
  425. package/dist/utils/assertValidSmartInputConfig.js.map +1 -0
  426. package/dist/utils/bridgeRunSkillGatewayMemory.d.ts +13 -0
  427. package/dist/utils/bridgeRunSkillGatewayMemory.d.ts.map +1 -0
  428. package/dist/utils/bridgeRunSkillGatewayMemory.js +65 -0
  429. package/dist/utils/bridgeRunSkillGatewayMemory.js.map +1 -0
  430. package/dist/utils/extractSmartInputRenderResult.d.ts +7 -0
  431. package/dist/utils/extractSmartInputRenderResult.d.ts.map +1 -0
  432. package/dist/utils/extractSmartInputRenderResult.js +30 -0
  433. package/dist/utils/extractSmartInputRenderResult.js.map +1 -0
  434. package/dist/utils/jsonPaths.d.ts +6 -0
  435. package/dist/utils/jsonPaths.d.ts.map +1 -0
  436. package/dist/utils/jsonPaths.js +32 -0
  437. package/dist/utils/jsonPaths.js.map +1 -0
  438. package/dist/utils/normalizeSmartInputConfig.d.ts +5 -0
  439. package/dist/utils/normalizeSmartInputConfig.d.ts.map +1 -0
  440. package/dist/utils/normalizeSmartInputConfig.js +30 -0
  441. package/dist/utils/normalizeSmartInputConfig.js.map +1 -0
  442. package/dist/utils/outputValidation.d.ts +19 -0
  443. package/dist/utils/outputValidation.d.ts.map +1 -0
  444. package/dist/utils/outputValidation.js +75 -0
  445. package/dist/utils/outputValidation.js.map +1 -0
  446. package/dist/utils/runTaskRequestShape.d.ts +16 -0
  447. package/dist/utils/runTaskRequestShape.d.ts.map +1 -0
  448. package/dist/utils/runTaskRequestShape.js +80 -0
  449. package/dist/utils/runTaskRequestShape.js.map +1 -0
  450. package/dist/utils/skillTemplateVariables.d.ts +20 -0
  451. package/dist/utils/skillTemplateVariables.d.ts.map +1 -0
  452. package/dist/utils/skillTemplateVariables.js +63 -0
  453. package/dist/utils/skillTemplateVariables.js.map +1 -0
  454. package/dist/utils/xynthesizedSmartInputPaths.d.ts +16 -0
  455. package/dist/utils/xynthesizedSmartInputPaths.d.ts.map +1 -0
  456. package/dist/utils/xynthesizedSmartInputPaths.js +56 -0
  457. package/dist/utils/xynthesizedSmartInputPaths.js.map +1 -0
  458. package/dist/utils/xynthesizedWrite.d.ts +10 -0
  459. package/dist/utils/xynthesizedWrite.d.ts.map +1 -0
  460. package/dist/utils/xynthesizedWrite.js +61 -0
  461. package/dist/utils/xynthesizedWrite.js.map +1 -0
  462. package/dist/validation/analyzeExpectedRunTaskInput.d.ts +41 -0
  463. package/dist/validation/analyzeExpectedRunTaskInput.d.ts.map +1 -0
  464. package/dist/validation/analyzeExpectedRunTaskInput.js +133 -0
  465. package/dist/validation/analyzeExpectedRunTaskInput.js.map +1 -0
  466. package/dist/validation/collectSmartInputValidationIssues.d.ts +6 -0
  467. package/dist/validation/collectSmartInputValidationIssues.d.ts.map +1 -0
  468. package/dist/validation/collectSmartInputValidationIssues.js +38 -0
  469. package/dist/validation/collectSmartInputValidationIssues.js.map +1 -0
  470. package/dist/validation/helpers.d.ts +15 -0
  471. package/dist/validation/helpers.d.ts.map +1 -0
  472. package/dist/validation/helpers.js +184 -0
  473. package/dist/validation/helpers.js.map +1 -0
  474. package/dist/validation/index.d.ts +9 -0
  475. package/dist/validation/index.d.ts.map +1 -0
  476. package/dist/validation/index.js +6 -0
  477. package/dist/validation/index.js.map +1 -0
  478. package/dist/validation/types.d.ts +51 -0
  479. package/dist/validation/types.d.ts.map +1 -0
  480. package/dist/validation/types.js +5 -0
  481. package/dist/validation/types.js.map +1 -0
  482. package/dist/validation/validateRunTaskConfig.d.ts +8 -0
  483. package/dist/validation/validateRunTaskConfig.d.ts.map +1 -0
  484. package/dist/validation/validateRunTaskConfig.js +158 -0
  485. package/dist/validation/validateRunTaskConfig.js.map +1 -0
  486. package/dist/validation/validateRunTaskInvoke.d.ts +30 -0
  487. package/dist/validation/validateRunTaskInvoke.d.ts.map +1 -0
  488. package/dist/validation/validateRunTaskInvoke.js +108 -0
  489. package/dist/validation/validateRunTaskInvoke.js.map +1 -0
  490. package/documenations/activix-feature-request-identity.md +123 -0
  491. package/documenations/activix.md +175 -0
  492. package/documenations/bug-report-xynthesis-and-synthesis-call.md +217 -0
  493. package/documenations/core-runtime-tokens-and-strategies.md +123 -0
  494. package/documenations/downstream-environment.md +48 -0
  495. package/documenations/downstream-test-runtime-teardown-cleanup.md +73 -0
  496. package/documenations/examples/xynthesis-run-task-request.example.json +170 -0
  497. package/documenations/feature-request-ai-skills-raw-template-access.md +82 -0
  498. package/documenations/feature-request-athenix-core-directive.md +145 -0
  499. package/documenations/feature-request-athenix-token-extraction.md +124 -0
  500. package/documenations/funcx-catalog-hosting-checklist.md +107 -0
  501. package/documenations/funcx-scoping-integration-gaps.md +120 -0
  502. package/documenations/funcx-upstream-github-issues-draft.md +153 -0
  503. package/documenations/identity-metadata-contract.md +165 -0
  504. package/documenations/intermediate-steps.md +33 -0
  505. package/documenations/record-and-template-variables.md +32 -0
  506. package/documenations/run-task-execution-flow.md +153 -0
  507. package/documenations/run-task-single-run-checklist.md +109 -0
  508. package/documenations/schemas/README.md +40 -0
  509. package/documenations/schemas/openapi-3.1-components.yaml +24 -0
  510. package/documenations/schemas/v1/output-schema.json +55 -0
  511. package/documenations/schemas/v1/output-validation-result.json +41 -0
  512. package/documenations/schemas/v1/run-task-request.json +219 -0
  513. package/documenations/schemas/v1/synthesized-artifacts.json +133 -0
  514. package/documenations/synthesis-invocation-notes.md +26 -0
  515. package/documenations/synthesized-context-guide.md +84 -0
  516. package/documenations/task-core-and-core-aware-synthesis.md +58 -0
  517. package/documenations/upstream-feature-requests/ai-skills-llm-observability.md +129 -0
  518. package/documenations/upstream-feature-requests/xynthesis-llm-observability.md +125 -0
  519. package/documenations/upstream-feedback-request-shape-clarification.md +101 -0
  520. package/documenations/web-context-precedence.md +33 -0
  521. package/documenations/web-scoping-in-ai-tasks.md +503 -0
  522. package/documenations/xynthesis-activix-telemetry.md +28 -0
  523. package/documenations/xynthesis-upstream-fixes-checklist.md +71 -0
  524. package/package.json +92 -0
package/README.md ADDED
@@ -0,0 +1,2497 @@
1
+ # @exellix/ai-tasks
2
+
3
+ Private Git/npm package for executing **tasks** using the Woreces execution stack.
4
+
5
+ **Breaking — `executionStrategies` (required):** Every `runTask` request must include **`executionStrategies`**: an array of FuncX MAIN wrappers or **`[]`** for plain gateway MAIN. The old **`executionStrategyKey`** field is removed. See [BREAKING-CHANGES.md](BREAKING-CHANGES.md) and [RUNTASK_REQUEST.md](RUNTASK_REQUEST.md). Supported MAIN execution is exactly: **direct** via **`executionStrategies: []`**, **planner** before MAIN, **optimizer** after MAIN, or planner + optimizer together. Default FuncX function ids (generic envelope via **`run()`**, **`@x12i/funcx` ≥ 3.8.2** recommended): **`execution/plan`**, **`execution/evaluate-result`** (overridable via each row’s `args.functionId` when the alternate implementation uses the **same** envelope). Planner/optimizer responses are normalized with **`getRunJsonResult`** from `@x12i/funcx/functions` (also re-exported from this package as **`unwrapFuncxRunValue`** → **`getRunJsonResult`**).
6
+
7
+ **FuncX catalog / hosting:** Those function ids must exist in your FuncX content resolver for live **`run()`** calls — see [`documenations/funcx-catalog-hosting-checklist.md`](documenations/funcx-catalog-hosting-checklist.md).
8
+
9
+ **Execution pipeline (optional):** You can use `executionPipeline` (array of pre/main/post steps) instead of a single `executionType`. PRE steps include `synthesized-context`; POST steps include `audit` (quality-gate loop) and `polish` (refinement checklist). See [BREAKING-CHANGES.md](BREAKING-CHANGES.md) for migration. When `executionPipeline` is omitted, existing `executionType` behavior is unchanged.
10
+
11
+ This package implements the canonical `runTask()` flow (every request carries **`executionStrategies`** — use **`[]`** for plain MAIN):
12
+
13
+ 1) **if** `request.narrix` **is set** (task-level pre-processor) → resolve raw record from executionMemory/jobMemory/input, run NARRIX (to-CNI + engine), build `_narrix` attachment (scoping/discovery/meta), inject into `executionMemory` and `jobMemory`; **if** `narrix.enableWebScope === true`, also run **`@exellix/narrix-web-scoper`** and set **`executionMemory.webContext`** (failures are non-fatal); then continue with the same request so the task sees enriched context
14
+ 2) **if a local task handler is registered for `skillKey`** → run it and return (no enrichment, no LLM); **`ctx`** includes optional **`xynthesized`** / **`smartInput`** when the caller supplied them
15
+ 3) **if** `executionType === 'narrix-then-direct'` **and** `narrixInput` is provided → resolve narrix input, run Narrix, append output to `taskMemory.narrix`, then run the standard DIRECT path (enrich → context → executor); response includes `metadata.narrix`
16
+ 4) inject `bindingDefaultsDb` for Xronox routing (defaulting to `MONGO_LOGS_DB` or `logs-db`)
17
+ 5) enrich job/task memory with **task-scoped** scoping (using `skillKey`)
18
+ 6) generate **task-scoped** context markdown only when requested (`includeContextInPrompt === true`; default is no context). When NARRIX is in play, context is the "## Scoping and discovery" section from `executionMemory._narrix` (`buildNarrixPreProcessorContextMarkdown`), plus—when web scoping returned a hit—a **## Web sources (primary evidence)** block built from `executionMemory.webContext` (by default **cleaned** text is preferred over raw HTML: `providerContent` / `content` before `providerRawContent` / `rawContent`, then `snippet`; override on the DIRECT path is not exposed—synthesis PRE step can tune via `SynthesisConfig.webEvidence.preferCleanContent`). Summary/findings are labeled as hints only. When NARRIX is not in play, context comes from the context generator plus any `taskMemory.narrix` section.
19
+ 7) execute via executor using `executionType` (or the pipeline MAIN step when `executionPipeline` is set)
20
+ 8) on not-found, call registry diagnostics
21
+
22
+ **`executionPipeline`:** When `executionPipeline` is a non-empty array, PRE steps (e.g. **`synthesized-context`**) run first, then exactly one MAIN step, then optional POST steps. Memory enrichment and context generation in step 6 apply to the MAIN `direct` run; the synthesizer’s **source material** is controlled separately by **`contextSourcePolicy`** / **`webEvidence`** (see below).
23
+
24
+ ### Template core and core-aware synthesis (additive)
25
+
26
+ `runTask` supports template-core-aware structured synthesis in PRE `synthesized-context`:
27
+
28
+ - Template content declares one or more core directives in Athenix `{{core:...}}` format (for example `{{core:analysis}}`); closed list: `question`, `action`, `plan`, `objective`, `decision`, `comparison`, `classification`, `evaluation`, `analysis`, `summary`, `generation`, `extraction`.
29
+ - `RunTaskRequest.taskCore` is removed from structured synthesis semantics; runtime derives cores from template declarations.
30
+ - Synthesis mode selection is additive:
31
+ - preferred: `SynthesisConfig.synthesisMode: "markdown" | "structured"`
32
+ - legacy-compatible: `SynthesisConfig.synthesisOutputFormat` still works.
33
+ - Structured mode uses detected `templateCores` + resolved question, then builds clean MAIN context from validated synthesized payload.
34
+ - Core discovery reads raw templates (instructions/prompt) via **`WorecesSkillsClient.resolveRawTemplate`** (templates loaded from the Catalox **`ai-skills`** catalog in `@woroces/ai-skills` 4.1+) before normal rendering; if no core directives are declared, structured synthesis is rejected as a template-definition error.
35
+ - Raw/enriched materials (`_narrix`, memory bundle, `webContext`) remain in memory; synthesized output is also stored at **`executionMemory.synthesizedContext`** for MAIN traceability unless a PRE step opts out via **`SynthesisConfig.xynthesizedOutput.alsoWriteLegacySynthesizedContext: false`** (see [Xynthesized memory and smart input](#xynthesized-memory-and-smart-input)).
36
+ - Optional **`RunTaskRequest.xynthesized`** holds **job-scoped**, **task-scoped**, and **execution-scoped** synthesized material for graph execution (distinct from raw memories). Optional **`smartInput`** matches **`SmartInputConfig`** from **`@exellix/ai-skills`** / Rendrix (**`paths`** as **`{ title, path, required? }[]`**). Callers may still send legacy **`paths: string[]`**; **`runTask`** validates and **normalizes** each string to **`{ title: path, path }`** before **`runSkill`**. Paths under **`xynthesized.*`** must use scope **`job`**, **`task`**, or **`execution`**; full path resolution for **`{{smartInput}}`** happens in the gateway/Rendrix stack. Optional **`smartInputRenderOptions`** is passed through like other **`RunSkillRequest`** fields.
37
+ - Backward compatibility is preserved by default: existing flows continue unchanged unless structured mode is explicitly selected.
38
+
39
+ Task responses may optionally include **`intermediateSteps`** when a task (or skill) runs multiple logical steps in one call (e.g. to-cni + enrich + triage); see [Intermediate steps (multi-step tasks)](#intermediate-steps-multi-step-tasks).
40
+
41
+ `@exellix/ai-tasks` reuses `@woroces/ai-skills` directly (private packages; naming leakage is explicitly acceptable).
42
+
43
+ ### Gateway template rendering (v4)
44
+
45
+ Task execution goes through **`WorecesSkillsClient.runSkill`** (or the skills client’s executor), which uses **`gateway.invoke()`** — not `invokeChat()` — so instruction, prompt, and context templates are built via **`buildMessages`**, nx-content resolution, and **`@x12i/rendrix`** `render`. The gateway rejects a top-level **`input`** field on invoke requests; **`runSkill` / `runAudit`** map caller **`input`** (and related fields) into **`workingMemory.input`** (merged with `variables`, memories, object `context`, `knowledge`) so `.prompt` templates can use **`{{input}}`**.
46
+
47
+ On the **`runTask`** MAIN path, **`variables`** is the **job/graph bucket** forwarded **as-is** (align with **`executionMemory.jobVariables`**). **`executionMemory.taskVariables`** holds node scope. **`input`**, **`xynthesized`**, and **`smartInput`** are separate top-level fields — ai-tasks does **not** fold them into **`variables`**. Templates and Rendrix resolve **`jobVariables.*`**, **`taskVariables.*`**, **`xynthesized.*`**, and **`smartInput`** against the forwarded payload. Optional host helper **`mergeSkillTemplateVariables`** merges maps outside default MAIN.
48
+
49
+ **In this package:**
50
+
51
+ - **`runTask({ ... })`** accepts the same optional fields as **`RunSkillRequest`**: **`templateRenderOptions`**, **`templateTokens`**, **`smartInputRenderOptions`** (with **`smartInput`**). They are passed through unchanged to **`runSkill`** on the DIRECT / pipeline MAIN path.
52
+ - **Default parser options** for all skill runs: set **`templateRendering`** on **`WorecesSkillsClientOptions`** when constructing **`WorecesSkillsClient`** (or configure **`templateRendering`** on a gateway instance you pass as **`options.gateway`**). Per-call overrides use **`templateRenderOptions`** on the request.
53
+ - **Synthesis, audit, polish, and AI scoping** are executed via **`@exellix/xynthesis`** (and therefore inherit xynthesis behavior for retries/repair and diagnostics when enabled).
54
+
55
+ | Mechanism | Where | Effect |
56
+ |-----------|--------|--------|
57
+ | **`WorecesSkillsClientOptions.templateRendering`** | Client constructor | Sets gateway defaults when this package (or your app) constructs **`AIGateway`**. If you inject **`options.gateway`**, set **`templateRendering`** on that instance. |
58
+ | **`RunTaskRequest.templateRenderOptions`** | Per **`runTask`** | Deep-merged on gateway defaults for that invoke only. |
59
+ | **`RunTaskRequest.templateTokens`** | Per **`runTask`** | Passed through as gateway **`templateTokens`** (highest overlay priority during render). |
60
+
61
+ Types **`TemplateRenderOptions`**, **`SmartInputConfig`**, **`SmartInputRenderOptions`**, **`SmartInputRenderResult`**, **`RunTaskSmartInput`**, and **`GatewayTemplateTokens`** are re-exported from **`@exellix/ai-tasks`** (skill/gateway/Rendrix definitions). **`mergeSkillTemplateVariables`** is exported for callers that reproduce the same merge outside **`runTask`**. **`TaskRequestBuilder`** supports **`.withTemplateRenderOptions(...)`** and **`.withTemplateTokens(...)`**, plus **`.withXynthesized`**, **`.withSmartInput`**, **`.withSmartInputPaths`**, **`.withSmartInputRenderOptions(...)`**, **`.withXynthesizedJob`**, **`.withXynthesizedTask`**, **`.withXynthesizedExecution`** (see [Xynthesized memory and smart input](#xynthesized-memory-and-smart-input)).
62
+
63
+ **Errors:** **`TemplateResolutionError`** (or codes like **`TEMPLATE_RESOLUTION_ERROR`** / **`TEMPLATE_VARIABLE_MISSING`**) means a **required** template path was **`undefined`** after the gateway merged memory. Fix **`variables` / memories / input**, use optional fragments (**`{{path \|}}`**) in templates, or see the v4 guide for legacy **`silentMissingMustTokens`**. Template resolution failures are **not** treated as missing registry content (they do not go through **`RegistryManager.diagnose`** the same way as “content not found”).
64
+
65
+ Full protocol (MUST vs optional tokens, **`subPathSearch`**, errors): **[GATEWAY_TEMPLATE_PROTOCOL_V4.md](https://github.com/woroces/ai-skills/blob/main/GATEWAY_TEMPLATE_PROTOCOL_V4.md)** in **`@woroces/ai-skills`**. Nx-content layout: **`@x12i/ai-gateway`** [Content Resolver — Upstream Guide](https://github.com/athenices/ai-gateway/blob/main/CONTENT_RESOLVER_UPSTREAM_GUIDE.md).
66
+
67
+ ---
68
+
69
+ ## Install
70
+
71
+ **Packaged skills (recommended):** depend only on `@exellix/ai-tasks`; it bundles the execution stack and ships `.metadata` templates.
72
+
73
+ ```bash
74
+ npm install @exellix/ai-tasks
75
+ ```
76
+
77
+ **Custom gateway / advanced injection:** add `@woroces/ai-skills` and construct `WorexClientTasks` with your own client + executor.
78
+
79
+ ```bash
80
+ npm install @exellix/ai-tasks @woroces/ai-skills
81
+ ```
82
+
83
+ **Bundled x12i stack (direct dependencies of this package):** `@x12i/catalox` (catalog reads / publish script), `@x12i/env` (env conventions aligned with the Woreces stack), and `@x12i/logxer` (package log-level helpers such as `resolvePackageLogsLevel` used by the optional Activix client). Older names `logs-gateway` and `nx-config2` are not direct dependencies here.
84
+
85
+ ### Narrix (v5+)
86
+
87
+ From **v5.0.0**, the local **`skills/skill.local:narrixRun`** path and **`narrixInput`** for **`narrix-then-direct`** require a unified shape: always set **`medium`** (`record` | `text` | `docs` | `chat`), **`datasetId`**, and the payload fields for that medium. Routing uses **`@exellix/narrix-runner` v2** (`runByQuestion` / `runByNarrative`) and **`@exellix/narrix-catalox`** for catalog hints—there is **no** `narrix-packs-library` or legacy “record job without `medium`” compatibility.
88
+
89
+ **Migration:** see **[BREAKING-CHANGES-NARRIX-V5.md](./BREAKING-CHANGES-NARRIX-V5.md)**. **Live Narrix tests:** `npm run test:narrix:live` (requires `.archive/packages/…` seed; see that doc).
90
+
91
+ ---
92
+
93
+ ## Catalox catalogs (upstream from this package)
94
+
95
+ `@exellix/ai-tasks` publishes **task-strategy** metadata to Catalox under **`appId: ai-tasks`**, and re-exports **read helpers** so apps can list catalogs, descriptors, and items **without adding `@x12i/catalox` as a direct dependency** (types such as `Catalox`, `CataloxContext`, `AppCatalogBootstrap` are re-exported from the task-strategies surface).
96
+
97
+ ### AI Tasks catalogs
98
+
99
+ | Catalog ID | Role |
100
+ |-------------|------|
101
+ | **`ai-task-strategies-pre`** | **Pre–core** strategies: `synthesisInputStrategy` for the `synthesized-context` PRE step. |
102
+ | **`ai-task-strategies-post`** | **Post–core** strategies: e.g. audit `selectionStrategy` (`best` / `synthesis`). |
103
+ | **`ai-task-input-strategies`** | Optional authoring hints: `inputStrategyKey` on `RunTaskRequest` (does not drive Narrix invocation). |
104
+ | **`execution-strategy`** | Preferred catalog for supported MAIN execution metadata: **`direct`**, **`planner`**, **`optimizer`**. |
105
+ | **`ai-task-execution-strategies`** | Compatibility MAIN wrappers metadata catalog: same seeded rows as **`execution-strategy`**. |
106
+ | **`ai-task-main-execution-wrappers`** | Compatibility MAIN FuncX wrappers catalog: same seeded rows as **`execution-strategy`**. |
107
+ | **`ai-task-narrix-modes`** | Narrix invocation: `narrixMode` (`off` \| `preprocessor` \| `handler`). |
108
+
109
+ Runtime **does not require** Catalox to be configured for these keys. Catalogs seed console metadata, and `execution-strategy` rows may be passed to `runTask` as `executionStrategyCatalogItems` for guarded metadata consumption. The runtime still code-validates supported strategy keys, phases, and retry rules; malformed or conflicting catalog rows are ignored. **Consumption order:** optional Narrix → optional web (preprocessor path) → optional Xynthesis **PRE** steps → **MAIN**.
110
+
111
+ ### Supported MAIN Execution Strategies
112
+
113
+ The runtime supports three MAIN execution strategy records:
114
+
115
+ | Strategy | Request shape | Runtime behavior |
116
+ |----------|---------------|------------------|
117
+ | **`direct`** | **`executionStrategies: []`** | Plain MAIN. Calls `runSkill` once. `direct` is catalog metadata only and is **not** valid as a row inside `executionStrategies[]`. |
118
+ | **`planner`** | **`{ strategyKey: "planner", phase: "before", priority }`** | Runs FuncX **`execution/plan`** before MAIN and can merge instructions, prompt, variables, and template tokens into the request. |
119
+ | **`optimizer`** | **`{ strategyKey: "optimizer", phase: "after", priority, maxIterations? }`** | Runs FuncX **`execution/evaluate-result`** after MAIN. If not satisfied, retries MAIN with feedback until satisfied or `maxIterations` is reached. |
120
+
121
+ `planner` must use `phase: "before"` and `optimizer` must use `phase: "after"`. `optimizer.maxIterations` controls MAIN attempts; when omitted it falls back to `AI_TASKS_OPTIMIZER_MAX_ITERATIONS` or the package default. Per-invocation `args.functionId` always wins over catalog metadata and code defaults.
122
+
123
+ `execution-strategy` catalog items are structured metadata, not an open behavior plug-in. Safe runtime consumption is currently limited to fields explicitly marked in `safeRuntimeFields`, such as wrapper `defaultFunctionId`, and only when the catalog row matches the already-validated strategy key, phase, request shape, and generic FuncX envelope.
124
+
125
+ Legacy monolith id **`ai-task-strategies`** is no longer written by this repo; use the catalogs above.
126
+
127
+ ### Publishing Firestore metadata
128
+
129
+ From a clone of this repo, with Firebase env in `.env` as required by **`createCataloxFromEnv()`** (see **`@exellix/ai-skills`**): **`FIREBASE_PROJECT_ID`** (required), **`GOOGLE_SERVICE_ACCOUNT_BASE64`** (required: service account JSON, base64-encoded), and optional **`FIRESTORE_DATABASE_ID`**:
130
+
131
+ ```bash
132
+ npm run publish:task-strategies
133
+ ```
134
+
135
+ That provisions **`apps/ai-tasks`**, all six native catalogs, bindings, descriptors, and canonical items.
136
+
137
+ ### Reading catalogs and items (from `@exellix/ai-tasks`)
138
+
139
+ Construct a **`Catalox`** instance (for example `createCataloxFromEnv()` from `@exellix/ai-tasks`, which re-exports it from `@woroces/ai-skills`), then use any of:
140
+
141
+ | Export | Purpose |
142
+ |--------|---------|
143
+ | **`defaultAiTasksCataloxContext()`** | Build `CataloxContext` with `appId: "ai-tasks"`. |
144
+ | **`listAiTasksAppCatalogs(catalox, context?)`** | Discovery list: catalog id, label, access, source mode. |
145
+ | **`getAiTasksAppCatalogBootstrap(catalox, context?)`** | All **descriptors** visible to the app (identity, query fields, capabilities). |
146
+ | **`getAiTasksTaskStrategiesCatalogDescriptor(catalox, catalogId, context?)`** | One catalog’s **descriptor** (any of the six catalog ids above). |
147
+ | **`listPreCoreTaskStrategies` / `listPostCoreTaskStrategies`** | **Normalized** rows for pre / post synthesis–audit catalogs. |
148
+ | **`listInputStrategies` / `listExecutionStrategyCatalogItems` / `listExecutionStrategies` / `listMainExecutionWrappers` / `listNarrixModes`** | **Normalized** rows for input, preferred execution metadata, compatibility execution catalogs, MAIN wrappers, and Narrix mode catalogs. |
149
+ | **`getAiTasksTaskStrategiesCatalogSnapshot(catalox, context?, query?)`** | **All seven** task-strategy catalogs in one round-trip (`pre`, `post`, `input`, `executionStrategy`, `execution`, `mainExecutionWrappers`, `narrixModes`). |
150
+
151
+ Raw rows (full `UnifiedCatalogItem`) if you need them:
152
+
153
+ ```typescript
154
+ import {
155
+ createCataloxFromEnv,
156
+ defaultAiTasksCataloxContext,
157
+ AI_TASK_PRE_STRATEGIES_CATALOG_ID,
158
+ } from "@exellix/ai-tasks";
159
+
160
+ const catalox = createCataloxFromEnv();
161
+ const ctx = defaultAiTasksCataloxContext();
162
+ const { listOutcome, items } = await catalox.listCatalogItems(
163
+ ctx,
164
+ AI_TASK_PRE_STRATEGIES_CATALOG_ID,
165
+ { limit: 100 }
166
+ );
167
+ ```
168
+
169
+ ### Skill templates (`ai-skills` app)
170
+
171
+ Skill definitions and template bodies for execution live under Catalox **`appId: ai-skills`** (owned by **`@woroces/ai-skills`**). This package re-exports the Catalox skill surface from **`@woroces/ai-skills`** via the same entrypoint (e.g. `listAiSkillsCatalogItems`, `fetchSkillTemplatesFromCatalox`, `createCataloxFromEnv`, `initFirebaseAdminFromEnv`, …) so you can **view and edit skill catalog items** while depending only on **`@exellix/ai-tasks`**.
172
+
173
+ ---
174
+
175
+ ## Usage
176
+
177
+ ### Packaged skills (default client)
178
+
179
+ ```typescript
180
+ import {
181
+ createDefaultWorexClientTasks,
182
+ ExecutionType,
183
+ } from "@exellix/ai-tasks";
184
+
185
+ const client = createDefaultWorexClientTasks();
186
+ // Optional before first runTask if nx-content needs time to connect:
187
+ await client.whenReady({ timeoutMs: 60_000 });
188
+
189
+ const result = await client.runTask({
190
+ skillKey: "tasks/your-skill",
191
+ agentId: "my-agent",
192
+ jobTypeId: "my-job-type",
193
+ taskTypeId: "my-task-type",
194
+ executionStrategies: [],
195
+ executionType: ExecutionType.DIRECT,
196
+ input: { question: "..." },
197
+ jobContext: { graphRunId: "..." },
198
+ });
199
+
200
+ await client.dispose();
201
+ ```
202
+
203
+ ---
204
+
205
+ ## Utility tasks (non-question tasks)
206
+
207
+ ai-tasks supports **utility tasks**: tasks that run from **structured inputs** and **memory/context** to produce **structured, machine-consumable output**, without requiring a user-facing question.
208
+
209
+ - **Contract**: `RunTaskRequest.input` can be any JSON-compatible object (or string). **`input.question` is optional.**
210
+ - **Correlation (required):** every `runTask` request must include non-empty **`agentId`**, **`jobTypeId`**, and **`taskTypeId`** (same as `@exellix/ai-skills`). See [`RUNTASK_REQUEST.md`](RUNTASK_REQUEST.md) (including **Graph task node → RunTaskRequest** for authoring vs invoke).
211
+ - **When question matters**: Only skills/templates that *explicitly* reference `{{question}}` (or rely on question-driven synthesis) need a question. Utility tasks should not.
212
+ - **Output**: Utility tasks should typically return a structured `parsed` payload (for downstream graph nodes), not only prose in `rawText`.
213
+
214
+ Example (utility task with no question):
215
+
216
+ ```typescript
217
+ import { runTask } from "@exellix/ai-tasks";
218
+
219
+ const res = await runTask({
220
+ agentId: "my-agent",
221
+ jobTypeId: "prepare-context-job",
222
+ taskTypeId: "prepare-context-task",
223
+ skillKey: "skills/graph.prepareContext",
224
+ input: {
225
+ record: { /* ... */ },
226
+ policy: { /* ... */ },
227
+ },
228
+ executionMemory: {},
229
+ jobMemory: {},
230
+ graphId: "graph-123",
231
+ nodeId: "prepare-context",
232
+ });
233
+
234
+ console.log(res.parsed);
235
+ ```
236
+
237
+ Example (utility task as a local deterministic handler):
238
+
239
+ ```typescript
240
+ import { runTask } from "@exellix/ai-tasks";
241
+
242
+ const res = await runTask({
243
+ skillKey: "skills/skill.local:validateInput",
244
+ input: {
245
+ recordsPath: { $path: "executionMemory.inputs.records" },
246
+ rules: { requirePaths: ["id"] },
247
+ },
248
+ executionMemory: { inputs: { records: [{ id: "a" }] } },
249
+ });
250
+
251
+ console.log(res.parsed); // { ok: true, meta: ... } (example shape)
252
+ ```
253
+
254
+ ### Structured decision output (`response.parsed`) + optional schema enforcement
255
+
256
+ For decision/utility nodes, prefer returning a stable object payload in `response.parsed` so graph `outputMapping` can reference fields deterministically (no markdown parsing).
257
+
258
+ Example decision task (built-in local handler):
259
+
260
+ ```typescript
261
+ import { runTask } from "@exellix/ai-tasks";
262
+
263
+ const res = await runTask({
264
+ skillKey: "skills/graph.decide-web-scope",
265
+ input: { localEvidenceSufficient: true },
266
+ taskKind: "decision",
267
+ outputValidation: {
268
+ schema: {
269
+ type: "object",
270
+ required: ["contractVersion", "shouldUseWeb", "reasonCodes", "missingSignals"],
271
+ properties: {
272
+ contractVersion: { type: "string" },
273
+ shouldUseWeb: { type: "boolean" },
274
+ reasonCodes: { type: "array", items: { type: "string" } },
275
+ missingSignals: { type: "array", items: { type: "string" } },
276
+ },
277
+ additionalProperties: false,
278
+ },
279
+ mode: "fail",
280
+ },
281
+ });
282
+
283
+ // Stable structured artifact for deterministic graph mapping:
284
+ // - res.parsed.shouldUseWeb
285
+ // - res.parsed.reasonCodes
286
+ // - res.parsed.missingSignals
287
+ ```
288
+
289
+ `getPackagedAiTasksMetadataDir()` returns the absolute path to the shipped `.metadata` folder (override with `contentRegistryLocalPath` in client options). `forPackagedSkills` is an alias of `createDefaultWorexClientTasks`.
290
+
291
+ ### Class-Based API (custom `ai-skills` client)
292
+
293
+ ```typescript
294
+ import { WorexClientSkills } from "@exellix/ai-tasks"; // re-export, or import from @woroces/ai-skills
295
+ import { WorexClientTasks, ExecutionType } from "@exellix/ai-tasks";
296
+
297
+ // skills client is the shared execution + helper layer
298
+ const skills = new WorexClientSkills({
299
+ // ... your existing ai-skills config (gateway/provider/router)
300
+ });
301
+
302
+ // Get executor from skills client (no-enrichment execute primitive)
303
+ const executor = skills.getExecutor();
304
+
305
+ // tasks client orchestrates task-level execution
306
+ const tasks = new WorexClientTasks(skills, executor);
307
+
308
+ const res = await tasks.runTask({
309
+ skillKey: "tasks/security-risk-summary",
310
+ // executionType is optional, defaults to ExecutionType.DIRECT
311
+ input: { assetId: "a-123", windowDays: 30 },
312
+
313
+ // optional
314
+ variables: { orgName: "Acme" },
315
+ modelConfig: {
316
+ model: "gpt-5",
317
+ temperature: 0.7,
318
+ maxTokens: 2000
319
+ },
320
+ jobId: "job-1",
321
+ agentId: "agent-1",
322
+ });
323
+
324
+ console.log(res.rawContent);
325
+ console.log(res.parsed); // if your pipeline supports parsed output
326
+ ```
327
+
328
+ ### Function-Based API (Convenience)
329
+
330
+ ```typescript
331
+ import { runTask, ExecutionType } from "@exellix/ai-tasks";
332
+
333
+ // Automatically initializes SDK (ERC mode)
334
+ // executionType is optional, defaults to ExecutionType.DIRECT
335
+ const res = await runTask({
336
+ skillKey: "tasks/security-risk-summary",
337
+ agentId: "agent-1",
338
+ jobTypeId: "security-job-type",
339
+ taskTypeId: "security-task-type",
340
+ executionStrategies: [],
341
+ input: { assetId: "a-123" },
342
+ jobId: "job-1",
343
+ });
344
+ ```
345
+
346
+ ---
347
+
348
+ ## RunTask Algorithm (Canonical)
349
+
350
+ Given a request, `runTask()` performs:
351
+
352
+ 1. **Validate**: Non-empty **`agentId`**, **`jobTypeId`**, **`taskTypeId`**; required **`executionStrategies`** array (use **`[]`** for plain MAIN). When **`smartInput`** is present, validate shape (**`paths`**: array of **non-empty strings** *or* **`{ title, path, required? }`** entries; reject `{}`, non-arrays, invalid elements, unknown **root** keys—only **`paths`** is allowed). Paths under **`xynthesized.*`** must use scope **`job`**, **`task`**, or **`execution`**.
353
+ 1b. **Compile `taskConfiguration`** (when **`RunTaskRequest.taskConfiguration`** is set): map **`aiTaskStrategies.pre: "synthesis"`** and/or **`aiTaskProfile.inputSynthesis.enabled`** into **`executionPipeline`** PRE **`synthesized-context`** + **`includeContextInPrompt: true`**. Strip **`taskConfiguration`** before execution. See **`compileTaskConfigurationOnRunTaskRequest`**. **Graph-engine** must forward the node blob and wire runtime input — [`reports/graph-engine-task-pre-synthesis-compile.md`](reports/graph-engine-task-pre-synthesis-compile.md).
354
+ 2. **NARRIX pre-processor** (if `request.narrix` is set): Resolve raw record (see [NARRIX task-level pre-processor](#narrix-task-level-pre-processor)), run NARRIX (to-CNI + engine), build `_narrix` attachment, set `executionMemory[attachToField]` and `jobMemory._narrix`. Continue with the updated request.
355
+ 3. **Structured Narrix** (`narrixMode: "handler"`): Resolve **`narrixInput`**, run handler, merge into **`taskMemory.narrix`** / **`input`** as implemented; handler **`ctx`** includes **`xynthesized`** and **`smartInput`**.
356
+ 4. **Local task dispatch**: If `getLocalTask(skillKey)` returns a handler, call `{ input, ctx }`. **`ctx`** includes **`skillKey`**, **`jobMemory`**, **`taskMemory`**, **`executionMemory`**, **`variables`**, **`xynthesized`**, **`smartInput`**, and graph/correlation ids. Returns **`RunTaskResponse`**; **skips LLM MAIN path below**.
357
+ 5. **LLM path** (pipeline or default MAIN): If **`executionPipeline`** is non-empty, run PRE (**`synthesized-context`** only), then MAIN, then optional POST (**`audit`** / **`polish`**). PRE synthesis may write **`request.xynthesized`** and **`response.xynthesizedPatch`** ([details](#execution-pipeline-and-synthesized-context-pre-step)). Otherwise behave as a single MAIN **`direct`** step.
358
+ 6. **MAIN execution** (inside pipeline MAIN or default path): Build **`memoryBundle = { jobMemory, taskMemory, executionMemory }`**, **`enrichMemoriesWithScoping(skillKey, 'task', bundle)`**, generate **`context`** when **`includeContextInPrompt`** (or pipeline synthesis overrides context). Optionally run **`aiScoping`** into **`input.aiScoped`**. Build **`enrichedInput`**: memories, **`context`**, **`input`**, top-level **`xynthesized`** and **`smartInput`**, and **`variables` = passthroughJobTemplateVariables(request.variables)` (job bucket only; see [Gateway template rendering](#gateway-template-rendering-v4)).
359
+ 7. **Execute MAIN**: Apply **`executionStrategies`** (planner/optimizer FuncX wrappers when non-empty) and call **`runSkill`** / gateway via the configured executor.
360
+ 8. **Finalize response**: Identity / task metadata as today; attach **`xynthesizedPatch`** when PRE synthesis produced **`job`**, **`task`**, or **`execution`** writes; when the executor returns **`SmartInputRenderResult`**, lift it to **`response.smartInputRenderResult`** and **`metadata.smartInputRenderResult`** (also accepts shaped **`metadata.smartInput`** from downstream).
361
+ 9. **Not-found**: Executor may invoke registry diagnostics when content is missing.
362
+
363
+ ### Trace mode (authoritative ordered debug trace)
364
+
365
+ Set `executionMode: "trace"` to opt in to an authoritative, ordered per-step execution trace. When enabled, the response includes:
366
+
367
+ - `debugTrace.tasks: DebugTraceTask[]`
368
+
369
+ Each entry represents a real unit of work that executed (pre steps, main skills/gateway call, post steps) and includes:
370
+
371
+ - `taskType`: `"pre-execution" | "ai-task" | "post-execution"`
372
+ - `details`: short human string
373
+ - `modelUsed`: string for LLM-backed steps; `null` for deterministic steps
374
+ - `metadata.timing`: `{ startedAt, endedAt, durationMs }`
375
+ - `metadata.step`: `{ phase, type, stepId }` (when applicable)
376
+
377
+ When upstream providers expose them (via `@exellix/xynthesis` / `@exellix/ai-skills` trace surfaces), trace tasks may also include usage/routing/cost under stable `metadata` keys.
378
+
379
+ **Graph / smart-input hints (ai-tasks–specific):** Trace entries for MAIN **direct** execution include **`metadata.smartInput: { paths }`** (echo of **`RunTaskRequest.smartInput.paths`** after normalization—**`{ title, path }[]`**, or empty array). PRE **`synthesized-context`** entries may include **`metadata.xynthesized`** (`inputPathsUsed`, `outputDestination`, `outputKey`, `patchKeys`) when **`SynthesisConfig.xynthesizedOutput`** is set—keys only, not full synthesized payloads.
380
+
381
+ ---
382
+
383
+ ## Xynthesized memory and smart input
384
+
385
+ Graph runtimes need structured execution context that is **not** the same as raw **`jobMemory` / `taskMemory` / `executionMemory`**: material synthesized once at job scope, scoped to a single node, or held in a run-wide **execution** bucket. **`RunTaskRequest`** supports:
386
+
387
+ | Field | Purpose |
388
+ |-------|---------|
389
+ | **`xynthesized?: { job?, task?, execution? }`** | **`job`** — synthesized context reusable across nodes in the same graph run (graph-engine merges patches from each **`runTask`**). **`task`** — scoped to this task/node invocation only. **`execution`** — run-wide execution bucket (distinct from job/task). Values are **`Record<string, unknown>`** buckets keyed by your **`outputKey`** (and any caller-supplied keys). |
390
+ | **`smartInput?: RunTaskSmartInput`** | Declares smart-input paths for Rendrix **`{{smartInput}}`** (via **`@exellix/ai-skills`** / gateway). Use Rendrix-native **`paths: { title, path, required? }[]`**, or legacy **`paths: string[]`** (normalized before **`runSkill`**). Paths under **`xynthesized.*`** must use **`job`**, **`task`**, or **`execution`** (e.g. **`xynthesized.execution.priorAnalysis`**). The **`smartInput`** object allows only the **`paths`** property at the root. Optional **`smartInputRenderOptions`** on **`RunTaskRequest`** controls markdown folding for the macro (same merge rules as **`RunSkillRequest`**). Full dot-path resolution against live memory happens downstream (Rendrix / gateway). |
391
+
392
+ ### Validation (`smartInput`)
393
+
394
+ If **`smartInput`** is present, it must be a plain object with **`paths`** (array of non-empty strings or **`{ title, path, required? }`**) and optional **`strict`** (boolean). Invalid: **`smartInput: {}`**, bad **`paths`**, extra root keys, or **`xynthesized.<scope>.*`** with **`<scope>`** not **`job`**, **`task`**, or **`execution`**. Prefer path prefixes **`jobVariables.*`** and **`taskVariables.*`** (legacy **`variables.*`** ≡ job scope when the engine resolves paths). **`{ paths: [] }`** is valid.
395
+
396
+ At **`runTask()`** entry, invalid shape throws **`SmartInputValidationError`** (`error.code`, `error.details`, `error.skillKey`) — the task does not run (no NARRIX, no PRE xynthesis, no MAIN). Omitted **`smartInput`** is allowed.
397
+
398
+ ### Pre-flight validation (no LLM invoke)
399
+
400
+ Use these helpers **before** `runTask()` to inspect config and payload without calling xynthesis or the gateway:
401
+
402
+ | Function | Purpose |
403
+ |----------|---------|
404
+ | **`validateRunTaskConfig(request)`** | Static checks: `agentId` / `jobTypeId` / `taskTypeId`, `executionStrategies`, `smartInput` shape, `modelConfig` / `llmCall` numeric fields, `executionPipeline` (one MAIN, synthesis PRE context flag). Returns `{ ok, issues, errors, warnings }`. |
405
+ | **`validateRunTaskInvoke({ request, templateResolver?, ... })`** | Config checks **plus** whether expected paths resolve on the request (`input`, memories, `xynthesized`). Uses Rendrix `renderSmartInput` for required smart-input paths and optional `analyzeTemplateResolution` on raw templates. |
406
+ | **`analyzeExpectedRunTaskInput({ skillKey, smartInput?, instructions?, prompt?, templateResolver? })`** | Returns expected dot-paths from **`smartInput.paths`** and path tokens in skill templates (Rendrix **`listTokens`**). |
407
+
408
+ ```typescript
409
+ import {
410
+ validateRunTaskConfig,
411
+ validateRunTaskInvoke,
412
+ analyzeExpectedRunTaskInput,
413
+ SmartInputValidationError,
414
+ isSmartInputValidationError,
415
+ } from "@exellix/ai-tasks";
416
+
417
+ // Config only (fast)
418
+ const configCheck = validateRunTaskConfig(runTaskRequest);
419
+ if (!configCheck.ok) {
420
+ for (const issue of configCheck.errors) {
421
+ console.error(issue.code, issue.path, issue.message);
422
+ }
423
+ }
424
+
425
+ // Config + payload + templates
426
+ const invokeCheck = await validateRunTaskInvoke({
427
+ request: runTaskRequest,
428
+ templateResolver: {
429
+ async resolveRawTemplate(skillKey, section) {
430
+ const r = await skillsClient.resolveRawTemplate(skillKey, section);
431
+ return r?.found ? r.content : undefined;
432
+ },
433
+ },
434
+ checkTemplateResolution: true,
435
+ });
436
+ if (!invokeCheck.ok) {
437
+ console.error(invokeCheck.errors);
438
+ }
439
+
440
+ // What paths does this node need?
441
+ const expected = await analyzeExpectedRunTaskInput({
442
+ skillKey: runTaskRequest.skillKey,
443
+ smartInput: runTaskRequest.smartInput,
444
+ prompt: "...",
445
+ });
446
+ ```
447
+
448
+ Each issue has **`code`**, **`severity`** (`error` \| `warning`), **`message`**, and optional **`path`** (e.g. `smartInput.paths[0].path`, `llmCall.maxTokensCap`). See [`.docs/flow-io/`](.docs/flow-io/flow-README.md) for flow-level wiring.
449
+
450
+ ### Skill request analysis (`@exellix/ai-skills` 5.6+)
451
+
452
+ **`@exellix/ai-tasks`** re-exports the Catalox-backed analysis and template-catalog helpers from **`@exellix/ai-skills`** so apps can depend on a single package:
453
+
454
+ | Export | Purpose |
455
+ |--------|---------|
456
+ | **`analyzeSkillRequest`**, **`buildSkillRequestAnalysisPacket`**, **`formatSkillRequestAnalysisMarkdown`** | Deterministic preflight for a flat **`RunSkillRequest`** (templates, smart input, output contract, gateway packet preview). |
457
+ | **`analyzeRunTaskRequest(catalox, request, options?)`** | Same analysis on **`pickRunSkillRequestFields(request)`**, plus optional merge of **`validateRunTaskConfig`** findings (`includeAiTasksConfigValidation`, default `true`). |
458
+ | **`extractTokenNamesFromStrings`**, **`getSkillTokens`**, **`getSkillContent`**, **`modifySkillContent`**, **`createSubSkill`**, **`deleteSubSkill`**, **`SKILL_TEMPLATE_ROLES_ORDER`** | Template catalog introspection and edits (Catalox). |
459
+ | **`runSkillRequestFromFlat`**, **`resolveAiEngineIdForRunSkill`**, **`SkillExecutionTraceError`**, trace helpers | Flat skill execution and trace utilities. |
460
+
461
+ Rendrix helpers used by validation are also re-exported: **`listTokens`**, **`analyzeTemplateResolution`**, **`renderSmartInput`**, **`SmartInputInvalidError`**, **`TemplateResolutionError`**, and related types.
462
+
463
+ ```typescript
464
+ import {
465
+ analyzeRunTaskRequest,
466
+ formatSkillRequestAnalysisMarkdown,
467
+ buildSkillRequestAnalysisPacket,
468
+ } from "@exellix/ai-tasks";
469
+
470
+ const analysis = await analyzeRunTaskRequest(catalox, runTaskRequest, {
471
+ logger,
472
+ includeAiTasksConfigValidation: true,
473
+ });
474
+ console.log(formatSkillRequestAnalysisMarkdown(analysis.report));
475
+ ```
476
+
477
+ ### Runtime surfaces
478
+
479
+ - **Narrix preprocessor / Narrix handler `ctx`**: Same **`xynthesized`** / **`smartInput`** as on the **`runTask`** request after normalization (pipeline PRE runs later and can mutate **`xynthesized`** before MAIN).
480
+ - **PRE `synthesized-context`**: The bundle passed into template/rendering includes **`xynthesized`** and **`smartInput`** so synthesis prompts can reference them consistently with MAIN.
481
+ - **MAIN executor payload**: Top-level **`xynthesized`**, **`smartInput`** (normalized), and **`smartInputRenderOptions`** (when set) are forwarded on the object passed to **`runSkill`** (alongside memories and **`context`**).
482
+ - **Template variables**: **`variables`** (job bucket) and **`executionMemory.jobVariables`** / **`executionMemory.taskVariables`** are forwarded separately; use **`{{xynthesized…}}`**, **`{{smartInput}}`**, **`jobVariables.*`**, and **`taskVariables.*`** in templates (not a single flattened bag).
483
+ - **Response**: When downstream returns a **`SmartInputRenderResult`**, **`runTask`** **`finalize`** exposes **`response.smartInputRenderResult`** and mirrors **`metadata.smartInputRenderResult`** (also reads **`metadata.smartInput`** when it matches that shape).
484
+
485
+ ### PRE synthesis destination (`SynthesisConfig.xynthesizedOutput`)
486
+
487
+ Optional on the **`synthesized-context`** step **`config`**:
488
+
489
+ - **`destination`**: **`"job"`** | **`"task"`** | **`"execution"`** — writes under **`request.xynthesized.job[outputKey]`**, **`.task[outputKey]`**, or **`.execution[outputKey]`** (no redirect to other scopes).
490
+ - **`outputKey`**: string key for the synthesized value.
491
+ - **`mode`**: **`"replace"`** (default) or **`"merge"`** — for merge, if the existing value at **`outputKey`** and the new value are both plain objects, ai-tasks **deep-merges** them; otherwise the new value replaces.
492
+ - **`alsoWriteLegacySynthesizedContext`**: When **`true`** or omitted, keep today’s behavior: persist structured/markdown artifacts on **`executionMemory.synthesizedContext`** (and **`synthesizedInput`** when applicable) for MAIN. When **`false`**, skip that legacy persistence for this PRE step; MAIN still receives synthesized **context markdown** from the PRE step when produced.
493
+
494
+ **Stored value:** Prefer the full synthesis **`artifact`** when present (structured / question-driven / markdown artifact shape). If there is no artifact, ai-tasks stores **`{ contextMarkdown }`** using the PRE step’s markdown string.
495
+
496
+ **Response:** **`RunTaskResponse.xynthesizedPatch`** has the same **`{ job?, task?, execution? }`** shape so the graph-engine can merge into durable graph memory. Omitted when nothing was written.
497
+
498
+ **Execution-scope example (PRE + smart-input):**
499
+
500
+ ```typescript
501
+ executionPipeline: [
502
+ {
503
+ phase: "pre",
504
+ type: "synthesized-context",
505
+ config: {
506
+ xynthesizedOutput: { destination: "execution", outputKey: "priorAnalysis" },
507
+ },
508
+ },
509
+ { phase: "main", type: "direct" },
510
+ ],
511
+ smartInput: { paths: ["xynthesized.execution.priorAnalysis"] },
512
+ // response.xynthesizedPatch.execution.priorAnalysis → graph-engine merges into executionMemory.xynthesized.execution
513
+ ```
514
+
515
+ ### Builder
516
+
517
+ **`TaskRequestBuilder`**: **`withXynthesized`**, **`withSmartInput`**, **`withSmartInputPaths`** (builds native **`{ title, path }`** entries), **`withSmartInputRenderOptions`**, **`withXynthesizedJob`**, **`withXynthesizedTask`**, **`withXynthesizedExecution`**.
518
+
519
+ ### What ai-tasks does **not** do
520
+
521
+ Does **not** choose graph mappings, apply **`outputMapping`**, run the full graph, or own long-lived graph state—that belongs to the graph runtime. It **allowlists** **`xynthesized.*`** smart-input scopes (**`job`**, **`task`**, **`execution`**) but does **not** render **`{{smartInput}}`** or resolve paths against live memory (Rendrix / gateway).
522
+
523
+ ---
524
+
525
+ ## Local Tasks
526
+
527
+ Local tasks are **deterministic handlers** (no LLM) that run inside `runTask()` before any memory enrichment or executor call. They are identified by **skillKey**. If a handler is registered for that `skillKey`, it runs with the task input and context; the return value is wrapped into a standard `RunTaskResponse` (e.g. `parsed`, `metadata.durationMs`, `metadata.localSkillKey`).
528
+
529
+ ### Built-in local tasks
530
+
531
+ | Skill key | Purpose |
532
+ |--------|--------|
533
+ | `skills/node.callExport` | Dynamically import a module, resolve an export (default or named), and call it. Supports `$path`-based argument resolution (e.g. `input.payload`, `jobMemory.foo`), optional job/global caching for instances. |
534
+ | `skills/node.callExportBatch` | Same module/export/cache semantics; creates or reuses a cached instance, then calls a **batch** method (e.g. `enrichJson`) with payload/options. Returns `{ ok, value, meta: { processed, errors, durationMs } }`. |
535
+ | `skills/skill.local:validateInput` | Validate records at a `$path` (e.g. `executionMemory.inputs.vulnInstances.records`). Rules: `requirePaths`, `graphTypeEquals`. No mutation; returns `{ ok, meta: { total, valid, invalid } }` or `{ ok: false, errors }`. |
536
+ | `skills/skill.local:normalizeNarrixResult` | Normalize records to a stable “Narrix attachment” shape: ensure `_narrix.meta.entity.entityKey`, `_narrix.scoping` / `_narrix.discovery` and their arrays, `joinCandidates`. Returns `{ ok: true, value: { records } }`. |
537
+ | `skills/skill.local:narrixRun` | Run one input through the Narrix pipeline (ingest → runner) for **record**, **text**, **docs**, or **chat**. Unified input: `medium` (`"record"` \| `"text"` \| `"docs"` \| `"chat"`) + `datasetId` + medium-specific payload. Returns `{ ok: true, entity, signals, stories, passes?, meta }` or `{ ok: false, error, message?, meta? }`. Legacy record shape (no `medium`) still supported. |
538
+ | `skills/graph.collectEvidence` | **Opt-in reference local handler**: collect evidence from structured requests (queries and/or URLs) and return a reusable **EvidenceBundle** for downstream graph nodes. No question required. |
539
+
540
+ ---
541
+
542
+ ## Generic evidence collection (`skills/graph.collectEvidence`)
543
+
544
+ ai-tasks standardizes a **generic, domain-agnostic** evidence collection contract for staged graph execution. This is **independent of NARRIX** question-driven web scoping: callers provide structured requests; the result is a reusable `EvidenceBundle` suitable for storing in `executionMemory` and reusing downstream.
545
+
546
+ - **Must-have**: a stable input/output contract graphs can depend on cleanly.
547
+ - **Reference handler**: ai-tasks ships an **opt-in local task** (`skills/graph.collectEvidence`) that implements the contract to reduce integration friction. Retrieval is **not** implicit; graphs opt in by selecting this `skillKey`.
548
+ - **Backends**: the reference handler may reuse existing internal search/fetch components, but the **public contract does not expose NARRIX semantics** (`narrix`, `webContext`, web-scope templates, etc.).
549
+ - **Types**: see `EvidenceCollectionInput` / `EvidenceBundle` exported from `@exellix/ai-tasks` (implemented in `src/types/evidence-types.ts`).
550
+ - **Current reference implementation notes**:
551
+ - Supports **query-driven discovery** and **direct URL fetch**.
552
+ - Enforces policy limits like `maxRequests`, `maxSourcesPerRequest`, `maxTotalSources`, domain allow/block lists, and snippet size caps.
553
+ - Returns `value.sources[]` + per-request `value.requests[]`. If `extraction.returnFacts` / `extraction.returnSummary` are set, the reference handler currently returns empty `facts: []` / `summary: ""` (placeholders for future variants).
554
+
555
+ Recommended graph pattern:
556
+
557
+ ```typescript
558
+ const evidenceRes = await runTask({
559
+ skillKey: "skills/graph.collectEvidence",
560
+ input: {
561
+ requests: [
562
+ { kind: "vendor_guidance", query: "ExampleProduct security advisory" },
563
+ { kind: "exploitation_status", url: "https://example.com/advisory" },
564
+ ],
565
+ policy: { maxTotalSources: 10, includeSnippets: true },
566
+ },
567
+ executionMemory,
568
+ jobMemory,
569
+ graphId,
570
+ nodeId,
571
+ });
572
+
573
+ // Store once, reuse later:
574
+ executionMemory.evidence = evidenceRes.parsed; // EvidenceBundle
575
+ ```
576
+
577
+ ### node.callExport (input contract)
578
+
579
+ - `module`: string (module name or path for dynamic `import()`)
580
+ - `export`: string (default `"default"`) — export name
581
+ - `call`: `{ method: string, args?: any[] }` — `method` can be `"function"` (call export as function), `"create"` (call and optionally cache), or any other string (call as method on cached/created instance)
582
+ - `cache`: optional `{ scope: "global" | "job", key: string }`
583
+ - Args may use `{ $path: "input.x" }` (or `jobMemory.*`, `taskMemory.*`, `executionMemory.*`, `variables.*`); resolved via dot-path before calling.
584
+
585
+ ### node.callExportBatch (input contract)
586
+
587
+ - `module`, `export`, `cache`: same as above
588
+ - `call.create`: `{ method, args? }` — used to create/cache the instance
589
+ - `call.batch`: `{ method, args? }` — method and args for the batch call (args can use `$path`)
590
+ - `payload`, `options`: typically passed via `$path` in `call.batch.args` (e.g. `input.payload`, `input.options`)
591
+
592
+ ### skills/skill.local:validateInput (input contract)
593
+
594
+ - `recordsPath`: `{ $path: "..." }` — path to records array (e.g. `executionMemory.inputs.vulnInstances.records`)
595
+ - `rules.requirePaths`: string[] — dot paths that must exist on each record
596
+ - `rules.graphTypeEquals`: optional string — require `graphized.meta.graphType` to equal this value
597
+ - `datasetId`: optional (for validation rules)
598
+
599
+ ### skills/skill.local:normalizeNarrixResult (input contract)
600
+
601
+ - `recordsPath`: `{ $path: "..." }` — path to records array
602
+ - `attachRoot`: string (default `"_narrix"`) — key under which Narrix attachment lives
603
+ - `defaults.scopingPath`, `defaults.discoveryPath`: optional paths under attachRoot (default `"scoping"`, `"discovery"`)
604
+
605
+ ### skills/skill.local:narrixRun (input contract)
606
+
607
+ One handler supports **four media**: record, text, docs, chat. Send a **unified input** with `medium` and `datasetId`, plus the payload for that medium. For full object shapes and use cases, see [NARRIX-FOUR-OBJECTS-AND-USE-CASES.md](./NARRIX-FOUR-OBJECTS-AND-USE-CASES.md).
608
+
609
+ **Unified input (recommended):**
610
+
611
+ - **Record:** `{ medium: "record", datasetId: string, record: Record<string, unknown> }`
612
+ - **Text:** `{ medium: "text", datasetId: string, text: string, meta?: object }`
613
+ - **Docs:** `{ medium: "docs", datasetId: string, document: { pages: Array<{ text, pageId?, pageNumber?, ... }>, docId?, title? } }`
614
+ - **Chat:** `{ medium: "chat", datasetId: string, thread: { messages: Array<{ role, text, ... }>, threadId?, title?, participants? } }`
615
+
616
+ **Success response:** `{ ok: true, entity, signals, stories, passes?, meta }` — `meta` includes `datasetId`, `packId`, `entityKind` (and run metadata).
617
+ **Failure response:** `{ ok: false, error: string, message?: string, meta?: object }` — e.g. `NARRIX_DISABLED`, `NARRIX_INVALID_INPUT`, `RUNNER_PACK_NOT_FOUND`, or ingest/runner error codes.
618
+
619
+ **Feature flag:** Set **`USE_NARRIX_INGEST=1`** to enable the Narrix pipeline (use **`npm run test:with-narrix-ingest`** on Windows instead of prefixing the command). When unset, the handler returns `{ ok: false, error: "NARRIX_DISABLED" }` without calling ingest or runner.
620
+
621
+ **Debug logging:** Set `NARRIX_DEBUG=1` to log datasetId, medium, packId, entityKind, signal codes, and narrativeTypeIds on success (default: quiet).
622
+
623
+ See [NARRIX-STATUS-REPORT.md](./NARRIX-STATUS-REPORT.md) for implementation details and test layout.
624
+
625
+ ### Custom local tasks
626
+
627
+ You can register your own handlers so they run when `skillKey` matches the registered key:
628
+
629
+ ```typescript
630
+ import {
631
+ registerLocalTask,
632
+ getLocalTask,
633
+ type LocalTaskContext,
634
+ type LocalTaskHandler,
635
+ } from "@exellix/ai-tasks";
636
+
637
+ const myHandler: LocalTaskHandler = async ({ input, ctx }) => {
638
+ // ctx: skillKey, jobMemory, taskMemory, executionMemory, variables, jobId, agentId, graphId, nodeId, ...
639
+ return { ok: true, value: input.someField };
640
+ };
641
+
642
+ registerLocalTask("skills/my.custom.task", myHandler);
643
+
644
+ // Later: runTask({ skillKey: "skills/my.custom.task", input: { someField: 1 } }) will run myHandler and skip enrichment/executor
645
+ ```
646
+
647
+ - **`registerLocalTask(skillKey: string, handler: LocalTaskHandler): void`** — register a handler for `skillKey`
648
+ - **`getLocalTask(skillKey: string): LocalTaskHandler | undefined`** — get handler (used internally by `runTask`)
649
+ - **`LocalTaskContext`** — `skillKey`, `jobMemory`, `taskMemory`, `executionMemory`, `variables`, `xynthesized`, `smartInput`, `jobId`, `taskId`, `agentId`, `graphId`, `nodeId`, `prevNodeId`, `coreSkillId`, `masterSkillId`, `masterSkillActivityId`
650
+ - **`LocalTaskHandler`** — `(args: { input: any; ctx: LocalTaskContext }) => Promise<any>`
651
+
652
+ ### Execution tracing (metadata)
653
+
654
+ For graph runtimes (e.g. worex-graph), local task responses include consistent metadata so a trace can be stored per node:
655
+
656
+ - **`metadata.durationMs`** — time taken by the handler
657
+ - **`metadata.localSkillKey`** — `skillKey` that was run (e.g. `"skills/node.callExportBatch"`, `"skills/skill.local:validateInput"`)
658
+
659
+ Downstream can store e.g. `executionMemory._trace.nodes[nodeId]` with `taskKey`, `ok`, `durationMs`, `summary` (from `parsed.meta` when present).
660
+
661
+ **Exported API for local tasks:** `registerLocalTask`, `getLocalTask`, `registerBuiltInLocalTasks`, `LocalTaskContext`, `LocalTaskHandler` (see [API Reference](#api-reference) and Types).
662
+
663
+ ---
664
+
665
+ ## ExecutionType
666
+
667
+ `executionType` is **optional** on every request and defaults to `DIRECT` if not provided.
668
+
669
+ | Way | How you trigger it | What runs |
670
+ |-----|--------------------|-----------|
671
+ | **NARRIX pre-processor** | `request.narrix` set (e.g. from graph node metadata) | Resolve record → run NARRIX → inject `_narrix` into executionMemory/jobMemory; then run local task or DIRECT as below. |
672
+ | **Local task** | `skillKey` = local skill key (e.g. `skills/skill.local:narrixRun`) | Handler only; no enrichment, no LLM. |
673
+ | **Executor only** | Any other `skillKey`, `executionType` omitted or `"direct"` | Enrich → context → executor/LLM. No Narrix. |
674
+ | **Narrix then execute** | Same as executor, but `executionType: "narrix-then-direct"` + `narrixInput` | Narrix → append to `taskMemory.narrix` → then enrich → context → executor/LLM. |
675
+
676
+ Currently supported:
677
+
678
+ - `DIRECT` — single execution call after task-level enrichment/context (default)
679
+ - `NARRIX_THEN_DIRECT` (`"narrix-then-direct"`) — run Narrix first, inject result into task memory and context, then run the same DIRECT path; **requires** `narrixInput` (or resolution from job memory via `narrixInput.$path`). See [Narrix then execute](#narrix-then-execute) below for details.
680
+
681
+ Unsupported types throw an error with actionable context.
682
+
683
+ ---
684
+
685
+ ## Narrix then execute
686
+
687
+ Narrix processes **four input types** (record, text, docs, chat) and supports several **use cases** (local `skills/skill.local:narrixRun`, narrix-then-direct, normalize, validate). For the four input objects, their payload shapes, and detailed use cases, see **[NARRIX-FOUR-OBJECTS-AND-USE-CASES.md](./NARRIX-FOUR-OBJECTS-AND-USE-CASES.md)**.
688
+
689
+ Use **narrix-then-direct** when you want one `runTask()` call to scope data with Narrix (entity, signals, stories) and then run an LLM skill with that context—without calling `skills/skill.local:narrixRun` and then a second `runTask()` manually.
690
+
691
+ ### Request
692
+
693
+ Same as a normal executor call, plus:
694
+
695
+ - **`executionType`**: `"narrix-then-direct"` or use the exported constant `NARRIX_THEN_DIRECT`.
696
+ - **`narrixInput`** (required): Either:
697
+ - Full Narrix input: `{ medium, datasetId, record }` (or `text` / `document` / `thread` per medium). Same shape as the **skills/skill.local:narrixRun (input contract)** section elsewhere in this README.
698
+ - Or `{ $path: "jobMemory.currentRecord" }` (or any `jobMemory.*` dot-path) to resolve from `request.jobMemory`.
699
+ - **`narrixScope`** (optional): Filters which signals and stories from Narrix are kept in task memory. Shape:
700
+ - `includeSignals?: string[]` — keep only signals whose `code` is in this array.
701
+ - `excludeSignals?: string[]` — drop signals whose `code` is in this array (ignored when `includeSignals` is set).
702
+ - `includeStories?: string[]` — keep only stories whose `narrativeTypeId` is in this array.
703
+ - `excludeStories?: string[]` — drop stories whose `narrativeTypeId` is in this array (ignored when `includeStories` is set).
704
+ - When omitted, all signals and stories pass through unfiltered.
705
+ - Example: `narrixScope: { includeSignals: ["OPEN_EGRESS_DETECTED", "PUBLIC_IP_EXPOSED"], includeStories: ["subnet-risk-summary"] }` — the task only sees these specific signals and narratives from the full Narrix output.
706
+
707
+ ### Where Narrix output goes
708
+
709
+ - **Task memory:** `taskMemory.narrix` is an **array** of Narrix run outputs (`{ entity, signals, stories, meta }`). The executor and LLM receive this in the enriched memory and in the generated context (e.g. "## Narrix Scoping" section).
710
+ - **Convenience:** The latest run is also set on the skill input as `input.narrixContext` so prompt templates can reference it directly.
711
+ - **Response:** On success, `result.metadata.narrix` contains the last run's `{ entity, signals, stories, meta, durationMs }`.
712
+
713
+ ### Minimal example
714
+
715
+ ```typescript
716
+ import { runTask, NARRIX_THEN_DIRECT } from "@exellix/ai-tasks";
717
+
718
+ const result = await runTask({
719
+ skillKey: "tasks/security-risk-summary",
720
+ executionType: NARRIX_THEN_DIRECT,
721
+ narrixInput: {
722
+ medium: "record",
723
+ datasetId: "subnetEgress",
724
+ record: { graphized: { id: "subnet-1", cidr: "10.0.0.0/24" } },
725
+ },
726
+ input: { question: "Summarize the security posture of this subnet" },
727
+ jobId: "job-1",
728
+ agentId: "agent-1",
729
+ });
730
+
731
+ // result.parsed → LLM output (from the skill)
732
+ // result.metadata.narrix → { entity, signals, stories, meta, durationMs } from Narrix
733
+ ```
734
+
735
+ ### Failure behavior
736
+
737
+ If Narrix fails (e.g. `ok: false`, or `NARRIX_DISABLED` when `USE_NARRIX_INGEST` is not set), the function returns early with `parsed: { ok: false, phase: "narrix", error, message }` and `metadata.narrix`; the executor/LLM is **not** called.
738
+
739
+ ### Export
740
+
741
+ `NARRIX_THEN_DIRECT` is exported from `@exellix/ai-tasks` for use in `executionType`.
742
+
743
+ ---
744
+
745
+ ## NARRIX task-level pre-processor
746
+
747
+ When a task request includes a **`narrix`** config (e.g. from graph node **`taskConfiguration.narrix`**), ai-tasks runs the NARRIX enrichment pipeline **before** executing the task and injects the result so the task handler (or LLM) can use it. This supports "narrix serving graph": graph nodes are domain tasks (e.g. assess reachability, assess posture); NARRIX is configuration on the node that enriches context, not a separate pipeline exposed as graph steps.
748
+
749
+ ### Request
750
+
751
+ Add optional **`narrix`** to `RunTaskRequest`:
752
+
753
+ - **`narrix.datasetId`** (required): e.g. `"network.egress.subnets"`, `"network.vuln.instances"`.
754
+ - **`narrix.attachToField`** (optional): field name on `executionMemory` for the attachment; default `"_narrix"`.
755
+ - **`narrix.engineConfigPath`**, **`narrix.packsRoot`**, **`narrix.deterministicSort`**, **`narrix.assumptionsPolicy`**: reserved for future use.
756
+ - **`narrix.enableWebScope`** (optional): when **`true`**, after a successful NARRIX run the SDK calls **`@exellix/narrix-web-scoper`** and stores the result on **`executionMemory.webContext`** (always set when enabled, including miss/error shapes). Requires **`TAVILY_API_KEY`**. Default off.
757
+ - **`narrix.webScopeTemplates`** (optional): non-empty array → passed to the scoper as template-driven queries (athenix-parser tokens); replaces default query planning when set.
758
+ - **`narrix.webScopeQuestionTemplate`** (optional): Handlebars template for the search question when `webScopeTemplates` is not set.
759
+ - **`narrix.webScopeObjects`** (optional): merged into template context for `webScopeTemplates` / Handlebars.
760
+ - **`narrix.webScopeQuestions`** (optional): explicit list of web-scope questions when `webScopeTemplates` is not set; uses `scopeQuestionPack` in `@exellix/narrix-web-scoper`. Each entry: `{ id?: string; question: string; source?: "manual" | "ai-driven" }` (see [documenations/web-scoping-in-ai-tasks.md](./documenations/web-scoping-in-ai-tasks.md)).
761
+ - **`narrix.webScoping`** (optional): per-request slice of **`WebScoperConfig.scoping`** from **`@exellix/narrix-web-scoper`** (snippets, caps, raw body, `maxQueries`, `freshnessDays`, etc.). Forwarded into the scoper so behavior matches upstream releases. Omit or use `{}` for package defaults.
762
+
763
+ ### Web scoping and upstream packages
764
+
765
+ Web scoping is implemented by **`@exellix/narrix-web-scoper`** (planning + **`WebContext`** mapping) and **`@exellix/search-adapter`** (Tavily normalization into **`SearchSource`** fields). This repo does not map provider JSON; it wires **`ScopeInput`**, optional **`narrix.webScoping`**, and **`runWebScope()`**.
766
+
767
+ When **`narrix.webScoping.includeSourceSnippets`** is **`true`**, each **`WebContext.sources[]`** entry may include provider-layer text as documented in the web-scoper README: first-class **`providerContent`** / **`providerRawContent`**, legacy mirrors **`content`** / **`rawContent`**, **`snippet`**, **`snippetCharCount`**, provenance fields (**`contentOrigin`**, **`retrievalStage`**, **`contentSource`**, etc.). Defaults keep **`includeSourceSnippets`** off so those fields are omitted. Optional caps: **`maxSnippetCharsPerSource`** (Unicode cap on provider excerpts and the text chosen for **`snippet`**; also forwarded as **`snippetMaxChars`** on search requests when positive). **`maxTotalWebContextChars`** applies **only** to **`WebSource.snippet`** across sources (after per-source caps)—it does not shrink stored **`providerContent`** / **`providerRawContent`**. To request raw body text from the provider, set **`narrix.webScoping.snippetIncludeRawContent`** (e.g. **`true`** or **`"markdown"`**); it is forwarded as **`includeRawContent`** on search requests. **`sourceExcerptFrom`** selects which provider field feeds **`snippet`** (**`providerContent`** default, or **`providerRawContent`**, with deprecated aliases **`content`** / **`rawContent`**). Use **`@exellix/search-adapter`** and **`@exellix/narrix-web-scoper`** versions from the same release family as this package’s dependencies.
768
+
769
+ **Downstream LLM context:** On the DIRECT path with **`includeContextInPrompt`**, when Narrix output is in play, ai-tasks appends a markdown section from **`executionMemory.webContext`** (when `available: true`) so the model sees source bodies, not only NARRIX signal codes. The **`synthesized-context`** PRE step uses the same markdown builder for policies that include web (e.g. **`narrix-web`**, **`narrix-web-memory`**, **`memory-web`**, **`auto`** when a web hit exists). Optional env **`WEB_CONTEXT_MARKDOWN_MAX_CHARS`** and optional **`SynthesisConfig.webEvidence`** cap and shape that markdown. Serialized memory for synthesis **never** embeds raw **`webContext`** as JSON; web appears as markdown only when the policy includes web.
770
+
771
+ Technical flow, failure behavior, and consumption patterns: **[documenations/web-scoping-in-ai-tasks.md](./documenations/web-scoping-in-ai-tasks.md)**.
772
+
773
+ ### Raw record resolution
774
+
775
+ The pre-processor needs a **raw record** to enrich. Resolution order:
776
+
777
+ 1. `executionMemory.input.raw`
778
+ 2. `executionMemory.inputs.<first key>.raw`
779
+ 3. `jobMemory.record` or `jobMemory.currentRecord`
780
+ 4. `input.record` or `input.raw`
781
+
782
+ If no record is found, `runTask` throws with a clear error. **sourceMeta** is taken from `executionMemory.input.sourceMeta` or `jobMemory.sourceMeta` or `{}`.
783
+
784
+ ### Where the result goes
785
+
786
+ - **Canonical:** `executionMemory[attachToField]` (default `executionMemory._narrix`) — shape `{ scoping: { stories, signals }, discovery: { stories, signals }, meta }`.
787
+ - **Mirrored:** `jobMemory._narrix` — same attachment so handlers can read from either `ctx.executionMemory._narrix` or `ctx.jobMemory._narrix`.
788
+ - **Web scope:** when **`narrix.enableWebScope === true`**, **`executionMemory.webContext`** is set to the **`WebScoperResult`** from **`@exellix/narrix-web-scoper`** (hit, miss, or structured error). It is independent of **`_narrix`**; NARRIX output remains attached even if web scoping fails.
789
+
790
+ Local task handlers receive the enriched `ctx`. On the DIRECT path, context is added only when `includeContextInPrompt === true`. When NARRIX is in play, that context is the "## Scoping and discovery" section from `executionMemory._narrix` when present, plus a **Web sources (primary evidence)** section when web scoping returned **`webContext.available === true`** (see above). We do not inject a "Narrix Pre-Processor" placeholder. The context generator (xontext) is not given _narrix when NARRIX is in play, so it cannot emit that section.
791
+
792
+ ### Per-node enrichment
793
+
794
+ Each `runTask` call with `narrix` set is independent. If a graph runs two nodes with narrix config, each gets its own NARRIX run (no shared state). Orchestrators (e.g. graph-engine) lift `node.taskConfiguration.narrix` to `RunTaskRequest.narrix`.
795
+
796
+ ### Example
797
+
798
+ ```typescript
799
+ await runTask({
800
+ skillKey: "tasks/assess-reachability",
801
+ narrix: { datasetId: "network.egress.subnets" },
802
+ executionMemory: { input: { raw: subnetRecord } },
803
+ input: { question: "Assess reachability based on scoping." },
804
+ });
805
+
806
+ // Handler or LLM sees ctx.executionMemory._narrix (scoping, discovery, meta)
807
+ ```
808
+
809
+ ---
810
+
811
+ ## Intermediate steps (multi-step tasks)
812
+
813
+ When a task runs multiple logical steps in one invocation (e.g. a combined node that does "to-cni + enrich + triage"), the response can include an optional **`intermediateSteps`** array so consumers can inspect and report the chain without extra round-trips.
814
+
815
+ ### Response shape
816
+
817
+ - **`intermediateSteps`** (optional): array of steps in execution order. Each step has:
818
+ - **`step`** (number): 1-based order
819
+ - **`id`** (string): stable identifier (e.g. `"to-cni"`, `"engine-enrich"`, `"triage-q1-q6"`)
820
+ - **`ok`** (boolean): whether the step succeeded
821
+ - **`summary`** (optional): short description (e.g. `"cni built"`, `"enriched"`)
822
+ - **`error`** (optional): error message when `ok` is false
823
+ - **`inputSummary`** (optional): summary input for reporting
824
+ - **`outputExcerpt`** (optional): small excerpt of step output for debugging
825
+
826
+ Skills/tasks can return steps **inside `parsed`** (e.g. `parsed.intermediateSteps`); the SDK lifts them to the top-level **`response.intermediateSteps`** so consumers always read from the same place. Local tasks can return `{ output: {...}, intermediateSteps: [...] }`; the SDK exposes `intermediateSteps` on the response and keeps only the rest in `parsed`. For **narrix-then-direct**, the runtime prepends a step `{ step: 1, id: "narrix", ok: true }` and renumbers any steps from the direct phase.
827
+
828
+ ### Example
829
+
830
+ ```typescript
831
+ const res = await runTask({ skillKey: "tasks/combined-cni-enrich-triage", input: { ... } });
832
+
833
+ if (res.intermediateSteps) {
834
+ for (const s of res.intermediateSteps) {
835
+ console.log(`Step ${s.step}: ${s.id} ${s.ok ? "ok" : s.error}`);
836
+ }
837
+ }
838
+ // res.parsed holds the final task output (intermediateSteps are not duplicated there)
839
+ ```
840
+
841
+ ### Execution pipeline and synthesized-context (PRE step)
842
+
843
+ **Breaking change:** Execution can use an **execution pipeline** instead of a single `executionType`. See [BREAKING-CHANGES.md](BREAKING-CHANGES.md) for migration from `executionType` to `executionPipeline`.
844
+
845
+ - **Pipeline:** `executionPipeline` is an array of steps with phases **pre**, **main**, and **post**. Order: all PRE steps (in order) → exactly one MAIN step → all POST steps (optional; built-in types: `audit`, `polish`). Default when omitted: `[{ phase: "main", type: "direct" }]`. Existing `executionType` (e.g. `DIRECT`, `NARRIX_THEN_DIRECT`) is still supported when `executionPipeline` is not set.
846
+ - **PRE step `synthesized-context`:** A weak model (default `gpt-5-nano`) synthesizes **source material** (Narrix / memory / web per policy below) into a single context string; the MAIN task then runs with that string as its `context`. Requires `includeContextInPrompt: true` (or the PRE step’s `config.autoEnableContext: true`). Add `{ phase: "pre", type: SYNTHESIZED_CONTEXT, config: SynthesisConfig }` before the main step (`SYNTHESIZED_CONTEXT` is exported as the string `"synthesized-context"`).
847
+ - **Two LLM calls (typical):** deterministic **source material** (policy + markdown/JSON assembly) → **synthesis** model → synthesized `context` → **main** task model. Synthesis is not a pure string merge; it is a separate gateway invocation before the main skill.
848
+ - **SynthesisConfig** (on the PRE step’s `config`): `modelConfig`, **`contextSourcePolicy`**, optional **`synthesisInputStrategy`** (`policy` / `execution-memory-only` / `job-memory-only` / `task-memory-only` / `full-memory-bundle`), optional **`webEvidence`**, `customSynthesizingGuidelines`, `fallbackToDirect` (default `false`), `memoryPaths`, `synthesisPromptOverride`, `timeoutMs`, `maxOutputLength`, `autoEnableContext`, optional mode selectors **`synthesisMode`** (preferred) and **`synthesisOutputFormat`** (legacy-compatible), plus optional **`questionPath`**, **`getQuestion`**, **`structuredMaxItemsPerSide`**, **`structuredMaxItemContentChars`**, plus optional **`questionDriven`** and **`questions`**, plus optional **`xynthesizedOutput`** (**`destination`**: **`"job"`** | **`"task"`** | **`"execution"`**, **`outputKey`**, **`mode`**, **`alsoWriteLegacySynthesizedContext`**) to mirror synthesis into **`request.xynthesized`** and return **`response.xynthesizedPatch`** (see [Xynthesized memory and smart input](#xynthesized-memory-and-smart-input)). Full API: [documenations/synthesized-context-guide.md](./documenations/synthesized-context-guide.md).
849
+ - **Question-driven synthesis (opt-in):** Set **`questionDriven: true`** and provide **`questions`** (`[{ id, question, source?: "record"|"web"|"both" }]`). This mode runs one synthesis per question and writes a structured artifact to `executionMemory.synthesizedContext` with `mode: "questionDriven"` and stable access path `executionMemory.synthesizedContext.answers.<id>.synthesis`. Web-only questions degrade gracefully when `executionMemory.webContext` is unavailable (answer is present but marked unknown + reason).
850
+ - **Structured synthesis (opt-in):** Set **`synthesisMode: "structured"`** (or legacy-compatible **`synthesisOutputFormat: "structured"`**) so the synthesis model returns **JSON** matching **`SynthesizedPromptPayload`** (`SynthesisInput`, `SynthesizedContext`, `SynthesizedItem`, etc., exported from **`@exellix/ai-tasks`**). The package validates the shape, then builds main-step **`context`** markdown via **`buildSynthesizedContextMarkdown`** (task cores → question → local → supporting → unknowns → assumptions). Raw local vs web blocks are passed as **`local_raw`** / **`supporting_raw`** in the synthesis prompt (see **`resolveSourceMaterialParts`**). The task **question** is taken from **`getQuestion(request)`**, else **`questionPath`** on **`request.input`** (default path **`question`**), else the rendered downstream user prompt (truncated). In structured mode, template core tokens are required in template content; no detected template cores is treated as a template-definition error. Structured artifacts are persisted at `executionMemory.synthesizedContext`. Templates: **`templates/synthesis/system-structured.md`** and **`user-structured.txt`** (override path still **`SYNTHESIS_TEMPLATES_PATH`**). For tests or custom execution, use **`setContextSynthesizer`** / **`getContextSynthesizer`**; **`runStructuredSynthesisGatewayCall`** mirrors the default gateway path.
851
+ - **Synthesis uses a real LLM call** (`AIGateway.invoke` via xynthesis `executeXynthesisAction`) — not just string merging. Building `source_material` is deterministic; the PRE step then **invokes the synthesis model** (e.g. `SYNTHESIS_MODEL` / `modelConfig`) before the MAIN step runs. See [documenations/synthesis-invocation-notes.md](./documenations/synthesis-invocation-notes.md) for details.
852
+ - **`webEvidence`:** Optional tuning for the **Web sources** markdown block inside synthesis source material: `preferCleanContent` (default `true`), `maxSources` (default `5`), `dedupeByUrl` (default `true`), `maxTotalChars`. Env **`WEB_CONTEXT_MARKDOWN_MAX_CHARS`** still applies when `maxTotalChars` is not set.
853
+ - **`contextSourcePolicy` (what becomes `<source_material>` in the synthesis prompt):**
854
+
855
+ | Policy | Narrix section | Web markdown | Memory JSON |
856
+ |--------|----------------|--------------|-------------|
857
+ | **`narrix-web`** | Yes (required) | If `webContext` hit | No |
858
+ | **`narrix-web-memory`** | If present | If hit | Yes — never embeds raw `webContext` in JSON |
859
+ | **`memory-web`** | **Stripped** from JSON | If hit | Yes |
860
+ | **`memory-only`** | No | No | Yes — never embeds raw `webContext` in JSON |
861
+ | **`auto`** | Resolves at runtime | | |
862
+
863
+ **`auto` resolution:** **`narrix-web-memory`** if Narrix output exists (`executionMemory[attachToField]` or coerced **`taskMemory.narrix`**); else **`memory-web`** if web scoping produced a usable hit; else **`memory-only`**. **Legacy aliases:** `narrix-only` → `narrix-web`, `narrix+memory` → `narrix-web-memory`.
864
+
865
+ **Synthesis output shape (default):** `templates/synthesis/system.md` asks the weak model for three markdown sections in order: **`## Local context (primary)`**, **`## Web evidence (supporting)`**, **`## Supporting sources (optional)`** (short traceability lines only). Override with `synthesisPromptOverride` if needed. With **`synthesisOutputFormat: "structured"`**, output is JSON validated to the generic synthesis contract, then converted to markdown for the main step only.
866
+
867
+ **Migration note:** If you relied on **`auto`** with Narrix but **without** memory in the synthesizer input, set **`contextSourcePolicy: "narrix-web"`** explicitly. See [BREAKING-CHANGES.md](BREAKING-CHANGES.md) (*Synthesis `contextSourcePolicy` and `auto`*).
868
+ - **Templates:** Synthesis prompts live in **templates/synthesis/** — markdown mode: `system.md`, `user.txt`; structured mode: `system-structured.md`, `user-structured.txt`. Optional env **`SYNTHESIS_TEMPLATES_PATH`** for base path.
869
+ - **Caching:** Optional synthesis result caching via **nx-cache** (same project) can be used to reduce cost for batch or repeated runs.
870
+ - **Non-streaming:** Synthesis returns a **single complete response**; there is no streaming support for the synthesis step.
871
+ - **Builder:** `withExecutionPipeline(steps)`, `withSynthesizedContextPreStep(modelOrConfig?)` to add the PRE step and set `includeContextInPrompt: true` (pass a **`SynthesisConfig`** object for `contextSourcePolicy`, `webEvidence`, `memoryPaths`, **`xynthesizedOutput`** with **`destination: "job" | "task" | "execution"`**, etc.). Also **`withXynthesized`**, **`withSmartInput`**, **`withSmartInputPaths`**, **`withSmartInputRenderOptions`**, **`withXynthesizedJob`**, **`withXynthesizedTask`**, **`withXynthesizedExecution`** for request-level fields.
872
+ - **Auditability fields in response metadata:** `synthesisEnabled`, `effectiveExecutionPipeline`, `synthesizedContextPresent`, `mainContextSource`, `detectedTemplateCores`. Final response also includes normalized `executionMemory` and `executionState.executionMemory`.
873
+
874
+ **Minimal example (explicit policy + web tuning):**
875
+
876
+ ```typescript
877
+ import { SYNTHESIZED_CONTEXT } from "@exellix/ai-tasks";
878
+
879
+ executionPipeline: [
880
+ {
881
+ phase: "pre",
882
+ type: SYNTHESIZED_CONTEXT,
883
+ config: {
884
+ contextSourcePolicy: "narrix-web-memory",
885
+ webEvidence: { maxSources: 3, preferCleanContent: true },
886
+ modelConfig: { model: "gpt-5-nano" },
887
+ },
888
+ },
889
+ { phase: "main", type: "direct" },
890
+ ],
891
+ includeContextInPrompt: true,
892
+ ```
893
+
894
+ ### POST Step Strategies (post-execution)
895
+
896
+ The execution pipeline supports **POST** steps that run after the MAIN step and operate on its response. Two built-in strategies: **`audit`** (quality-gate loop with re-runs) and **`polish`** (refinement pass against a prioritized checklist). POST steps run in pipeline order; each step’s output becomes the next step’s input.
897
+
898
+ **Pipeline example:**
899
+
900
+ ```typescript
901
+ executionPipeline: [
902
+ { phase: "pre", type: "synthesized-context", config: { /* ... */ } },
903
+ { phase: "main", type: "direct" },
904
+ { phase: "post", type: "audit", config: { gateway: { must: [...], should: [...] }, maxCycles: 3 } },
905
+ { phase: "post", type: "polish", config: { checklist: [...], maxPasses: 2 } },
906
+ ]
907
+ ```
908
+
909
+ Metadata for each POST step is under `response.metadata.postSteps.<type>`. All POST steps’ intermediate steps are merged into `response.intermediateSteps` with continuous numbering (e.g. `audit-cycle-1`, `audit-synthesis`, `audit-select`, `polish-pass-1`).
910
+
911
+ #### POST step: `audit`
912
+
913
+ Quality-gate loop: evaluate MAIN output against **must** and **should** checks, get actionable feedback, re-run the MAIN step when criteria aren’t met, then choose the best result (or run an optional synthesis merge of the top two candidates).
914
+
915
+ - **Config:** `AuditConfig`: `gateway.must`, `gateway.should` (each `{ check, weight }`), `threshold` (default 80), `minCycles`, `maxCycles` (default 3), `selectionStrategy` (`"best"` | `"synthesis"`), `auditModelConfig`, `synthesisModelConfig`, `customAuditGuidelines`, `customSynthesisGuidelines`, `fallbackToBest` (default true), `auditTimeoutMs`.
916
+ - **Output:** Audit uses **structured Markdown** (Flex-MD style); the runtime parses it into checks and overall feedback and computes `weightedScore`.
917
+ - **Re-runs:** When the loop continues, the MAIN step is re-invoked with the original context plus a feedback block from `templates/post-steps/audit/feedback-injection.md`.
918
+ - **Metadata:** `response.metadata.postSteps.audit`: `totalCycles`, `candidateScores`, `selectedCycleIndex`, `allMustPassed`, `synthesisUsed`, `auditResults`, `durationMs`.
919
+ - **Caching:** Audit (and synthesis) LLM calls use **nx-cache** when inputs and config are unchanged (no cache on re-runs with feedback).
920
+ - **Failure:** If all cycles are exhausted and no candidate passes all `must` checks: with `fallbackToBest: true` (default) the best-scored candidate is returned and `allMustPassed: false`; with `fallbackToBest: false` an error is thrown.
921
+
922
+ #### POST step: `polish`
923
+
924
+ Single- or multi-pass refinement against a **prioritized checklist**. Does not re-run MAIN; always produces a refined output (or falls back to the last good output on failure).
925
+
926
+ - **Config:** `PolishConfig`: `checklist` (each `{ instruction, priority: "high" | "medium" | "low" }`), `maxPasses` (default 1), `modelConfig`, `customGuidelines`, `includeOriginalContext`, `timeoutMs`.
927
+ - **Output:** Polish LLM returns **JSON**: `{ polishedOutput, changeNotes[] }`.
928
+ - **Metadata:** `response.metadata.postSteps.polish`: `totalPasses`, `changeNotes` (per pass), `durationMs`.
929
+ - **Failure:** Polish never throws; on LLM/parse failure it returns the previous output and records `ok: false` in the step.
930
+
931
+ #### Minimal examples
932
+
933
+ **Audit only:**
934
+
935
+ ```typescript
936
+ const result = await runTask({
937
+ skillKey: "tasks/security-risk-summary",
938
+ input: { assetId: "a-123" },
939
+ jobId: "job-1",
940
+ agentId: "agent-1",
941
+ executionPipeline: [
942
+ { phase: "main", type: "direct" },
943
+ {
944
+ phase: "post",
945
+ type: "audit",
946
+ config: {
947
+ gateway: {
948
+ must: [{ check: "Output includes a severity rating (critical/high/medium/low)", weight: 10 }],
949
+ should: [{ check: "Recommendations are ordered by priority", weight: 5 }],
950
+ },
951
+ threshold: 80,
952
+ maxCycles: 3,
953
+ },
954
+ },
955
+ ],
956
+ });
957
+ console.log(result.parsed); // best candidate output
958
+ console.log(result.metadata.postSteps?.audit); // totalCycles, candidateScores, allMustPassed, etc.
959
+ ```
960
+
961
+ **Polish only:**
962
+
963
+ ```typescript
964
+ const result = await runTask({
965
+ skillKey: "tasks/executive-summary",
966
+ input: { report: "..." },
967
+ executionPipeline: [
968
+ { phase: "main", type: "direct" },
969
+ {
970
+ phase: "post",
971
+ type: "polish",
972
+ config: {
973
+ checklist: [
974
+ { instruction: "Remove jargon; use plain language", priority: "high" },
975
+ { instruction: "Each paragraph ≤ 3 sentences", priority: "medium" },
976
+ ],
977
+ maxPasses: 2,
978
+ },
979
+ },
980
+ ],
981
+ });
982
+ ```
983
+
984
+ #### Chaining audit + polish
985
+
986
+ Common pattern: audit first to ensure quality, then polish to refine the approved candidate. Audit’s chosen output becomes polish’s input.
987
+
988
+ #### Builder
989
+
990
+ - **`withAuditPostStep(config: AuditConfig)`** — Appends an audit POST step; ensures pipeline has a MAIN step.
991
+ - **`withPolishPostStep(config: PolishConfig)`** — Appends a polish POST step; ensures pipeline has a MAIN step.
992
+
993
+ Example with builder (no need to set `executionPipeline` manually):
994
+
995
+ ```typescript
996
+ const request = new TaskRequestBuilder()
997
+ .withSkillKey("tasks/security-risk-summary")
998
+ .withInput({ assetId: "a-123" })
999
+ .withJobId("job-1")
1000
+ .withAgentId("agent-1")
1001
+ .withAuditPostStep({
1002
+ gateway: {
1003
+ must: [{ check: "Output includes severity rating", weight: 10 }],
1004
+ should: [{ check: "Recommendations ordered by priority", weight: 5 }],
1005
+ },
1006
+ threshold: 80,
1007
+ maxCycles: 3,
1008
+ })
1009
+ .withPolishPostStep({
1010
+ checklist: [
1011
+ { instruction: "Use active voice throughout", priority: "high" },
1012
+ { instruction: "Remove redundant qualifiers", priority: "medium" },
1013
+ ],
1014
+ })
1015
+ .build();
1016
+ const result = await runTask(request);
1017
+ ```
1018
+
1019
+ #### Template paths
1020
+
1021
+ - Audit: `templates/post-steps/audit/` (`system.md`, `user.txt`, `feedback-injection.md`, `synthesis.md`). Override: `AUDIT_TEMPLATES_PATH` (base path for the audit folder).
1022
+ - Polish: `templates/post-steps/polish/` (`system.md`, `user.txt`). Override: `POLISH_TEMPLATES_PATH`.
1023
+
1024
+ Templates use Handlebars (`{{variable}}`, `{{#each}}`, `{{#if}}`).
1025
+
1026
+ #### Design decisions
1027
+
1028
+ - **Audit re-runs MAIN (not the audit model):** The MAIN step has the full prompt and skill context; re-running it with audit feedback yields better corrections.
1029
+ - **Polish does not re-run MAIN:** Polish is surface-level (tone, format); a dedicated rewrite step is cheaper and sufficient.
1030
+ - **Audit output is Markdown (Flex-MD), not forced JSON:** More robust across models; runtime parses structured sections.
1031
+ - **nx-cache for audit/synthesis:** Reduces cost for repeated or batch runs with identical inputs.
1032
+ - **No global cost guardrails** (e.g. `maxTotalPostCalls`) in this release; control via `maxCycles` and `maxPasses`.
1033
+ - **No streaming** for POST steps; final output is returned after all POST steps complete.
1034
+
1035
+ #### Testing
1036
+
1037
+ POST steps are covered by unit and integration tests in `test/post-steps/`:
1038
+
1039
+ - **resolvePostStepConfig.test.ts** — model and timeout resolution (config, env, fallback).
1040
+ - **parseAuditOutput.test.ts** — parsing audit structured markdown into checks and overall feedback.
1041
+ - **parsePolishOutput.test.ts** — parsing polish JSON (including code-fenced) into `polishedOutput` and `changeNotes`.
1042
+ - **runAuditPostStep.test.ts** — audit step with mocked gateway: passing/failing markdown, fallback, and audit-call failure.
1043
+ - **runPolishPostStep.test.ts** — polish step with mocked gateway: valid JSON, call failure fallback, multiple passes.
1044
+ - **pipeline-post-steps.test.ts** — pipeline integration: `runTask` with `executionPipeline` (main + audit, main + polish) and mocked executor/gateways; asserts `metadata.postSteps` and final output.
1045
+
1046
+ Run post-steps tests: `npm test` (includes `dist-test/test/post-steps/*.js`).
1047
+
1048
+ ### Types
1049
+
1050
+ Use **`RunTaskResponseWithSteps<TParsed>`** when you expect steps; it extends `RunSkillResponse` with `intermediateSteps?: IntermediateStep[]`. When POST steps run, `response.metadata.postSteps` is set (`audit?: AuditPostStepMetadata`, `polish?: PolishPostStepMetadata`). Exported POST step types: `AuditConfig`, `AuditCheck`, `AuditResult`, `AuditCheckResult`, `AuditLLMOutput`, `AuditPostStepMetadata`, `PolishConfig`, `PolishChecklistItem`, `PolishLLMOutput`, `PolishPostStepMetadata`, `RunTaskResponsePostStepsMetadata`. See the [Types](#intermediate-step-intermediate-steps-runtaskresponsewithsteps) section and [documenations/intermediate-steps.md](./documenations/intermediate-steps.md) for full details.
1051
+
1052
+ ---
1053
+
1054
+ ## How max tokens are resolved (automation + caller controls)
1055
+
1056
+ `@exellix/xynthesis` 3.1+ ships `resolveMaxTokens` — a deterministic function that picks the right `maxTokens` for any LLM call from `(inputText, outputExpectation, modelId, callerMaxTokens?, structuredOutput?)`. `@exellix/ai-tasks` invokes it on **every** xynthesis-backed call (PRE synthesis, AI scoping, POST audit, POST polish, structured-repair fallback) so callers don't have to pick a number.
1057
+
1058
+ **The pipeline is:**
1059
+
1060
+ 1. **Estimate input tokens** from `inputText` (system + user + extra material) using `xynthesis.estimateTokens`.
1061
+ 2. **Resolve the effective `OutputExpectation`** = caller's `llmCall.outputExpectation` → per-action default (`xynthesis.resolveOutputExpectation('synthesis' | 'audit' | 'fix' | 'pick-best' | 'craft-final')`) → stage-local default (e.g. AI scoping uses `{ size: { mode: "absolute", minWords: 5, maxWords: 200 }, density: "concise" }`) → xynthesis package fallback.
1062
+ 3. **Compute estimated output tokens** from the expectation (absolute words → tokens; relative → ratio × input tokens), multiplied by `headroomMultiplier(density)` and (if `structuredOutput`) the structured-output factor.
1063
+ 4. **Cap at the model's `maxOutputTokens`** from `MODEL_CAPABILITIES` (unknown models fall back to `{ contextWindow: 128_000, maxOutputTokens: 4_096, completionTokenCost: "medium" }`).
1064
+ 5. **Honor the caller's `maxTokensCap`** as a HARD CEILING — the auto-sizer can pick a smaller number, but never larger.
1065
+ 6. The `resolveMaxTokens` result (`{ maxTokens, reason: "computed" | "caller-override" | "model-ceiling" | "fallback", diagnostics: { … } }`) is forwarded to xynthesis as `tokenResolutionMetadata` and surfaced in trace mode as `LlmCallObservation.request.tokenResolution`.
1066
+
1067
+ **To adjust per-call dynamically** (without losing the automation):
1068
+
1069
+ - Set `llmCall.outputExpectation` to bias the auto-sizer toward smaller / larger / denser output.
1070
+ - Set `llmCall.maxTokensCap` to bound the upper limit (e.g. for cost ceilings).
1071
+ - Leave both unset to get the per-stage / per-action default.
1072
+
1073
+ The legacy `llmCall.maxTokens` field is still accepted as an alias for `maxTokensCap`. The legacy `modelConfig.maxTokens` (forwarded as-is to ai-skills for the MAIN skill call) continues to work.
1074
+
1075
+ > **Note:** the MAIN skill call goes through `@exellix/ai-skills` `runSkill`, which today uses your `modelConfig.maxTokens` directly (no auto-sizer). Setting `RunTaskRequest.llmCall.maxTokensCap` on the main skill is overlaid onto `modelConfig.maxTokens` before the call. ai-skills auto-sizing is tracked as an upstream feature request in [`documenations/upstream-feature-requests/ai-skills-llm-observability.md`](documenations/upstream-feature-requests/ai-skills-llm-observability.md).
1076
+
1077
+ ---
1078
+
1079
+ ## LLM call configuration (`LlmCallConfig`)
1080
+
1081
+ `LlmCallConfig` is the canonical, per-stage knob set every LLM-orchestrated step accepts. It composes with the legacy `modelConfig` (and per-step `model` / `timeoutMs` / `maxOutputLength`) — when both are set, `llmCall.*` wins.
1082
+
1083
+ ```typescript
1084
+ type LlmCallConfig = {
1085
+ model?: string; // provider/model id
1086
+ maxTokensCap?: number; // HARD CEILING for resolveMaxTokens (does NOT disable automation)
1087
+ maxTokens?: number; // alias for maxTokensCap (backward compat)
1088
+ temperature?: number;
1089
+ topP?: number;
1090
+ maxOutputLength?: number; // post-call character truncation
1091
+ timeoutMs?: number;
1092
+ outputExpectation?: OutputExpectation; // drives resolveMaxTokens auto-sizing
1093
+ };
1094
+ ```
1095
+
1096
+ **Per-stage placement:**
1097
+
1098
+ | Stage | Where to set `llmCall` |
1099
+ |-------|------------------------|
1100
+ | MAIN skill | `RunTaskRequest.llmCall` (or use the legacy `modelConfig`) |
1101
+ | AI scoping (per-instruction) | `RunTaskRequest.aiScoping[].llmCall` |
1102
+ | AI scoping (request fallback) | `RunTaskRequest.aiScopingOptions.llmCall` |
1103
+ | PRE synthesis | `SynthesisConfig.llmCall` (on the `synthesized-context` PRE step) |
1104
+ | POST audit (evaluator) | `AuditConfig.llmCall` or `AuditConfig.audit.llmCall` |
1105
+ | POST audit (synthesis-merge) | `AuditConfig.audit.synthesis.llmCall` |
1106
+ | POST polish | `PolishConfig.llmCall` |
1107
+ | Utility (xynthesis-finalize) | `RunUtilityRequest.exec.{model, maxTokensCap, temperature, topP, outputExpectation, ...}` |
1108
+
1109
+ **Builder helpers (`TaskRequestBuilder`):**
1110
+
1111
+ - `withLlmCall(llmCall)` — main skill.
1112
+ - `withAiScopingOptions({ llmCall, concurrency, timeoutMsPerItem })` — fallback for every `aiScoping[]` item.
1113
+ - `withSynthesisLlmCall(llmCall)` — PRE `synthesized-context` step.
1114
+ - `withAuditLlmCall(llmCall, role?)` — POST audit step (`role: "audit" | "synthesis"`, omit for top-level).
1115
+ - `withPolishLlmCall(llmCall)` — POST polish step.
1116
+ - `withXynthesized(memory)` / `withSmartInput(config)` / `withSmartInputPaths(paths)` / `withSmartInputRenderOptions(opts)` — graph **`xynthesized`** / **`smartInput`** contract (see [Xynthesized memory and smart input](#xynthesized-memory-and-smart-input)).
1117
+ - `withXynthesizedJob(key, value)` / `withXynthesizedTask(key, value)` / `withXynthesizedExecution(key, value)` — merge one key into **`xynthesized.job`** / **`xynthesized.task`** / **`xynthesized.execution`**.
1118
+
1119
+ **Environment variables (POST steps + AI scoping):** every stage reads from per-stage env vars first, then shared `POST_STEP_*` env vars, then the supplied fallback. Order is: `config.<field>` → step env → shared env → fallback.
1120
+
1121
+ | Env var (per stage) | Effect |
1122
+ |---------------------|--------|
1123
+ | `AUDIT_MODEL`, `POLISH_MODEL`, `SYNTHESIS_MODEL`, `AI_SCOPING_MODEL` | Model override for that stage |
1124
+ | `AUDIT_TIMEOUT_MS`, `POLISH_TIMEOUT_MS`, `SYNTHESIS_TIMEOUT_MS`, `AI_SCOPING_TIMEOUT_MS` | Per-call timeout override |
1125
+ | `AUDIT_MAX_TOKENS_CAP`, `POLISH_MAX_TOKENS_CAP`, `SYNTHESIS_MAX_TOKENS_CAP` | Hard ceiling override |
1126
+ | `AUDIT_TEMPERATURE`, `POLISH_TEMPERATURE`, `SYNTHESIS_TEMPERATURE` | Temperature override |
1127
+ | `AUDIT_TOP_P`, `POLISH_TOP_P`, `SYNTHESIS_TOP_P` | Top-P override |
1128
+ | `AUDIT_MAX_OUTPUT_LENGTH`, `POLISH_MAX_OUTPUT_LENGTH`, `SYNTHESIS_MAX_OUTPUT_LENGTH` | Post-call char truncation override |
1129
+ | `AI_SCOPING_CONCURRENCY` | Max concurrent scoping calls (default 5) |
1130
+ | `AI_SCOPING_TIMEOUT_MS_PER_ITEM` | Per-item timeout in ms (default 30_000) |
1131
+
1132
+ | Shared env var | Effect |
1133
+ |----------------|--------|
1134
+ | `POST_STEP_MODEL` | Default model for any POST step that didn't get a specific value |
1135
+ | `POST_STEP_TIMEOUT_MS` | Default timeout |
1136
+ | `POST_STEP_MAX_TOKENS_CAP` | Default hard ceiling |
1137
+ | `POST_STEP_TEMPERATURE` | Default temperature |
1138
+ | `POST_STEP_TOP_P` | Default Top-P |
1139
+ | `POST_STEP_MAX_OUTPUT_LENGTH` | Default char truncation |
1140
+
1141
+ `outputExpectation` is intentionally NOT read from env vars (too structured).
1142
+
1143
+ ---
1144
+
1145
+ ## Trace mode observability for LLM calls
1146
+
1147
+ When `RunTaskRequest.executionMode === "trace"` (or `RunUtilityRequest.exec.executionMode === "trace"`), `@exellix/ai-tasks` populates a structured `LlmCallObservation` for **every** LLM call attempted (success or failure) and surfaces them on response metadata:
1148
+
1149
+ | `result.metadata` field | Stage | Notes |
1150
+ |-------------------------|-------|-------|
1151
+ | `synthesizedContextLlmCalls?: LlmCallObservation[]` | PRE synthesis (markdown / question-driven / structured) | one entry per question or branch |
1152
+ | `aiScopingLlmCalls?: LlmCallObservation[]` | AI scoping | one entry per `aiScoping[]` instruction |
1153
+ | `aiScopingFailures?: AiScopingFailureRecord[]` | AI scoping | **always** populated when any item failed (regardless of trace mode) — previously these were silently dropped |
1154
+ | `postSteps.audit.llmCalls?: LlmCallObservation[]` | POST audit (evaluator + synthesis-merge) | per-cycle entries |
1155
+ | `postSteps.polish.llmCalls?: LlmCallObservation[]` | POST polish | per-pass entries |
1156
+ | `intermediateSteps[].observation?: LlmCallObservation` | any failed POST step | attached to the failure step so consumers can read the request/response context inline |
1157
+
1158
+ `LlmCallObservation` is a discriminated union over `source: "xynthesis" | "ai-skills"`:
1159
+
1160
+ ```typescript
1161
+ type LlmCallObservation =
1162
+ | {
1163
+ source: "xynthesis"; stage: LlmCallStage; stepId?: string;
1164
+ request: LlmCallRequestSnapshot; // includes resolved tokenResolution from resolveMaxTokens
1165
+ summary?: InvokeAttemptSummary; // from executeXynthesisAction on success / typed errors on failure
1166
+ debugTrace?: XynthesisDebugTrace; // present when executionMode: "trace" was forwarded
1167
+ durationMs: number; ok: boolean; error?: { name: string; message: string };
1168
+ }
1169
+ | {
1170
+ source: "ai-skills"; stage: "main-skill"; stepId?: string;
1171
+ request: LlmCallRequestSnapshot;
1172
+ diagnostics?: RunSkillDiagnostics; // from runSkill in trace mode
1173
+ durationMs: number; ok: boolean; error?: { name: string; message: string };
1174
+ };
1175
+ ```
1176
+
1177
+ **`InvokeAttemptSummary`** (xynthesis ≥ 3.1) carries `{ jobId, taskId, modelRequested, modelResolved, maxTokensFromCaller, maxTokensEffective, modelUsedFromProvider, usage, routing, costUsd, traceKind, executionMetadata }`.
1178
+
1179
+ **`RunSkillDiagnostics`** (ai-skills ≥ 5.0) carries `{ trace: { step, timing, routing, usage, costUsd, modelUsed, attempts[], rawProviderPayload?, invokeRequest? }, ... }`.
1180
+
1181
+ ### Typed errors (instanceof-friendly)
1182
+
1183
+ Failures from xynthesis call sites are returned as upstream typed errors that already carry rich context — `@exellix/ai-tasks` passes them through unchanged so callers can `instanceof` them:
1184
+
1185
+ | Error class | Carries | Where |
1186
+ |-------------|---------|-------|
1187
+ | `XynthesisInvokeError` | `invokeSummary`, `cause` | xynthesis invoke failure (PRE / POST / scoping / utility) |
1188
+ | `XynthesisResponseParseError` | `stage`, `invokeSummary`, `responseLength`, `responsePreview`, `actionType?`, `cause` | xynthesis parse failure |
1189
+ | `SkillExecutionTraceError` | `RunSkillDiagnostics` (incl. `trace.invokeRequest` echo) | MAIN skill failure in trace mode |
1190
+ | `LlmCallContextError` | `observation: LlmCallObservation`, `cause` | thin wrapper for the few sites where the upstream still throws untyped (raw `setTimeout` timeouts, MAIN skill outside trace mode, AIGateway repair fallback) |
1191
+
1192
+ All of these are re-exported from `@exellix/ai-tasks`:
1193
+
1194
+ ```typescript
1195
+ import {
1196
+ XynthesisInvokeError,
1197
+ XynthesisResponseParseError,
1198
+ SkillExecutionTraceError,
1199
+ LlmCallContextError,
1200
+ type LlmCallObservation,
1201
+ type LlmCallConfig,
1202
+ type InvokeAttemptSummary,
1203
+ type RunSkillDiagnostics,
1204
+ resolveMaxTokens,
1205
+ resolveOutputExpectation,
1206
+ ACTION_OUTPUT_DEFAULTS,
1207
+ MODEL_CAPABILITIES,
1208
+ } from "@exellix/ai-tasks";
1209
+ ```
1210
+
1211
+ ### Worked example
1212
+
1213
+ ```typescript
1214
+ import { runTask, type LlmCallConfig } from "@exellix/ai-tasks";
1215
+
1216
+ const llmCall: LlmCallConfig = {
1217
+ model: "gpt-5-mini",
1218
+ maxTokensCap: 4096, // hard ceiling
1219
+ outputExpectation: { size: { kind: "absolute", maxWords: 600 }, density: "default" },
1220
+ temperature: 0.4,
1221
+ };
1222
+
1223
+ const result = await runTask({
1224
+ skillKey: "tasks/security-risk-summary",
1225
+ input: { assetId: "a-123" },
1226
+ executionMode: "trace",
1227
+ llmCall,
1228
+ aiScopingOptions: { llmCall: { model: "gpt-5-nano", maxTokensCap: 512 } },
1229
+ // executionPipeline: [{ phase: "pre", type: "synthesized-context", config: { llmCall } }, { phase: "main", type: "direct" }],
1230
+ });
1231
+
1232
+ const m = (result as any).metadata ?? {};
1233
+ console.log("PRE synth calls:", m.synthesizedContextLlmCalls?.length ?? 0);
1234
+ console.log("AI scoping calls:", m.aiScopingLlmCalls?.length ?? 0);
1235
+ console.log("AI scoping fails:", m.aiScopingFailures?.length ?? 0);
1236
+ console.log("audit calls:", m.postSteps?.audit?.llmCalls?.length ?? 0);
1237
+ console.log("polish calls:", m.postSteps?.polish?.llmCalls?.length ?? 0);
1238
+ ```
1239
+
1240
+ ---
1241
+
1242
+ ## Graph Execution Support
1243
+
1244
+ ### Overview
1245
+
1246
+ `@exellix/ai-tasks` supports optional graph execution context fields that enable smarter activity tracking and identity for nodes executing within graph workflows. When executing tasks in a graph (e.g., via `worex-graphs`), you can provide additional context fields to improve activity tracking and debugging.
1247
+
1248
+ The client automatically passes these fields through to `@woroces/ai-skills`, which then passes them to `@x12i/ai-gateway`. The gateway maps them to the activity identity structure according to `@x12i/activix-tracking` v3.5.0+ mapping rules.
1249
+
1250
+ ### Optional Fields
1251
+
1252
+ #### `graphId` (optional)
1253
+
1254
+ **Type:** `string`
1255
+ **Description:** Identifier for the graph execution context
1256
+
1257
+ Used when executing tasks within a graph to track which graph contains the task. This enables:
1258
+ - Activity grouping and filtering by graph
1259
+ - Better activity identity for graph-based workflows
1260
+ - Enhanced debugging and traceability
1261
+
1262
+ **Example:**
1263
+ ```typescript
1264
+ const result = await tasks.runTask({
1265
+ skillKey: 'tasks/security-risk-summary',
1266
+ executionType: ExecutionType.DIRECT,
1267
+ input: { assetId: 'a-123' },
1268
+ jobId: 'job-123',
1269
+ agentId: 'agent-1',
1270
+ graphId: 'graph-456', // Graph identifier
1271
+ nodeId: 'node-789', // Node identifier
1272
+ });
1273
+ ```
1274
+
1275
+ **Use Cases:**
1276
+ - Graph workflow execution tracking
1277
+ - Multi-graph job orchestration
1278
+ - Graph-level activity aggregation
1279
+
1280
+ #### `nodeId` (optional)
1281
+
1282
+ **Type:** `string`
1283
+ **Description:** Identifier for the specific node being executed
1284
+
1285
+ Used when executing tasks within a graph to track which specific node is being executed. When provided along with `graphId`, enables:
1286
+ - Precise activity identity for graph nodes
1287
+ - Node-level activity tracking and debugging
1288
+ - Better correlation between graph structure and executed activities
1289
+
1290
+ **Example:**
1291
+ ```typescript
1292
+ const result = await tasks.runTask({
1293
+ skillKey: 'tasks/security-risk-summary',
1294
+ executionType: ExecutionType.DIRECT,
1295
+ input: { assetId: 'a-123' },
1296
+ jobId: 'job-123',
1297
+ agentId: 'agent-1',
1298
+ graphId: 'workflow-data-processing', // Graph identifier
1299
+ nodeId: 'extract-emails-node', // Node identifier
1300
+ });
1301
+ ```
1302
+
1303
+ **Use Cases:**
1304
+ - Individual node execution tracking
1305
+ - Node-level activity analysis
1306
+ - Graph node debugging
1307
+
1308
+ #### `prevNodeId` (optional)
1309
+
1310
+ **Type:** `string`
1311
+ **Description:** Identifier for the previous node in the graph execution flow
1312
+
1313
+ Used when executing tasks within a graph to track which node was executed before the current one. This enables:
1314
+ - Graph flow tracking and debugging
1315
+ - Understanding execution order in graph workflows
1316
+ - Better activity correlation between consecutive nodes
1317
+
1318
+ **Example:**
1319
+ ```typescript
1320
+ const result = await tasks.runTask({
1321
+ skillKey: 'tasks/security-risk-summary',
1322
+ executionType: ExecutionType.DIRECT,
1323
+ input: { assetId: 'a-123' },
1324
+ jobId: 'job-123',
1325
+ agentId: 'agent-1',
1326
+ graphId: 'workflow-data-processing',
1327
+ nodeId: 'extract-emails-node',
1328
+ prevNodeId: 'validate-input-node', // Previous node identifier
1329
+ });
1330
+ ```
1331
+
1332
+ **Use Cases:**
1333
+ - Graph execution flow tracking
1334
+ - Understanding node execution sequence
1335
+ - Debugging graph workflow issues
1336
+
1337
+ #### `coreSkillId` (optional)
1338
+
1339
+ **Type:** `string`
1340
+ **Description:** Alternative node identifier for graph execution
1341
+
1342
+ Used as an alternative to `nodeId` when executing tasks within a graph. This field is automatically mapped to `identity.nodeId` in activity tracking, with priority over `nodeId` but below `skillId` (from the underlying skill request).
1343
+
1344
+ **Example:**
1345
+ ```typescript
1346
+ const result = await tasks.runTask({
1347
+ skillKey: 'tasks/security-risk-summary',
1348
+ executionType: ExecutionType.DIRECT,
1349
+ input: { assetId: 'a-123' },
1350
+ jobId: 'job-123',
1351
+ agentId: 'agent-1',
1352
+ graphId: 'workflow-data-processing',
1353
+ coreSkillId: 'q0', // Alternative node identifier
1354
+ });
1355
+ ```
1356
+
1357
+ **Use Cases:**
1358
+ - Alternative node identification (common in worex-graphs integration)
1359
+ - Node-level tracking when using coreSkillId naming convention
1360
+
1361
+ ### Field Mapping
1362
+
1363
+ The client automatically passes graph/node fields through `@woroces/ai-skills` to the underlying `@x12i/ai-gateway`, which maps them to the activity identity structure. The gateway uses the following priority rules:
1364
+
1365
+ #### Graph ID Mapping
1366
+
1367
+ 1. `masterSkillId` (primary) → `identity.graphId`
1368
+ 2. `graphId` (fallback) → `identity.graphId`
1369
+
1370
+ #### Node ID Mapping
1371
+
1372
+ 1. `skillId` (primary, from underlying skill request) → `identity.nodeId`
1373
+ 2. `coreSkillId` (alternative) → `identity.nodeId`
1374
+ 3. `nodeId` (fallback) → `identity.nodeId`
1375
+
1376
+ #### Previous Node ID Mapping
1377
+
1378
+ 1. `prevNodeId` → `identity.prevNodeId` (automatically mapped for graph flow tracking)
1379
+
1380
+ #### Preserved Fields
1381
+
1382
+ - `masterSkillId` → `identity.masterSkillId` (preserved for backward compatibility)
1383
+ - `masterSkillActivityId` → `identity.masterSkillActivityId` (preserved for skill hierarchies)
1384
+
1385
+ This enables:
1386
+ - **Smarter Identity**: Activity records include graph and node context automatically
1387
+ - **Better Grouping**: Filter and aggregate activities by graph or node
1388
+ - **Enhanced Debugging**: Trace activities back to specific graph nodes
1389
+ - **Improved Analytics**: Analyze graph execution patterns and node performance
1390
+ - **Flexible Integration**: Support for multiple field naming conventions
1391
+
1392
+ ### Complete Field Mapping Guide
1393
+
1394
+ | @exellix/ai-tasks Field | @woroces/ai-skills Field | @x12i/ai-gateway Field | Identity Field | Mapping Priority |
1395
+ |-------------------------|--------------------------|----------------------------|----------------|------------------|
1396
+ | `masterSkillId` | `masterSkillId` | `masterSkillId` (AIRequest) | `identity.graphId` | Priority 1 (primary) |
1397
+ | `graphId` | `graphId` | `graphId` | `identity.graphId` | Priority 2 (fallback) |
1398
+ | `skillId` (via skills) | `skillId` (AIRequest) | `skillId` (AIRequest) | `identity.nodeId` | Priority 1 (primary) |
1399
+ | `coreSkillId` | `coreSkillId` | `coreSkillId` | `identity.nodeId` | Priority 2 (alternative) |
1400
+ | `nodeId` | `nodeId` | `nodeId` | `identity.nodeId` | Priority 3 (fallback) |
1401
+ | `skillKey` | `skillKey` | `instructions` | - | Direct mapping |
1402
+ | `jobId` | `jobId` | `jobId` | `identity.jobId` | Direct mapping |
1403
+ | `agentId` | `agentId` | `agentId` | `identity.agentId` | Direct mapping |
1404
+ | `masterSkillActivityId` | `masterSkillActivityId` | `masterSkillActivityId` (AIRequest) | `identity.masterSkillActivityId` | Preserved |
1405
+
1406
+ **Note:** `masterSkillId` is a legacy field that is still supported for backward compatibility. The new `graphId` and `nodeId` fields provide a cleaner API for graph-based workflows.
1407
+
1408
+ ### Usage Examples
1409
+
1410
+ #### Basic Graph Node Execution
1411
+
1412
+ ```typescript
1413
+ import { WorexClientTasks, ExecutionType } from "@exellix/ai-tasks";
1414
+
1415
+ const tasks = new WorexClientTasks(skills, executor);
1416
+
1417
+ // Execute a task within a graph
1418
+ const result = await tasks.runTask({
1419
+ skillKey: 'tasks/security-risk-summary',
1420
+ executionType: ExecutionType.DIRECT,
1421
+ input: { assetId: 'a-123' },
1422
+ jobId: 'job-123',
1423
+ agentId: 'agent-1',
1424
+ graphId: 'workflow-data-processing',
1425
+ nodeId: 'extract-emails-node',
1426
+ });
1427
+ ```
1428
+
1429
+ #### Graph Execution with Legacy Fields
1430
+
1431
+ ```typescript
1432
+ // Using legacy masterSkillId (still supported)
1433
+ const result = await tasks.runTask({
1434
+ skillKey: 'tasks/security-risk-summary',
1435
+ executionType: ExecutionType.DIRECT,
1436
+ input: { assetId: 'a-123' },
1437
+ jobId: 'job-123',
1438
+ agentId: 'agent-1',
1439
+ masterSkillId: 'workflow-data-processing', // Maps to identity.graphId
1440
+ coreSkillId: 'extract-emails-node', // Maps to identity.nodeId
1441
+ });
1442
+ ```
1443
+
1444
+ #### Graph Execution with coreSkillId
1445
+
1446
+ ```typescript
1447
+ // Using coreSkillId (common in worex-graphs)
1448
+ const result = await tasks.runTask({
1449
+ skillKey: 'tasks/security-risk-summary',
1450
+ executionType: ExecutionType.DIRECT,
1451
+ input: { assetId: 'a-123' },
1452
+ jobId: 'job-123',
1453
+ agentId: 'agent-1',
1454
+ graphId: 'workflow-data-processing',
1455
+ coreSkillId: 'q0', // Alternative node identifier
1456
+ });
1457
+ ```
1458
+
1459
+ #### Graph Execution Without Node ID
1460
+
1461
+ ```typescript
1462
+ // Graph-level execution (no specific node)
1463
+ const result = await tasks.runTask({
1464
+ skillKey: 'tasks/process-data',
1465
+ executionType: ExecutionType.DIRECT,
1466
+ input: { data: '...' },
1467
+ jobId: 'job-123',
1468
+ agentId: 'agent-1',
1469
+ graphId: 'workflow-data-processing',
1470
+ // nodeId not provided - still works
1471
+ });
1472
+ ```
1473
+
1474
+ #### Non-Graph Execution (Backward Compatible)
1475
+
1476
+ ```typescript
1477
+ // Standard execution (no graph context)
1478
+ const result = await tasks.runTask({
1479
+ skillKey: 'tasks/security-risk-summary',
1480
+ executionType: ExecutionType.DIRECT,
1481
+ input: { assetId: 'a-123' },
1482
+ jobId: 'job-123',
1483
+ agentId: 'agent-1',
1484
+ // graphId and nodeId not provided - works as before
1485
+ });
1486
+ ```
1487
+
1488
+ #### Using TaskRequestBuilder with Graph Context
1489
+
1490
+ ```typescript
1491
+ import { TaskRequestBuilder } from "@exellix/ai-tasks";
1492
+
1493
+ const builder = new TaskRequestBuilder();
1494
+ const request = builder
1495
+ .withSkillKey('tasks/security-risk-summary')
1496
+ .withInput({ assetId: 'a-123' })
1497
+ .withGraphId('workflow-data-processing')
1498
+ .withNodeId('extract-emails-node')
1499
+ .withJobId('job-123')
1500
+ .withAiSkillsCorrelation('agent-1', 'job-type-id', 'task-type-id')
1501
+ .build();
1502
+
1503
+ const result = await tasks.runTask(request);
1504
+ ```
1505
+
1506
+ #### Using TaskRequestBuilder with Memory Management
1507
+
1508
+ ```typescript
1509
+ import { TaskRequestBuilder } from "@exellix/ai-tasks";
1510
+ import type { JobHistory, TaskHistory, ExecutionHistory } from "@exellix/ai-tasks";
1511
+
1512
+ const jobMemory: JobHistory = { /* previous task results */ };
1513
+ const taskMemory: TaskHistory = { /* previous skill executions */ };
1514
+ const executionMemory: ExecutionHistory = { /* execution context */ };
1515
+
1516
+ const builder = new TaskRequestBuilder();
1517
+ const request = builder
1518
+ .withSkillKey('tasks/my-task')
1519
+ .withInput({ data: 'process' })
1520
+ .withJobMemory(jobMemory)
1521
+ .withTaskMemory(taskMemory)
1522
+ .withExecutionMemory(executionMemory)
1523
+ .withJobId('job-123')
1524
+ .withAiSkillsCorrelation('agent-1', 'job-type-id', 'task-type-id')
1525
+ .build();
1526
+
1527
+ const result = await tasks.runTask(request);
1528
+ ```
1529
+
1530
+ #### Using Convenience Methods with Graph Context
1531
+
1532
+ ```typescript
1533
+ import { runTask, ExecutionType } from "@exellix/ai-tasks";
1534
+
1535
+ // Convenience method automatically initializes SDK (ERC mode)
1536
+ const result = await runTask({
1537
+ skillKey: 'tasks/security-risk-summary',
1538
+ executionType: ExecutionType.DIRECT,
1539
+ input: { assetId: 'a-123' },
1540
+ jobId: 'job-123',
1541
+ agentId: 'agent-1',
1542
+ graphId: 'workflow-data-processing',
1543
+ nodeId: 'extract-emails-node',
1544
+ });
1545
+ ```
1546
+
1547
+ ### Integration with Graph Runtimes
1548
+
1549
+ When integrating with graph execution runtimes (e.g., `worex-graphs`), you can pass graph/node fields directly - the client automatically passes them through to the underlying skills client and gateway.
1550
+
1551
+ #### Automatic Mapping (Recommended)
1552
+
1553
+ The client automatically passes fields from graph runtimes to the gateway. You can pass fields directly without manual mapping. **Production calls** must also include **`jobTypeId`**, **`taskTypeId`**, and **`executionStrategies`** (often `[]`); the snippets below omit them for brevity.
1554
+
1555
+ ```typescript
1556
+ // worex-graphs sends fields directly - client handles passing through automatically
1557
+ const requestFromGraph = {
1558
+ skillKey: 'tasks/security-risk-summary',
1559
+ executionType: ExecutionType.DIRECT,
1560
+ jobId: 'job-123',
1561
+ agentId: 'agent-1',
1562
+ // jobTypeId, taskTypeId, executionStrategies: [] — required in real requests
1563
+ masterSkillId: 'graph-question-breakdown', // Automatically mapped to identity.graphId
1564
+ coreSkillId: 'q0', // Automatically mapped to identity.nodeId
1565
+ masterSkillActivityId: 'job-123:q0', // Preserved in identity
1566
+ input: { assetId: 'a-123' },
1567
+ // ... other fields
1568
+ };
1569
+
1570
+ // Pass fields directly - client passes through automatically
1571
+ const result = await tasks.runTask({
1572
+ skillKey: requestFromGraph.skillKey,
1573
+ executionType: ExecutionType.DIRECT,
1574
+ input: requestFromGraph.input,
1575
+ jobId: requestFromGraph.jobId,
1576
+ agentId: requestFromGraph.agentId,
1577
+ masterSkillId: requestFromGraph.masterSkillId, // ✅ Automatically passed to gateway
1578
+ coreSkillId: requestFromGraph.coreSkillId, // ✅ Automatically passed to gateway
1579
+ masterSkillActivityId: requestFromGraph.masterSkillActivityId, // ✅ Automatically passed to gateway
1580
+ // ... other fields
1581
+ });
1582
+ ```
1583
+
1584
+ #### Using New Graph Fields (Alternative)
1585
+
1586
+ Alternatively, you can map to the new direct fields (`graphId`/`nodeId`):
1587
+
1588
+ ```typescript
1589
+ // Map to new graph fields
1590
+ const result = await tasks.runTask({
1591
+ skillKey: requestFromGraph.skillKey,
1592
+ executionType: ExecutionType.DIRECT,
1593
+ input: requestFromGraph.input,
1594
+ jobId: requestFromGraph.jobId,
1595
+ agentId: requestFromGraph.agentId,
1596
+ graphId: requestFromGraph.masterSkillId, // Direct graph identifier
1597
+ nodeId: requestFromGraph.coreSkillId, // Direct node identifier
1598
+ // ... other fields
1599
+ });
1600
+ ```
1601
+
1602
+ #### Xynthesized memory and `smartInput` from graph-engine
1603
+
1604
+ Graph runtimes can pass **`xynthesized`** (**`job`**, **`task`**, and **`execution`** buckets) and **`smartInput`** (**Rendrix paths** or legacy **`paths: string[]`**) on the same `runTask` request. ai-tasks **normalizes** legacy string paths, **forwards** **`smartInput`** / **`smartInputRenderOptions`** to local **`ctx`**, PRE synthesis, MAIN **`runSkill`**, and merged **template `variables`**; it **does not** apply graph output mapping or merge patches into a global store—that is the engine’s job after reading **`response.xynthesizedPatch`** (**`job`**, **`task`**, and/or **`execution`** slices) and any **`response.smartInputRenderResult`** from downstream. See [Xynthesized memory and smart input](#xynthesized-memory-and-smart-input).
1605
+
1606
+ ### Activity Record Structure
1607
+
1608
+ When graph context is provided, activity records include the mapped fields in the identity structure:
1609
+
1610
+ ```typescript
1611
+ {
1612
+ _id: ObjectId('...'),
1613
+ jobId: 'job-123',
1614
+ agentId: 'agent-1',
1615
+ taskId: 'task-456',
1616
+ taskTypeId: 'abc123...',
1617
+ identity: {
1618
+ jobId: 'job-123',
1619
+ agentId: 'agent-1',
1620
+ taskId: 'task-456',
1621
+ taskTypeId: 'abc123...',
1622
+ graphId: 'workflow-data-processing', // ← Automatically mapped from masterSkillId or graphId
1623
+ nodeId: 'extract-emails-node', // ← Automatically mapped from skillId, coreSkillId, or nodeId
1624
+ masterSkillId: 'workflow-data-processing', // ← Preserved if provided (backward compatibility)
1625
+ masterSkillActivityId: 'job-123:node-456' // ← Preserved if provided (skill hierarchies)
1626
+ },
1627
+ request: { /* ... */ },
1628
+ config: { /* ... */ },
1629
+ // ... other fields
1630
+ }
1631
+ ```
1632
+
1633
+ ### Benefits
1634
+
1635
+ 1. **Enhanced Activity Tracking**: Activities include graph and node context for better traceability
1636
+ 2. **Smarter Identity**: Activity identity is enriched with graph/node information
1637
+ 3. **Better Debugging**: Easier to correlate activities with graph structure
1638
+ 4. **Improved Analytics**: Analyze execution patterns at graph and node levels
1639
+ 5. **Flexible Integration**: Optional fields don't break existing integrations
1640
+ 6. **Backward Compatible**: Legacy `masterSkillId` field still works
1641
+
1642
+ ### Migration Guide
1643
+
1644
+ #### For Existing Integrations
1645
+
1646
+ These fields are **optional**, so existing integrations continue to work without changes:
1647
+
1648
+ ```typescript
1649
+ // ✅ Existing code continues to work
1650
+ const result = await tasks.runTask({
1651
+ skillKey: 'tasks/security-risk-summary',
1652
+ executionType: ExecutionType.DIRECT,
1653
+ input: { assetId: 'a-123' },
1654
+ jobId: 'job-123',
1655
+ agentId: 'agent-1',
1656
+ // graphId and nodeId not required
1657
+ });
1658
+ ```
1659
+
1660
+ #### Adding Graph Support
1661
+
1662
+ To add graph execution support, simply include the optional fields:
1663
+
1664
+ ```typescript
1665
+ // ✅ Add graph context for better tracking
1666
+ const result = await tasks.runTask({
1667
+ skillKey: 'tasks/security-risk-summary',
1668
+ executionType: ExecutionType.DIRECT,
1669
+ input: { assetId: 'a-123' },
1670
+ jobId: 'job-123',
1671
+ agentId: 'agent-1',
1672
+ graphId: 'my-graph-id', // Add graph context
1673
+ nodeId: 'my-node-id', // Add node context (optional)
1674
+ });
1675
+ ```
1676
+
1677
+ #### Migrating from Legacy Fields
1678
+
1679
+ If you're using `masterSkillId`, you can optionally migrate to the new `graphId` field for clarity:
1680
+
1681
+ ```typescript
1682
+ // Before (still works)
1683
+ const result = await tasks.runTask({
1684
+ skillKey: 'tasks/security-risk-summary',
1685
+ executionType: ExecutionType.DIRECT,
1686
+ input: { assetId: 'a-123' },
1687
+ masterSkillId: 'graph-id',
1688
+ coreSkillId: 'node-id',
1689
+ });
1690
+
1691
+ // After (cleaner API)
1692
+ const result = await tasks.runTask({
1693
+ skillKey: 'tasks/security-risk-summary',
1694
+ executionType: ExecutionType.DIRECT,
1695
+ input: { assetId: 'a-123' },
1696
+ graphId: 'graph-id', // More explicit
1697
+ nodeId: 'node-id', // More explicit
1698
+ });
1699
+ ```
1700
+
1701
+ ### TypeScript Types
1702
+
1703
+ All graph execution fields are properly typed in the TypeScript definitions:
1704
+
1705
+ ```typescript
1706
+ interface RunTaskRequest {
1707
+ skillKey: string;
1708
+ agentId: string; // Required by @exellix/ai-skills
1709
+ jobTypeId: string;
1710
+ taskTypeId: string;
1711
+ input: Record<string, any> | string;
1712
+ executionType: ExecutionType;
1713
+ // ... other fields ...
1714
+ graphId?: string; // Graph execution context
1715
+ nodeId?: string; // Node identifier
1716
+ coreSkillId?: string; // Alternative node identifier
1717
+ masterSkillId?: string; // Legacy graph identifier (still supported)
1718
+ // ... other fields ...
1719
+ }
1720
+ ```
1721
+
1722
+ ### Related Documentation
1723
+
1724
+ - [Activity Tracking](#) - Main documentation with usage examples
1725
+ - [@woroces/ai-skills Graph Execution Support](https://github.com/woroces/ai-skills#graph-execution-support) - Skills-level documentation
1726
+ - [@x12i/ai-gateway Graph Execution Support](https://github.com/athenices/ai-gateway#graph-execution-support) - Gateway-level documentation
1727
+ - [@x12i/activix-tracking](https://github.com/athenices/ai-activities-tracking) - Activity tracking specification
1728
+
1729
+ ### Notes
1730
+
1731
+ - **`agentId` / `jobTypeId` / `taskTypeId` are required** on every `runTask` (see [`RUNTASK_REQUEST.md`](RUNTASK_REQUEST.md)).
1732
+ - Graph/node fields (`graphId`, `nodeId`, …) remain optional for non-graph runs — provide them when executing tasks in graphs.
1733
+ - Fields are automatically passed through to the skills client and gateway, then mapped to activity identity.
1734
+ - Multiple field naming conventions supported:
1735
+ - New fields: `graphId`/`nodeId`/`coreSkillId` (recommended for new code)
1736
+ - Legacy fields: `masterSkillId` (still supported for backward compatibility)
1737
+ - Mapping priority ensures consistent identity structure regardless of field naming.
1738
+ - Best practice: Include graph/node context fields for graph task execution for maximum traceability.
1739
+
1740
+ ---
1741
+
1742
+ ## API Reference
1743
+
1744
+ ### `WorexClientTasks`
1745
+
1746
+ Main class for task execution.
1747
+
1748
+ #### Constructor
1749
+
1750
+ ```typescript
1751
+ constructor(
1752
+ skillsClient: WorexClientSkills,
1753
+ executor: {
1754
+ execute<TParsed = any>(
1755
+ input: any,
1756
+ onNotFound: (key: string) => Promise<void>
1757
+ ): Promise<RunTaskResponse<TParsed>>;
1758
+ }
1759
+ )
1760
+ ```
1761
+
1762
+ #### Methods
1763
+
1764
+ ##### `runTask<TParsed>(input: RunTaskRequest): Promise<RunTaskResponse<TParsed>>`
1765
+
1766
+ Executes a task with task-level enrichment and context generation.
1767
+
1768
+ ```typescript
1769
+ const result = await tasks.runTask({
1770
+ skillKey: "tasks/security-risk-summary",
1771
+ agentId: "my-agent",
1772
+ jobTypeId: "security-risk-job",
1773
+ taskTypeId: "security-risk-task",
1774
+ // executionType is optional, defaults to ExecutionType.DIRECT
1775
+ input: { assetId: "a-123" },
1776
+ variables: { orgName: "Acme" },
1777
+ jobMemory: previousJobMemory,
1778
+ taskMemory: previousTaskMemory,
1779
+ executionMemory: previousExecutionMemory,
1780
+ jobId: "job-123"
1781
+ });
1782
+ ```
1783
+
1784
+ #### Local task registry
1785
+
1786
+ - **`registerLocalTask(skillKey: string, handler: LocalTaskHandler): void`** — Register a local task handler. When `runTask` is called with this `skillKey`, the handler runs instead of the normal enrichment/executor path.
1787
+ - **`getLocalTask(skillKey: string): LocalTaskHandler | undefined`** — Return the registered handler for `skillKey`, or `undefined`.
1788
+ - **`registerBuiltInLocalTasks(): void`** — Register built-in handlers (`skills/node.callExport`, `skills/node.callExportBatch`, `skills/skill.local:validateInput`, `skills/skill.local:normalizeNarrixResult`). The Narrix skill `skills/skill.local:narrixRun` (record/text/docs/chat) is registered via a side-effect import and is available when the package is loaded.
1789
+ - **`LocalTaskContext`** — Type: `skillKey`, `jobMemory`, `taskMemory`, `executionMemory`, `variables`, `xynthesized`, `smartInput`, `jobId`, `taskId`, `agentId`, `graphId`, `nodeId`, `prevNodeId`, `coreSkillId`, `masterSkillId`, `masterSkillActivityId`.
1790
+ - **`LocalTaskHandler`** — Type: `(args: { input: any; ctx: LocalTaskContext }) => Promise<any>`.
1791
+
1792
+ ### `TaskRequestBuilder`
1793
+
1794
+ Builder class for constructing task requests with a fluent API.
1795
+
1796
+ #### Methods
1797
+
1798
+ - `withSkillKey(skillKey: string): this` - Set the skill key
1799
+ - `withInput(input: Record<string, any> | string): this` - Set input data
1800
+ - `withExecutionStrategies(invocations: ExecutionStrategyInvocation[]): this` — MAIN FuncX wrappers (**required** on the built request; defaults to **`[]`** in **`build()`** when unset)
1801
+ - `withExecutionPipeline(steps: ExecutionStep[]): this` - Set execution pipeline (pre/main/post steps)
1802
+ - `withSynthesizedContextPreStep(modelOrConfig?: string | SynthesisConfig): this` - Add synthesized-context PRE step and set `includeContextInPrompt: true`. Pass a **`SynthesisConfig`** object to set **`contextSourcePolicy`**, **`webEvidence`**, **`memoryPaths`**, etc.
1803
+ - `withAuditPostStep(config: AuditConfig): this` - Add audit POST step (quality-gate loop)
1804
+ - `withPolishPostStep(config: PolishConfig): this` - Add polish POST step (refinement checklist)
1805
+ - `withVariables(variables: Record<string, any>): this` - Set contextual variables
1806
+ - `withContext(context: Record<string, any> | string): this` - Set context
1807
+ - `withKnowledge(knowledge: Record<string, any>): this` - Set knowledge data
1808
+ - `withJobMemory(jobMemory: JobHistory): this` - Set job memory
1809
+ - `withTaskMemory(taskMemory: TaskHistory): this` - Set task memory
1810
+ - `withExecutionMemory(executionMemory: ExecutionHistory): this` - Set execution memory
1811
+ - `withJobId(jobId: string): this` - Set job ID
1812
+ - `withAgentId(agentId: string): this` - Set agent ID
1813
+ - `withJobTypeId(jobTypeId: string): this` - Set job type id (**required** before `build()`)
1814
+ - `withTaskTypeId(taskTypeId: string): this` - Set task type id (**required** before `build()`)
1815
+ - `withAiSkillsCorrelation(agentId: string, jobTypeId: string, taskTypeId: string): this` - Set all three correlation ids (recommended)
1816
+ - `withGraphId(graphId: string): this` - Set graph identifier
1817
+ - `withNodeId(nodeId: string): this` - Set node identifier
1818
+ - `withPrevNodeId(prevNodeId: string): this` - Set previous node identifier
1819
+ - `withModelConfig(modelConfig: ModelConfig): this` - Set model configuration (model, temperature, maxTokens, etc.)
1820
+ - `withTemplateRenderOptions(templateRenderOptions: TemplateRenderOptions): this` - Per-task gateway / parser template overrides (v4)
1821
+ - `withTemplateTokens(templateTokens: GatewayTemplateTokens): this` - Per-task `templateTokens` overlay
1822
+ - `withTimeout(timeoutMs: number): this` - Set timeout
1823
+ - `withXynthesized(memory: XynthesizedMemory): this` — optional **`xynthesized`** bucket (**`job`** / **`task`** / **`execution`** records)
1824
+ - `withSmartInput(config: RunTaskSmartInput): this` — passthrough **`smartInput`** (Rendrix **`paths`** or legacy **`string[]`**)
1825
+ - `withSmartInputPaths(paths: string[]): this` — builds **`smartInput: { paths: paths.map(p => ({ title: p, path: p })) }`**
1826
+ - `withSmartInputRenderOptions(opts: SmartInputRenderOptions): this` — sets **`smartInputRenderOptions`** on the request (gateway/Rendrix merge)
1827
+ - `withXynthesizedJob(key: string, value: unknown): this` / `withXynthesizedTask(key: string, value: unknown): this` / `withXynthesizedExecution(key: string, value: unknown): this` — merge a single key into **`xynthesized.job`** / **`xynthesized.task`** / **`xynthesized.execution`**
1828
+ - `build(): RunTaskRequest` - Build the final request (**`executionStrategies`** defaults to **`[]`** when unset)
1829
+
1830
+ #### Example
1831
+
1832
+ ```typescript
1833
+ import { TaskRequestBuilder } from "@exellix/ai-tasks";
1834
+
1835
+ const request = new TaskRequestBuilder()
1836
+ .withSkillKey("tasks/my-task")
1837
+ .withInput({ data: "process" })
1838
+ .withAiSkillsCorrelation("my-agent", "my-job-type", "my-task-type")
1839
+ .withExecutionStrategies([]) // [] = plain MAIN; omit only if you rely on build() defaulting strategies to []
1840
+ .withModelConfig({
1841
+ model: "gpt-5",
1842
+ temperature: 0.7,
1843
+ maxTokens: 2000
1844
+ })
1845
+ .withJobMemory(jobMemory)
1846
+ .withTaskMemory(taskMemory)
1847
+ .withExecutionMemory(executionMemory)
1848
+ .withJobId("job-123")
1849
+ .build();
1850
+
1851
+ const result = await tasks.runTask(request);
1852
+ ```
1853
+
1854
+ ---
1855
+
1856
+ ## Types
1857
+
1858
+ ### `RunTaskRequest`
1859
+
1860
+ ```typescript
1861
+ interface RunTaskRequest {
1862
+ skillKey: string; // "tasks/security-risk-summary"
1863
+ agentId: string; // Required — @exellix/ai-skills correlation
1864
+ jobTypeId: string; // Required — catalog / telemetry job classification
1865
+ taskTypeId: string; // Required — catalog / telemetry task classification
1866
+ /** Required: MAIN FuncX wrappers; use [] for plain gateway MAIN (see BREAKING-CHANGES / RUNTASK_REQUEST). */
1867
+ executionStrategies: ExecutionStrategyInvocation[];
1868
+ /** Optional guarded metadata rows from catalogId `execution-strategy`; code validation remains authoritative. */
1869
+ executionStrategyCatalogItems?: TaskStrategyItemData[];
1870
+ input: Record<string, any> | string; // Task payload (canonical user/host data)
1871
+ executionType?: ExecutionType | string; // Optional; DIRECT default when using plain MAIN; narrix-then-direct when configured
1872
+ narrixInput?: NarrixRunInput | { $path: string }; // Required when narrixMode infers handler without preprocessor narrix
1873
+ narrixScope?: NarrixScope; // When set with narrix handler path, filters which signals/stories go to task memory
1874
+ /** Task-level NARRIX pre-processor: datasetId, enableWebScope, webScope*, webScoping (see [NARRIX task-level pre-processor](#narrix-task-level-pre-processor)). */
1875
+ narrix?: NarrixPreProcessorConfig;
1876
+ includeContextInPrompt?: boolean; // Opt-in: when true, task-scoped context is added to the prompt; default is false (no context)
1877
+
1878
+ /** Optional: pre / main / post steps. When set (non-empty), runs PRE (e.g. synthesized-context), one MAIN, then POST. See [Execution pipeline](#execution-pipeline-and-synthesized-context-pre-step). */
1879
+ executionPipeline?: ExecutionStep[];
1880
+
1881
+ /** executionMode: "trace" — ordered debugTrace + richer LLM observability (see [Trace mode](#trace-mode-authoritative-ordered-debug-trace)). */
1882
+ executionMode?: "default" | "trace";
1883
+
1884
+ /** Merged into template variables before explicit variables (MAIN path). See mergeSkillTemplateVariables. */
1885
+ jobContext?: Record<string, unknown>;
1886
+
1887
+ /** Job-, task-, and execution-scoped synthesized buckets for graph execution (distinct from raw memories). */
1888
+ xynthesized?: XynthesizedMemory;
1889
+
1890
+ /** Rendrix smart-input paths or legacy `paths: string[]` (normalized before MAIN); root allows only `paths`. */
1891
+ smartInput?: RunTaskSmartInput;
1892
+ /** Optional Rendrix options for the `{{smartInput}}` macro (merged on gateway like `RunSkillRequest`). */
1893
+ smartInputRenderOptions?: SmartInputRenderOptions;
1894
+
1895
+ // Optional fields
1896
+ variables?: Record<string, any>; // Contextual variables
1897
+ context?: Record<string, any> | string;
1898
+ knowledge?: Record<string, any>;
1899
+ jobMemory?: JobHistory; // History of previous task results
1900
+ taskMemory?: TaskHistory; // History of skills executed
1901
+ executionMemory?: ExecutionHistory; // History of execution context (execution-level memory)
1902
+ modelConfig?: ModelConfig; // Model configuration (model, temperature, maxTokens, etc.)
1903
+ jobId?: string;
1904
+
1905
+ // Graph execution context (optional)
1906
+ graphId?: string; // Graph identifier for activity tracking
1907
+ nodeId?: string; // Node identifier for activity tracking
1908
+ coreSkillId?: string; // Alternative node identifier (maps to identity.nodeId)
1909
+
1910
+ // Legacy fields (still supported for backward compatibility)
1911
+ masterSkillId?: string; // Legacy graph identifier (maps to identity.graphId)
1912
+ masterSkillActivityId?: string; // Preserved for skill hierarchies
1913
+
1914
+ timeoutMs?: number;
1915
+
1916
+ /** Per-invoke template parser options (merged on gateway defaults). See [Gateway template rendering (v4)](#gateway-template-rendering-v4). */
1917
+ templateRenderOptions?: TemplateRenderOptions;
1918
+ /** Highest-priority template token overlay for this invoke. */
1919
+ templateTokens?: GatewayTemplateTokens;
1920
+ }
1921
+ ```
1922
+
1923
+ `NarrixPreProcessorConfig` is exported from this package; **`webScoping`** matches **`WebScoperConfig["scoping"]`** in **`@exellix/narrix-web-scoper`** (snippet toggles, caps, `snippetIncludeRawContent`, `sourceExcerptFrom`, query limits, etc.).
1924
+
1925
+ The closed JSON schema for hosts and codegen is **[documenations/schemas/v1/run-task-request.json](./documenations/schemas/v1/run-task-request.json)** (includes **`xynthesized`** and **`smartInput`**). Narrative contract: **[RUNTASK_REQUEST.md](./RUNTASK_REQUEST.md)**. For **smart input**, the **TypeScript** types exported from this package (**`RunTaskSmartInput`**, **`SmartInputConfig`**) are authoritative; the JSON schema / RUNTASK narrative may still emphasize string **`paths`** only until updated to Rendrix **`{ title, path }`** entries.
1926
+
1927
+ `TemplateRenderOptions` and `GatewayTemplateTokens` are re-exported from this package (see [Gateway template rendering (v4)](#gateway-template-rendering-v4)).
1928
+
1929
+ ### `RunTaskResponse<TParsed>`
1930
+
1931
+ ai-tasks extends the skills response with optional **`identity`**, **`xynthesizedPatch`** (**`job`**, **`task`**, and/or **`execution`** writes from PRE synthesis when configured), **`intermediateSteps`**, **`debugTrace`** (when **`executionMode: "trace"`**), and observability-related **`metadata`** extensions. Underlying shape:
1932
+
1933
+ ```typescript
1934
+ type RunTaskResponse<TParsed = any> = RunSkillResponse<TParsed> & {
1935
+ /** When downstream returns smart-input diagnostics markdown + resolved paths. */
1936
+ smartInputRenderResult?: SmartInputRenderResult;
1937
+ identity?: Record<string, unknown>;
1938
+ xynthesizedPatch?: XynthesizedMemory;
1939
+ intermediateSteps?: IntermediateStep[];
1940
+ debugTrace?: { tasks: DebugTraceTask[] };
1941
+ aiTasksObservability?: AiTasksObservabilityDigest; // usually attached by orchestrators, not by default
1942
+ };
1943
+
1944
+ interface RunSkillResponse<TParsed = any> {
1945
+ skillKey: string;
1946
+ rawText: string;
1947
+ flexMd: {
1948
+ frame: string;
1949
+ payloads: Record<string, string>;
1950
+ };
1951
+ parsed: TParsed;
1952
+ metadata: {
1953
+ instructionVersion?: string;
1954
+ activityId?: string;
1955
+ durationMs?: number; // Present for local tasks (execution tracing)
1956
+ localSkillKey?: string; // Present for local tasks (e.g. "skills/node.callExportBatch")
1957
+ narrix?: { // Present when narrix-then-direct ran and Narrix succeeded (or failed; see parsed.phase)
1958
+ entity: { entityKind: string; entityKey: string };
1959
+ signals: unknown[];
1960
+ stories: unknown[];
1961
+ meta: Record<string, unknown>;
1962
+ durationMs?: number;
1963
+ };
1964
+ };
1965
+ }
1966
+ ```
1967
+
1968
+ Task responses **may** include an optional top-level **`intermediateSteps`** (array of `IntermediateStep`) for combined/multi-step tasks; see [Intermediate steps (multi-step tasks)](#intermediate-steps-multi-step-tasks).
1969
+
1970
+ ### `IntermediateStep`, `IntermediateSteps`, `RunTaskResponseWithSteps`
1971
+
1972
+ For multi-step tasks, use the extended response type and step shape:
1973
+
1974
+ ```typescript
1975
+ import type { IntermediateStep, IntermediateSteps, RunTaskResponseWithSteps } from "@exellix/ai-tasks";
1976
+
1977
+ interface IntermediateStep {
1978
+ step: number; // 1-based order
1979
+ id: string; // e.g. "to-cni", "engine-enrich"
1980
+ ok: boolean;
1981
+ summary?: string;
1982
+ error?: string;
1983
+ inputSummary?: Record<string, unknown> | string;
1984
+ outputExcerpt?: Record<string, unknown>;
1985
+ }
1986
+
1987
+ type IntermediateSteps = IntermediateStep[];
1988
+
1989
+ interface RunTaskResponseWithSteps<TParsed = any> extends RunSkillResponse<TParsed> {
1990
+ intermediateSteps?: IntermediateSteps;
1991
+ }
1992
+ ```
1993
+
1994
+ See [documenations/intermediate-steps.md](./documenations/intermediate-steps.md) for full documentation and examples.
1995
+
1996
+ ### `ExecutionPhase`, `ExecutionStep`, `SynthesisConfig`, `ContextSourcePolicy`, `WebEvidenceConfig`, structured synthesis types
1997
+
1998
+ Used with **`executionPipeline`** and the **`synthesized-context`** PRE step. Import from **`@exellix/ai-tasks`** (and use **`SYNTHESIZED_CONTEXT`** for the PRE step `type` string):
1999
+
2000
+ ```typescript
2001
+ import type {
2002
+ ExecutionPhase,
2003
+ ExecutionStep,
2004
+ ExecutionStrategyInvocation,
2005
+ SynthesisConfig,
2006
+ ContextSourcePolicy,
2007
+ WebEvidenceConfig,
2008
+ SynthesisInput,
2009
+ SynthesizedPromptPayload,
2010
+ SynthesizedContext,
2011
+ SynthesizedItem,
2012
+ ContextSynthesizer,
2013
+ XynthesizedMemory,
2014
+ SmartInputConfig,
2015
+ SmartInputRenderOptions,
2016
+ SmartInputRenderResult,
2017
+ RunTaskSmartInput,
2018
+ } from "@exellix/ai-tasks";
2019
+ import { SYNTHESIZED_CONTEXT } from "@exellix/ai-tasks";
2020
+
2021
+ // ExecutionPhase = "pre" | "main" | "post"
2022
+ // ExecutionStep: { phase, type: SYNTHESIZED_CONTEXT | "direct" | "audit" | "polish", config? }
2023
+ // SynthesisConfig: … synthesisOutputFormat?: "markdown" | "structured", questionPath?, getQuestion?, structuredMaxItemsPerSide?, …
2024
+ // ContextSourcePolicy: "auto" | "narrix-web" | "narrix-web-memory" | "memory-web" | "memory-only" | "narrix-only" | "narrix+memory"
2025
+ // WebEvidenceConfig: preferCleanContent?, maxSources?, dedupeByUrl?, maxTotalChars?
2026
+ ```
2027
+
2028
+ Semantics and the full policy table: [Execution pipeline and synthesized-context](#execution-pipeline-and-synthesized-context-pre-step).
2029
+
2030
+ ### `ExecutionType`
2031
+
2032
+ ```typescript
2033
+ enum ExecutionType {
2034
+ DIRECT = "direct"
2035
+ }
2036
+ ```
2037
+
2038
+ The package also exports the constant `NARRIX_THEN_DIRECT = "narrix-then-direct"` for use with `executionType` when running Narrix first, then the executor. See [Narrix then execute](#narrix-then-execute).
2039
+
2040
+ ### `NarrixScope`
2041
+
2042
+ Optional filter for which signals and stories from Narrix are written to task memory (used with `executionType: "narrix-then-direct"`):
2043
+
2044
+ ```typescript
2045
+ interface NarrixScope {
2046
+ includeSignals?: string[]; // Signal codes to keep (allowlist)
2047
+ excludeSignals?: string[]; // Signal codes to drop (blocklist; ignored when includeSignals is set)
2048
+ includeStories?: string[]; // narrativeTypeIds to keep (allowlist)
2049
+ excludeStories?: string[]; // narrativeTypeIds to drop (blocklist; ignored when includeStories is set)
2050
+ }
2051
+ ```
2052
+
2053
+ ### `NarrixPreProcessorConfig`
2054
+
2055
+ Exported type for **`RunTaskRequest.narrix`**. Includes **`datasetId`**, optional **`attachToField`**, **`enableWebScope`**, **`webScopeTemplates`** / **`webScopeQuestionTemplate`** / **`webScopeObjects`** / **`webScopeQuestions`**, and **`webScoping`** (the same shape as **`WebScoperConfig["scoping"]`** in **`@exellix/narrix-web-scoper`** — snippets, caps, raw content, query limits, etc.). See [NARRIX task-level pre-processor](#narrix-task-level-pre-processor) and [documenations/web-scoping-in-ai-tasks.md](./documenations/web-scoping-in-ai-tasks.md).
2056
+
2057
+ ```typescript
2058
+ import type { NarrixPreProcessorConfig } from "@exellix/ai-tasks";
2059
+ ```
2060
+
2061
+ ### `ModelConfig`
2062
+
2063
+ Model configuration for per-request model selection and generation parameters:
2064
+
2065
+ ```typescript
2066
+ import type { ModelConfig } from "@exellix/ai-tasks";
2067
+
2068
+ interface ModelConfig {
2069
+ model?: string; // Model identifier (e.g., "gpt-5")
2070
+ modelId?: string; // Provider-specific model ID
2071
+ provider?: string; // Provider name (e.g., "openai", "anthropic")
2072
+ temperature?: number; // 0.0 to 2.0 - Controls randomness
2073
+ maxTokens?: number; // Maximum tokens to generate
2074
+ topP?: number; // 0.0 to 1.0 - Nucleus sampling
2075
+ frequencyPenalty?: number; // -2.0 to 2.0
2076
+ presencePenalty?: number; // -2.0 to 2.0
2077
+ stop?: string[]; // Stop sequences
2078
+ [key: string]: any; // Additional provider-specific parameters
2079
+ }
2080
+ ```
2081
+
2082
+ **Example:**
2083
+ ```typescript
2084
+ const modelConfig: ModelConfig = {
2085
+ model: "gpt-5",
2086
+ temperature: 0.7,
2087
+ maxTokens: 2000,
2088
+ topP: 0.9
2089
+ };
2090
+
2091
+ const result = await runTask({
2092
+ skillKey: "tasks/analysis",
2093
+ executionType: ExecutionType.DIRECT,
2094
+ input: { data: "analyze" },
2095
+ modelConfig
2096
+ });
2097
+ ```
2098
+
2099
+ **Note:** `ModelConfig` is re-exported from `@woroces/ai-skills`. If not provided, uses gateway/router defaults from client initialization.
2100
+
2101
+ ### `JobHistory`, `TaskHistory`, and `ExecutionHistory`
2102
+
2103
+ History objects for managing execution context across workflows:
2104
+
2105
+ ```typescript
2106
+ import type { JobHistory, TaskHistory, ExecutionHistory } from "@exellix/ai-tasks";
2107
+
2108
+ // JobHistory: Contains history of all previous task results in the job
2109
+ const jobMemory: JobHistory = {
2110
+ // Previous task results
2111
+ };
2112
+
2113
+ // TaskHistory: Contains history of skills executed up to this task
2114
+ const taskMemory: TaskHistory = {
2115
+ // Previous skill executions
2116
+ };
2117
+
2118
+ // ExecutionHistory: Contains history of execution context (execution-level memory)
2119
+ const executionMemory: ExecutionHistory = {
2120
+ // Execution state, intermediate results, execution-level variables
2121
+ previousAttempts: 2,
2122
+ lastError: "timeout",
2123
+ intermediateResults: { step1: "completed" }
2124
+ };
2125
+ ```
2126
+
2127
+ - `JobHistory` and `TaskHistory` are re-exported from `@x12i/execution-memory-manager` for convenience
2128
+ - `ExecutionHistory` is defined in this package as `Record<string, any>` for execution-level memory
2129
+
2130
+ See [HISTORY-OBJECTS.md](./HISTORY-OBJECTS.md) for detailed documentation on history objects and [HISTORY-OBJECTS-DOWNSTREAM.md](./HISTORY-OBJECTS-DOWNSTREAM.md) for information on how they flow downstream.
2131
+
2132
+ ---
2133
+
2134
+ ## Content Registry Structure (v2.0.2+)
2135
+
2136
+ To ensure robust resolution with `@x12i/ai-gateway` v7.0.0+, it is recommended to provide both `.instructions` and `.prompt` files for each skill in your `.metadata/skills` directory.
2137
+
2138
+ - **`{skillId}.instructions`**: Contains the system instructions (persona, constraints, output format).
2139
+ - **`{skillId}.prompt`**: Contains the user prompt template (e.g., `{{input}}`). With gateway template **v4**, `{{input}}` is supplied from merged working memory (see [Gateway template rendering (v4)](#gateway-template-rendering-v4)); use **`templateRenderOptions.subPathSearch`** when templates need paths like **`{{execution.summary}}`** backed by nested task input.
2140
+
2141
+ See [SKILL-CONTENT-GUIDE.md](./SKILL-CONTENT-GUIDE.md) for a detailed migration and implementation guide.
2142
+
2143
+ ---
2144
+
2145
+ ## Environment Variables
2146
+
2147
+ See `.env.example` for complete configuration. Key variables:
2148
+
2149
+ **Used by ai-tasks:**
2150
+
2151
+ - `MONGO_LOGS_DB` - [OPTIONAL] Default database for activity logs (defaults to `logs-db`), passed as `bindingDefaultsDb` to the skills client.
2152
+
2153
+ **POST step (shared):**
2154
+
2155
+ - `POST_STEP_MODEL` - [OPTIONAL] Fallback model for all POST step LLM calls when no step-specific env is set.
2156
+ - `POST_STEP_TIMEOUT_MS` - [OPTIONAL] Fallback timeout in ms for POST step LLM calls (default `30000`).
2157
+
2158
+ **Audit POST step:**
2159
+
2160
+ - `AUDIT_MODEL` - [OPTIONAL] Model for audit evaluator (→ `POST_STEP_MODEL` → gateway default).
2161
+ - `AUDIT_SYNTHESIS_MODEL` - [OPTIONAL] Model for synthesis merge when `selectionStrategy: "synthesis"` (→ `POST_STEP_MODEL` → MAIN step model).
2162
+ - `AUDIT_TIMEOUT_MS` - [OPTIONAL] Timeout per audit LLM call (→ `POST_STEP_TIMEOUT_MS` → `30000`).
2163
+ - `AUDIT_TEMPLATES_PATH` - [OPTIONAL] Base path for audit templates (default `<project>/templates/post-steps/audit`).
2164
+
2165
+ **Polish POST step:**
2166
+
2167
+ - `POLISH_MODEL` - [OPTIONAL] Model for polish LLM (→ `POST_STEP_MODEL` → gateway default).
2168
+ - `POLISH_TIMEOUT_MS` - [OPTIONAL] Timeout per polish call (→ `POST_STEP_TIMEOUT_MS` → `30000`).
2169
+ - `POLISH_TEMPLATES_PATH` - [OPTIONAL] Base path for polish templates (default `<project>/templates/post-steps/polish`).
2170
+
2171
+ **Required for content and providers (validated by ai-skills):**
2172
+
2173
+ - `GITHUB_TOKEN` - [REQUIRED] GitHub token for content registry access
2174
+ - `GITHUB_REPO_URL` - [REQUIRED] Content registry repository URL
2175
+ - `OPENAI_API_KEY` or `GROK_API_KEY` - [REQUIRED] AI provider key (or `OPEN_ROUTER_KEY` / `OPENROUTER_API_KEY` if ai-skills accepts OpenRouter-only)
2176
+
2177
+ **Downstream (used by ai-skills → ai-gateway → ai-provider-router, ai-activities-tracking):** ai-tasks does not read these; they affect LLM and activity tracking when using this package.
2178
+
2179
+ - `OPEN_ROUTER_KEY` or `OPENROUTER_API_KEY` - [OPTIONAL] Use OpenRouter for LLM calls; no provider registration needed when set.
2180
+ - `MONGO_LOGS_COLLECTION` - [OPTIONAL] Activities collection (default in ai-activities-tracking: `cognitive-activities`).
2181
+ - `MONGO_BAD_REQUESTS_COLLECTION` - [OPTIONAL] Bad-requests collection (default: `ai-bad-requests`).
2182
+
2183
+ **Synthesis (synthesized-context PRE step):**
2184
+
2185
+ - `SYNTHESIS_MODEL` - [OPTIONAL] Default model for synthesis (e.g. `gpt-5-nano`).
2186
+ - `SYNTHESIS_TIMEOUT_MS` - [OPTIONAL] Timeout for synthesis call (default 30000).
2187
+ - `SYNTHESIS_MAX_OUTPUT_LENGTH` - [OPTIONAL] Max length of synthesis output (characters).
2188
+ - `SYNTHESIS_TEMPLATES_PATH` - [OPTIONAL] Base path for `templates/synthesis/system.md` and `user.txt` (default: project root).
2189
+
2190
+ **Other:**
2191
+
2192
+ - `MONGODB_URI` - [OPTIONAL] MongoDB for context enrichment
2193
+ - `CONTENT_REGISTRY_LOCAL_PATH` - [OPTIONAL] Local registry path
2194
+ - `USE_NARRIX_INGEST` - [OPTIONAL] Set to `"1"` to enable the Narrix pipeline for `skills/skill.local:narrixRun` (record/text/docs/chat). When unset, the handler returns `NARRIX_DISABLED`.
2195
+ - `NARRIX_DEBUG` - [OPTIONAL] Set to `"1"` to log datasetId, medium, packId, entityKind, signal codes, and narrativeTypeIds when the Narrix handler runs.
2196
+ - `TAVILY_API_KEY` - [OPTIONAL] Required when using **`narrix.enableWebScope`** (web scoping via `@exellix/narrix-web-scoper` + `@exellix/search-adapter` / Tavily). When missing or invalid, `executionMemory.webContext` reflects a miss or error; the NARRIX task still completes.
2197
+ - `WEB_CONTEXT_MARKDOWN_MAX_CHARS` - [OPTIONAL] Positive integer: max Unicode length of the **Web sources** markdown block appended to task context / synthesis source material (tail truncated with a marker). Omit for no downstream cap (upstream **`maxTotalWebContextChars`** / **`maxSnippetCharsPerSource`** still apply in the scoper).
2198
+
2199
+ For a concise breakdown of what ai-tasks uses vs downstream, see [documenations/downstream-environment.md](./documenations/downstream-environment.md).
2200
+
2201
+ ---
2202
+
2203
+ ## Examples
2204
+
2205
+ ### Basic Usage
2206
+
2207
+ ```typescript
2208
+ import { WorexClientSkills } from "@woroces/ai-skills";
2209
+ import { WorexClientTasks, ExecutionType } from "@exellix/ai-tasks";
2210
+
2211
+ const skills = new WorexClientSkills();
2212
+ const executor = { /* get from skills client */ };
2213
+ const tasks = new WorexClientTasks(skills, executor);
2214
+
2215
+ const result = await tasks.runTask({
2216
+ skillKey: "tasks/security-risk-summary",
2217
+ executionType: ExecutionType.DIRECT,
2218
+ input: { assetId: "a-123" },
2219
+ variables: { orgName: "MyOrganization" },
2220
+ modelConfig: {
2221
+ model: "gpt-5",
2222
+ temperature: 0.7
2223
+ }
2224
+ });
2225
+
2226
+ console.log(result.flexMd.payloads);
2227
+ ```
2228
+
2229
+ ### With Memory Management
2230
+
2231
+ ```typescript
2232
+ import { WorexClientSkills } from "@woroces/ai-skills";
2233
+ import { WorexClientTasks, ExecutionType } from "@exellix/ai-tasks";
2234
+ import type { JobHistory, TaskHistory, ExecutionHistory } from "@exellix/ai-tasks";
2235
+
2236
+ const skills = new WorexClientSkills();
2237
+ const executor = { /* get from skills client */ };
2238
+ const tasks = new WorexClientTasks(skills, executor);
2239
+
2240
+ const jobMemory: JobHistory = {
2241
+ // Previous task results
2242
+ };
2243
+
2244
+ const taskMemory: TaskHistory = {
2245
+ // Previous skill executions
2246
+ };
2247
+
2248
+ const executionMemory: ExecutionHistory = {
2249
+ previousAttempts: 1,
2250
+ intermediateResults: { analysis: "in-progress" }
2251
+ };
2252
+
2253
+ const result = await tasks.runTask({
2254
+ skillKey: "tasks/professional-decision",
2255
+ executionType: ExecutionType.DIRECT,
2256
+ input: { scenario: "Should we migrate to cloud?" },
2257
+ variables: { orgName: "EnterpriseCorp" },
2258
+ jobMemory,
2259
+ taskMemory,
2260
+ executionMemory,
2261
+ jobId: "job-456"
2262
+ });
2263
+ ```
2264
+
2265
+ ### With Model Configuration
2266
+
2267
+ ```typescript
2268
+ import { WorexClientSkills } from "@woroces/ai-skills";
2269
+ import { WorexClientTasks, ExecutionType } from "@exellix/ai-tasks";
2270
+
2271
+ const skills = new WorexClientSkills();
2272
+ const executor = { /* get from skills client */ };
2273
+ const tasks = new WorexClientTasks(skills, executor);
2274
+
2275
+ // Configure model per-request (@exellix/ai-skills correlation ids required)
2276
+ const result = await tasks.runTask({
2277
+ skillKey: "tasks/analysis",
2278
+ agentId: "agent-1",
2279
+ jobTypeId: "analysis-job",
2280
+ taskTypeId: "analysis-task",
2281
+ executionStrategies: [],
2282
+ executionType: ExecutionType.DIRECT,
2283
+ input: { data: "analyze this" },
2284
+ modelConfig: {
2285
+ model: "gpt-5",
2286
+ temperature: 0.7,
2287
+ maxTokens: 2000,
2288
+ topP: 0.9
2289
+ }
2290
+ });
2291
+
2292
+ // Or using the builder
2293
+ import { TaskRequestBuilder } from "@exellix/ai-tasks";
2294
+
2295
+ const request = new TaskRequestBuilder()
2296
+ .withSkillKey("tasks/analysis")
2297
+ .withInput({ data: "analyze this" })
2298
+ .withAiSkillsCorrelation("agent-1", "analysis-job", "analysis-task")
2299
+ .withExecutionStrategies([])
2300
+ .withModelConfig({
2301
+ model: "gpt-5",
2302
+ temperature: 0.5,
2303
+ maxTokens: 4000
2304
+ })
2305
+ .build();
2306
+
2307
+ const result2 = await tasks.runTask(request);
2308
+ ```
2309
+
2310
+ ### With Graph Execution Context
2311
+
2312
+ ```typescript
2313
+ import { WorexClientSkills } from "@woroces/ai-skills";
2314
+ import { WorexClientTasks, ExecutionType } from "@exellix/ai-tasks";
2315
+
2316
+ const skills = new WorexClientSkills();
2317
+ const executor = { /* get from skills client */ };
2318
+ const tasks = new WorexClientTasks(skills, executor);
2319
+
2320
+ // Execute a task within a graph workflow
2321
+ const result = await tasks.runTask({
2322
+ skillKey: "tasks/security-risk-summary",
2323
+ executionType: ExecutionType.DIRECT,
2324
+ input: { assetId: "a-123" },
2325
+ variables: { orgName: "TechCorp" },
2326
+ modelConfig: {
2327
+ model: "gpt-5",
2328
+ temperature: 0.7
2329
+ },
2330
+ jobId: "job-123",
2331
+ agentId: "agent-1",
2332
+ graphId: "workflow-data-processing", // Graph identifier for activity tracking
2333
+ nodeId: "extract-emails-node" // Node identifier for activity tracking
2334
+ });
2335
+
2336
+ console.log(result.flexMd.payloads);
2337
+ // Graph and node context are automatically passed through to activity tracking
2338
+ ```
2339
+
2340
+ ---
2341
+
2342
+ ## Activix — task activity tracking
2343
+
2344
+ `@xronoces/activix` is used here for multi-phase activity tracking: each `runTask()` invocation writes several phase records to MongoDB (`local`, `narrix`, `pipeline_pre`, `direct`, `audit`, `polish`, `narrix_then_direct`). All phase records share the same `correlationId`, so you can group them back into one logical task run.
2345
+
2346
+ **Best practices checklist:** see [documenations/activix.md](./documenations/activix.md) (includes an operational integration checklist + deeper references under `.docs/`).
2347
+
2348
+ ### Enable / disable
2349
+
2350
+ ```
2351
+ ACTIVIX_ENABLED=false # set true to activate
2352
+ ```
2353
+
2354
+ When `false` (the default), `runTask()` runs exactly as today with zero overhead. All activix calls are non-fatal — a MongoDB outage will never surface to the `runTask()` caller.
2355
+
2356
+ ### Configuration
2357
+
2358
+ | Variable | Default | Description |
2359
+ |---|---|---|
2360
+ | `ACTIVIX_ENABLED` | `false` | Master toggle |
2361
+ | `ACTIVIX_DB` | `MONGO_LOGS_DB` | MongoDB database for activity records |
2362
+ | `ACTIVIX_COLLECTION` | `task-activities` | Collection name |
2363
+ | `ACTIVIX_STALE_TTL_MS` | `300000` | TTL (ms) before a stuck `started` record is marked `timeout` |
2364
+ | `ACTIVIX_LOGS_LEVEL` | _(see below)_ | Canonical internal log threshold for injected Activix logger (`@x12i/logxer`). If unset, falls back to `ACTIVIX_LOG_LEVEL`, then default `warn`. Values: `verbose` \| `debug` \| `info` \| `warn` \| `error` \| `off` / `silent` / `none`. |
2365
+ | `ACTIVIX_LOG_LEVEL` | `warn` | Legacy alias when `ACTIVIX_LOGS_LEVEL` is unset (same resolution rules as `@x12i/logxer` `resolvePackageLogsLevel`). |
2366
+
2367
+ ### What is recorded
2368
+
2369
+ Each record in `ACTIVIX_COLLECTION` captures:
2370
+
2371
+ - `correlationId`, `phase` — join key for grouping phase records into one `runTask()` invocation
2372
+ - `runContext` (Activix 6+) — correlation envelope: `sessionId` matches `correlationId`, plus task fields that used to be duplicated as a top-level `identity` blob (`taskId`, `skillId`, etc.) now live here only
2373
+ - `outer` (Activix 6+) — required activity I/O tier (`input` / `output` / `metadata`); phase tracking uses minimal placeholders until completion metadata is merged
2374
+ - `skillKey`, `executionType`, `jobId`, `agentId`, `graphId`, `nodeId` — from the request
2375
+ - `hasPipeline`, `hasNarrix`, `hasNarrixInput` — execution path flags
2376
+ - `status` — `started` → `completed` | `failed` | `timeout`
2377
+ - `startTime`, `endTime`, `duration` — auto-managed by activix
2378
+ - `instructionVersion`, `durationMs`, `stepCount` — from the response on success
2379
+ - `error` / `errorMessage`, `errorCode` — on failure
2380
+
2381
+ ### Stale record cleanup
2382
+
2383
+ Crashed or hung tasks leave records stuck in `started`. Call `ax.markStaleRecords()` from a periodic job (e.g. every 5 minutes) in the host process to flip them to `timeout`.
2384
+
2385
+ **Full integration plan → [documenations/activix.md](./documenations/activix.md)**
2386
+
2387
+ ---
2388
+
2389
+ ## Development Notes
2390
+
2391
+ - This is a **private package**; we optimize for reuse and speed.
2392
+ - `@woroces/ai-skills` is treated as the shared helper + execution layer.
2393
+ - Naming leakage is explicitly acceptable (private monorepo + private packages).
2394
+ - Task-level enrichment always uses `level: 'task'` (not `'skill'`).
2395
+ - The executor must be a "no-enrichment execute primitive" to avoid double-enrichment.
2396
+
2397
+ ---
2398
+
2399
+ ## Troubleshooting
2400
+
2401
+ ### "Content not found" Error
2402
+
2403
+ - Verify skill key format: `"tasks/security-risk-summary"` (not `"security-risk-summary"`)
2404
+ - Check content registry contains the task
2405
+ - Verify `GITHUB_TOKEN` and `GITHUB_REPO_URL` are set correctly
2406
+
2407
+ ### "Repository not accessible"
2408
+
2409
+ - Verify `GITHUB_TOKEN` has `repo` scope for private repos
2410
+ - Check `GITHUB_REPO_URL` points to an existing repository
2411
+
2412
+ ### "Environment validation failed"
2413
+
2414
+ - Ensure at least one AI provider key is set (`OPENAI_API_KEY` or `GROK_API_KEY`)
2415
+ - Verify `GITHUB_TOKEN` and `GITHUB_REPO_URL` are set
2416
+ - Check `.env` file is loaded (if using `dotenv`)
2417
+
2418
+ ---
2419
+
2420
+ ## Development
2421
+
2422
+ ```bash
2423
+ # Install dependencies (requires @exellix scope registry/auth for narrix packages)
2424
+ npm install
2425
+
2426
+ # Build
2427
+ npm run build
2428
+
2429
+ # Run tests (all narrix tests under test/narrix/; requires build first)
2430
+ npm test
2431
+
2432
+ # Run full Narrix integration tests (record, text, docs, chat + 14 record scenarios)
2433
+ npm run test:with-narrix-ingest
2434
+ # Bash/macOS/Linux alternative: USE_NARRIX_INGEST=1 npm test
2435
+ # PowerShell: $env:USE_NARRIX_INGEST='1'; npm test
2436
+
2437
+ # Run real E2E for intermediateSteps (real executor + real skill test-intermediate-steps; requires LLM + gateway env like other E2Es)
2438
+ # Also requires Catalox/Firebase: FIREBASE_PROJECT_ID and GOOGLE_SERVICE_ACCOUNT_BASE64 (tests load repo-root `.env` first; skip gate requires both to be set).
2439
+ npm run test:e2e:intermediateSteps
2440
+ # Bash alternative: RUN_INTERMEDIATE_STEPS_E2E=1 npm test
2441
+
2442
+ # Run real E2E for synthesized-context (requires a provider API key in .env)
2443
+ # Keys recognized by the gate: OPENROUTER_API_KEY, OPENAI_API_KEY, OPENAI_KEY, OPEN_ROUTER_KEY, GROK_API_KEY.
2444
+ # Optional: SYNTHESIS_MODEL, LLM_MODEL_STRONG, LLM_MODEL_NORMAL, LLM_MODEL, POST_STEP_MODEL (else gateway default).
2445
+ # Runs three opt-in tests in test/synthesis/e2e-synthesis-real.test.ts:
2446
+ # (1) default markdown synthesis PRE step + main skill,
2447
+ # (2) runStructuredSynthesisGatewayCall (real gateway JSON → parse → buildSynthesizedContextMarkdown),
2448
+ # (3) synthesisOutputFormat: "structured" PRE + main skill (memory-only source + input.question).
2449
+ # The synthesis model must follow JSON instructions for (2) and (3). Uses ~3 synthesis + ~2 main LLM calls.
2450
+ RUN_SYNTHESIS_E2E=1 npm test
2451
+ # Or: npm run test:e2e:synthesis
2452
+ #
2453
+ # Xynthesis + Mongo / Activix + sidekick hardening: documenations/xynthesis-upstream-fixes-checklist.md
2454
+
2455
+ # Development mode
2456
+ npm run dev
2457
+ ```
2458
+
2459
+ **Narrix tests:** When `USE_NARRIX_INGEST` is not set, only the "feature flag off" test runs; the rest are skipped. Use **`npm run test:with-narrix-ingest`** (or set the env var per shell above) to run contract and integration tests; live catalog tests also need `.archive/packages/…` (see **BREAKING-CHANGES-NARRIX-V5.md**). Optional: `NARRIX_DEBUG=1` for verbose handler logging.
2460
+
2461
+ **NARRIX task-level pre-processor tests:** `test/narrix/preProcessorAdapter.integration.test.ts` (adapter produces record from jobMemory/executionMemory/input shapes), `test/narrix/buildNarrixAttachment.test.ts` (attachment from runner result with/without passes), and `test/narrix/preProcessorE2E.test.ts` (throw when `narrix` set but no record; e2e handler receives `ctx.executionMemory._narrix` when ingest is enabled and the narrix seed archive is present).
2462
+
2463
+ ---
2464
+
2465
+ ## License
2466
+
2467
+ ISC
2468
+
2469
+ ## Repository
2470
+
2471
+ [GitHub](https://github.com/exellix/ai-tasks)
2472
+
2473
+ ## Related Packages
2474
+
2475
+ - [`@woroces/ai-skills`](https://github.com/woroces/ai-skills) - Full SDK with additional features
2476
+ - [`@x12i/ai-gateway`](https://github.com/athenices/ai-gateway) - AI Gateway for LLM interactions
2477
+ - [`@x12i/execution-memory-manager`](https://github.com/athenices/execution-memory-manager) - Memory management utilities
2478
+
2479
+ ## Related Documentation
2480
+
2481
+ - [@woroces/ai-skills — GATEWAY_TEMPLATE_PROTOCOL_V4.md](https://github.com/woroces/ai-skills/blob/main/GATEWAY_TEMPLATE_PROTOCOL_V4.md) — Gateway + parser template protocol (MUST tokens, `subPathSearch`, errors)
2482
+ - [documenations/intermediate-steps.md](./documenations/intermediate-steps.md) - Structured intermediate results for combined/multi-step tasks (`intermediateSteps` response field)
2483
+ - [HISTORY-OBJECTS.md](./HISTORY-OBJECTS.md) - Comprehensive guide to `JobHistory`, `TaskHistory`, and `ExecutionHistory` objects
2484
+ - [HISTORY-OBJECTS-DOWNSTREAM.md](./HISTORY-OBJECTS-DOWNSTREAM.md) - How history objects flow downstream through the execution pipeline
2485
+ - [GRAPH-EXECUTION-SUPPORT.md](./GRAPH-EXECUTION-SUPPORT.md) - Graph execution context and activity tracking
2486
+ - [MODEL-CONFIGURATION.md](./MODEL-CONFIGURATION.md) - Complete guide to model configuration in `runTask()`
2487
+ - [NARRIX-FOUR-OBJECTS-AND-USE-CASES.md](./NARRIX-FOUR-OBJECTS-AND-USE-CASES.md) - The four Narrix input objects (record, text, docs, chat), payload shapes, and use cases (local narrixRun, narrix-then-direct, normalize, validate)
2488
+ - [NARRIX-STATUS-REPORT.md](./NARRIX-STATUS-REPORT.md) - Narrix pipeline status: unified handler (record/text/docs/chat), resolver, runners, and tests
2489
+ - [documenations/web-scoping-in-ai-tasks.md](./documenations/web-scoping-in-ai-tasks.md) - Web scoping in the task SDK
2490
+ - [documenations/run-task-execution-flow.md](./documenations/run-task-execution-flow.md) - Step-by-step current `runTask()` behavior and flow diagram
2491
+ - [documenations/schemas/README.md](./documenations/schemas/README.md) - JSON Schema / OpenAPI 3.1 fragments for `outputValidation` and PRE synthesis `executionMemory` fields
2492
+ - [documenations/core-runtime-tokens-and-strategies.md](./documenations/core-runtime-tokens-and-strategies.md) - Core token handling and strategy behavior overview
2493
+ - [documenations/synthesized-context-guide.md](./documenations/synthesized-context-guide.md) - User-facing guide for `synthesized-context` PRE-step behavior and controls
2494
+ - [documenations/task-core-and-core-aware-synthesis.md](./documenations/task-core-and-core-aware-synthesis.md) - Template core token model and structured synthesis behavior
2495
+ - [documenations/synthesis-invocation-notes.md](./documenations/synthesis-invocation-notes.md) - Notes on synthesis call behavior, cost, and operational implications
2496
+ - [documenations/activix.md](./documenations/activix.md) - Activix integration plan: task activity lifecycle tracking, record schema, env config, execution path mapping, and stale cleanup: `enableWebScope`, `narrix.webScoping` vs `@exellix/narrix-web-scoper` / `@exellix/search-adapter`, `executionMemory.webContext`, source snippets
2497
+ - [documenations/downstream-test-runtime-teardown-cleanup.md](./documenations/downstream-test-runtime-teardown-cleanup.md) - Downstream `ai-skills` lifecycle cleanup note for open handles/process-exit hygiene in tests