spine-framework 0.3.124 → 0.3.126

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 (319) hide show
  1. package/.framework/cli/commands/agents.ts +35 -37
  2. package/.framework/cli/commands/auth.ts +35 -34
  3. package/.framework/cli/commands/create-app.ts +104 -40
  4. package/.framework/cli/commands/dev.ts +104 -69
  5. package/.framework/cli/commands/doctor.ts +179 -69
  6. package/.framework/cli/commands/generate.ts +83 -143
  7. package/.framework/cli/commands/init.ts +72 -34
  8. package/.framework/cli/commands/install-app.ts +260 -125
  9. package/.framework/cli/commands/items.ts +33 -44
  10. package/.framework/cli/commands/migrate.ts +122 -40
  11. package/.framework/cli/commands/migrations.ts +33 -40
  12. package/.framework/cli/commands/pipelines.ts +31 -39
  13. package/.framework/cli/commands/status.ts +47 -23
  14. package/.framework/cli/commands/system.ts +47 -37
  15. package/.framework/cli/commands/test.ts +112 -68
  16. package/.framework/cli/commands/validate-app.ts +215 -121
  17. package/.framework/cli/context.ts +112 -130
  18. package/.framework/cli/env-loader.ts +47 -18
  19. package/.framework/cli/index.ts +81 -42
  20. package/.framework/docs/runtime-types.md +1024 -0
  21. package/.framework/functions/_shared/agent-runner.ts +1296 -961
  22. package/.framework/functions/_shared/app-manifest.ts +82 -68
  23. package/.framework/functions/_shared/audit.ts +135 -83
  24. package/.framework/functions/_shared/{cil-state.ts → cil.ts} +158 -56
  25. package/.framework/functions/_shared/db.ts +80 -126
  26. package/.framework/functions/_shared/hash-api-key.ts +63 -0
  27. package/.framework/functions/_shared/index.ts +224 -335
  28. package/.framework/functions/_shared/kb.ts +319 -0
  29. package/.framework/functions/_shared/llm.ts +92 -0
  30. package/.framework/functions/_shared/logging.ts +288 -0
  31. package/.framework/functions/_shared/middleware.ts +329 -365
  32. package/.framework/functions/_shared/outcomes.ts +36 -0
  33. package/.framework/functions/_shared/permissions.ts +359 -643
  34. package/.framework/functions/_shared/pipeline-runner.ts +243 -316
  35. package/.framework/functions/_shared/principal.ts +376 -468
  36. package/.framework/functions/_shared/resolve-credentials.ts +102 -0
  37. package/.framework/functions/_shared/resolve-ids.ts +188 -16
  38. package/.framework/functions/_shared/retry-deliveries.ts +104 -0
  39. package/.framework/functions/_shared/schema-utils.ts +611 -373
  40. package/.framework/functions/_shared/testing.ts +207 -137
  41. package/.framework/functions/_shared/tracing.ts +276 -0
  42. package/.framework/functions/_shared/trigger-engine.ts +159 -211
  43. package/.framework/functions/_shared/webhook-registration.ts +95 -81
  44. package/.framework/functions/_shared/webhook-registry.ts +153 -75
  45. package/.framework/functions/account-nodes.ts +31 -53
  46. package/.framework/functions/adapters/generic-webhook-inbound.ts +56 -0
  47. package/.framework/functions/adapters/generic-webhook-outbound.ts +43 -0
  48. package/.framework/functions/adapters/openai-outbound.ts +40 -0
  49. package/.framework/functions/admin-data.ts +685 -537
  50. package/.framework/functions/admin-funnel.ts +31 -20
  51. package/.framework/functions/admin-install-funnel.ts +47 -28
  52. package/.framework/functions/admin-signals.ts +47 -29
  53. package/.framework/functions/ai-agents.ts +122 -147
  54. package/.framework/functions/ai-interactions.ts +180 -90
  55. package/.framework/functions/api-keys.ts +250 -192
  56. package/.framework/functions/apps.ts +203 -197
  57. package/.framework/functions/auth.ts +98 -91
  58. package/.framework/functions/cil-agent-ingest.ts +773 -0
  59. package/.framework/functions/cil-agent.ts +592 -0
  60. package/.framework/functions/cil-callmap.ts +1227 -0
  61. package/.framework/functions/cil-config.ts +583 -0
  62. package/.framework/functions/cil-context.ts +445 -0
  63. package/.framework/functions/cil-doc.ts +1752 -0
  64. package/.framework/functions/cil-embeddings.ts +1202 -0
  65. package/.framework/functions/cil-graph.ts +585 -0
  66. package/.framework/functions/cil-ingest.ts +2416 -0
  67. package/.framework/functions/cil-pipeline.ts +318 -0
  68. package/.framework/functions/cil-project.ts +499 -0
  69. package/.framework/functions/cil-validate.ts +469 -0
  70. package/.framework/functions/debug-auth.ts +73 -53
  71. package/.framework/functions/deliveries.ts +117 -0
  72. package/.framework/functions/embeddings.ts +528 -516
  73. package/.framework/functions/eval-harness.ts +158 -106
  74. package/.framework/functions/integration-outbound.ts +311 -0
  75. package/.framework/functions/integration-routes.ts +240 -466
  76. package/.framework/functions/integrations.ts +132 -140
  77. package/.framework/functions/invites.ts +401 -201
  78. package/.framework/functions/item-progress.ts +113 -53
  79. package/.framework/functions/kb.ts +28 -0
  80. package/.framework/functions/logs.ts +239 -222
  81. package/.framework/functions/observability.ts +308 -259
  82. package/.framework/functions/pipeline-executions.ts +179 -235
  83. package/.framework/functions/pipelines.ts +163 -205
  84. package/.framework/functions/prompt-configs.ts +109 -156
  85. package/.framework/functions/roles.ts +97 -147
  86. package/.framework/functions/system-cron.ts +132 -218
  87. package/.framework/functions/system.ts +112 -52
  88. package/.framework/functions/tests.ts +32 -28
  89. package/.framework/functions/timers.ts +162 -210
  90. package/.framework/functions/triggers.ts +282 -346
  91. package/.framework/functions/types.ts +283 -400
  92. package/.framework/migrations/001_core_schema.sql +630 -0
  93. package/.framework/migrations/002_core_rls_and_functions.sql +532 -0
  94. package/.framework/migrations/{003_system_data.sql → 003_core_system_data.sql} +26 -153
  95. package/.framework/migrations/004_core_rpc_and_broker.sql +421 -0
  96. package/.framework/migrations/005_core_observability.sql +598 -0
  97. package/.framework/migrations/006_security_hardening_rls.sql +97 -0
  98. package/.framework/migrations/007_kb_article_list.sql +237 -0
  99. package/.framework/migrations/008_items_target.sql +8 -0
  100. package/.framework/migrations/009_roles_slug_unique.sql +11 -0
  101. package/.framework/public/spine.config.json +91 -0
  102. package/.framework/scripts/app-install-cli.ts +112 -98
  103. package/.framework/src/App.tsx +67 -20
  104. package/.framework/src/apps/admin/index.tsx +97 -27
  105. package/.framework/src/components/AppWrapper.tsx +50 -11
  106. package/.framework/src/components/CustomAppLoader.tsx +131 -22
  107. package/.framework/src/components/admin/AdminListPage.tsx +75 -8
  108. package/.framework/src/components/admin/AdminSidebar.tsx +148 -36
  109. package/.framework/src/components/admin/AdminStatsCard.tsx +48 -29
  110. package/.framework/src/components/admin/PageInstructions.tsx +49 -0
  111. package/.framework/src/components/admin/SortableTableHeader.tsx +49 -2
  112. package/.framework/src/components/app-shell/GenericAppShell.tsx +69 -11
  113. package/.framework/src/components/app-shell/GenericDetailPage.tsx +55 -10
  114. package/.framework/src/components/app-shell/GenericListPage.tsx +76 -9
  115. package/.framework/src/components/app-sidebar.tsx +50 -3
  116. package/.framework/src/components/auth/ProtectedRoute.tsx +53 -45
  117. package/.framework/src/components/kb/KBArticleEditor.tsx +409 -0
  118. package/.framework/src/components/kb/KBArticleList.tsx +557 -0
  119. package/.framework/src/components/layout/AppShell.tsx +54 -16
  120. package/.framework/src/components/layout/Header.tsx +50 -38
  121. package/.framework/src/components/layout/Layout.tsx +48 -32
  122. package/.framework/src/components/layout/Sidebar.tsx +167 -16
  123. package/.framework/src/components/runtime/DataDetailHeader.tsx +33 -1
  124. package/.framework/src/components/runtime/DataDetailPage.tsx +289 -4
  125. package/.framework/src/components/runtime/DataFilters.tsx +78 -1
  126. package/.framework/src/components/runtime/DataHeader.tsx +32 -29
  127. package/.framework/src/components/runtime/DataListPage.tsx +161 -2
  128. package/.framework/src/components/runtime/DataStats.tsx +48 -1
  129. package/.framework/src/components/runtime/DataTable.tsx +153 -1
  130. package/.framework/src/components/runtime/SchemaDetailForm.tsx +77 -46
  131. package/.framework/src/components/runtime/index.ts +16 -9
  132. package/.framework/src/components/search-form.tsx +33 -1
  133. package/.framework/src/components/shared/AgentView.tsx +48 -51
  134. package/.framework/src/components/shared/FieldRenderer.tsx +84 -67
  135. package/.framework/src/components/shared/InviteManager.tsx +113 -52
  136. package/.framework/src/components/shared/MemberList.tsx +182 -0
  137. package/.framework/src/components/shared/SchemaFields.tsx +96 -78
  138. package/.framework/src/components/ui/DataTable.tsx +86 -56
  139. package/.framework/src/components/ui/Form.tsx +188 -109
  140. package/.framework/src/components/ui/ItemCard.tsx +86 -58
  141. package/.framework/src/components/ui/ItemListView.tsx +88 -61
  142. package/.framework/src/components/ui/LoadingSpinner.tsx +52 -27
  143. package/.framework/src/components/ui/Modal.tsx +81 -4
  144. package/.framework/src/components/ui/RichTextEditor.tsx +101 -9
  145. package/.framework/src/components/ui/accordion.tsx +97 -5
  146. package/.framework/src/components/ui/alert-dialog.tsx +225 -13
  147. package/.framework/src/components/ui/alert.tsx +113 -6
  148. package/.framework/src/components/ui/aspect-ratio.tsx +49 -2
  149. package/.framework/src/components/ui/avatar.tsx +129 -7
  150. package/.framework/src/components/ui/badge.tsx +65 -3
  151. package/.framework/src/components/ui/breadcrumb.tsx +145 -8
  152. package/.framework/src/components/ui/button-group.tsx +81 -4
  153. package/.framework/src/components/ui/button.tsx +49 -2
  154. package/.framework/src/components/ui/calendar.tsx +65 -3
  155. package/.framework/src/components/ui/card.tsx +145 -8
  156. package/.framework/src/components/ui/carousel.tsx +129 -7
  157. package/.framework/src/components/ui/chart.tsx +161 -10
  158. package/.framework/src/components/ui/checkbox.tsx +49 -2
  159. package/.framework/src/components/ui/collapsible.tsx +81 -5
  160. package/.framework/src/components/ui/combobox.tsx +305 -20
  161. package/.framework/src/components/ui/command.tsx +177 -11
  162. package/.framework/src/components/ui/context-menu.tsx +273 -17
  163. package/.framework/src/components/ui/dialog.tsx +209 -27
  164. package/.framework/src/components/ui/direction.tsx +64 -4
  165. package/.framework/src/components/ui/drawer.tsx +193 -12
  166. package/.framework/src/components/ui/dropdown-menu.tsx +273 -18
  167. package/.framework/src/components/ui/empty.tsx +145 -9
  168. package/.framework/src/components/ui/field.tsx +209 -14
  169. package/.framework/src/components/ui/hover-card.tsx +81 -4
  170. package/.framework/src/components/ui/input-group.tsx +162 -10
  171. package/.framework/src/components/ui/input-otp.tsx +98 -5
  172. package/.framework/src/components/ui/input.tsx +49 -2
  173. package/.framework/src/components/ui/item.tsx +225 -13
  174. package/.framework/src/components/ui/kbd.tsx +65 -3
  175. package/.framework/src/components/ui/label.tsx +49 -2
  176. package/.framework/src/components/ui/menubar.tsx +289 -17
  177. package/.framework/src/components/ui/native-select.tsx +97 -5
  178. package/.framework/src/components/ui/navigation-menu.tsx +177 -10
  179. package/.framework/src/components/ui/pagination.tsx +161 -9
  180. package/.framework/src/components/ui/popover.tsx +145 -8
  181. package/.framework/src/components/ui/progress.tsx +49 -2
  182. package/.framework/src/components/ui/radio-group.tsx +65 -3
  183. package/.framework/src/components/ui/resizable.tsx +82 -4
  184. package/.framework/src/components/ui/scroll-area.tsx +65 -3
  185. package/.framework/src/components/ui/select.tsx +193 -11
  186. package/.framework/src/components/ui/separator.tsx +49 -2
  187. package/.framework/src/components/ui/sheet.tsx +193 -11
  188. package/.framework/src/components/ui/sidebar.tsx +481 -38
  189. package/.framework/src/components/ui/skeleton.tsx +49 -2
  190. package/.framework/src/components/ui/slider.tsx +49 -2
  191. package/.framework/src/components/ui/sonner.tsx +49 -2
  192. package/.framework/src/components/ui/spinner.tsx +49 -2
  193. package/.framework/src/components/ui/switch.tsx +49 -2
  194. package/.framework/src/components/ui/table-primitives.tsx +160 -33
  195. package/.framework/src/components/ui/table.tsx +161 -9
  196. package/.framework/src/components/ui/tabs.tsx +113 -6
  197. package/.framework/src/components/ui/textarea.tsx +49 -2
  198. package/.framework/src/components/ui/toggle-group.tsx +81 -4
  199. package/.framework/src/components/ui/toggle.tsx +65 -3
  200. package/.framework/src/components/ui/tooltip.tsx +97 -5
  201. package/.framework/src/contexts/AppContext.tsx +129 -45
  202. package/.framework/src/contexts/AuthContext.tsx +187 -144
  203. package/.framework/src/hooks/use-mobile.ts +48 -2
  204. package/.framework/src/hooks/useApi.ts +208 -159
  205. package/.framework/src/hooks/useAppPath.ts +32 -23
  206. package/.framework/src/hooks/useApps.ts +51 -20
  207. package/.framework/src/hooks/useEntityList.ts +32 -78
  208. package/.framework/src/hooks/useEntityRecord.ts +35 -96
  209. package/.framework/src/hooks/useForm.ts +32 -92
  210. package/.framework/src/hooks/useListSchema.ts +49 -78
  211. package/.framework/src/hooks/useSchemaRecord.ts +114 -105
  212. package/.framework/src/lib/api.ts +195 -116
  213. package/.framework/src/lib/supabase.ts +98 -64
  214. package/.framework/src/lib/utils.ts +287 -151
  215. package/.framework/src/main.tsx +49 -3
  216. package/.framework/src/pages/DashboardPage.tsx +52 -29
  217. package/.framework/src/pages/NotFoundPage.tsx +32 -10
  218. package/.framework/src/pages/SetupPage.tsx +32 -12
  219. package/.framework/src/pages/admin/AIAgentDetailPage.tsx +119 -10
  220. package/.framework/src/pages/admin/AIAgentsPage.tsx +48 -19
  221. package/.framework/src/pages/admin/APIKeyDetailPage.tsx +187 -17
  222. package/.framework/src/pages/admin/APIKeysPage.tsx +98 -22
  223. package/.framework/src/pages/admin/AlertsConfigPage.tsx +113 -26
  224. package/.framework/src/pages/admin/AppDetailPage.tsx +125 -8
  225. package/.framework/src/pages/admin/AppsPage.tsx +49 -2
  226. package/.framework/src/pages/admin/DeliveriesPage.tsx +196 -0
  227. package/.framework/src/pages/admin/DesignedPage.tsx +32 -18
  228. package/.framework/src/pages/admin/EmbeddingDetailPage.tsx +215 -484
  229. package/.framework/src/pages/admin/EmbeddingsPage.tsx +116 -319
  230. package/.framework/src/pages/admin/ExtendedShadcnTestPage.tsx +34 -9
  231. package/.framework/src/pages/admin/IncrementalShadcnTestPage.tsx +34 -9
  232. package/.framework/src/pages/admin/IntegratedDashboard.tsx +32 -16
  233. package/.framework/src/pages/admin/IntegrationDetailPage.tsx +183 -26
  234. package/.framework/src/pages/admin/IntegrationsPage.tsx +48 -19
  235. package/.framework/src/pages/admin/InvitesPage.tsx +48 -14
  236. package/.framework/src/pages/admin/LogsPage.tsx +422 -75
  237. package/.framework/src/pages/admin/MinimalShadcnTestPage.tsx +34 -9
  238. package/.framework/src/pages/admin/ObservabilityDashboard.tsx +113 -24
  239. package/.framework/src/pages/admin/PipelineDetailPage.tsx +206 -34
  240. package/.framework/src/pages/admin/PipelineExecutionsPage.tsx +50 -17
  241. package/.framework/src/pages/admin/PipelinesPage.tsx +160 -21
  242. package/.framework/src/pages/admin/PromptConfigDetailPage.tsx +378 -88
  243. package/.framework/src/pages/admin/PromptConfigsPage.tsx +125 -16
  244. package/.framework/src/pages/admin/ProperlyDesignedPage.tsx +49 -10
  245. package/.framework/src/pages/admin/RoleDetailPage.tsx +130 -10
  246. package/.framework/src/pages/admin/RolesPage.tsx +171 -18
  247. package/.framework/src/pages/admin/SelectTestPage.tsx +34 -9
  248. package/.framework/src/pages/admin/ShadcnTestPage.tsx +49 -11
  249. package/.framework/src/pages/admin/SimpleDashboard.tsx +49 -10
  250. package/.framework/src/pages/admin/TestRunDetailPage.tsx +49 -4
  251. package/.framework/src/pages/admin/TestingDashboard.tsx +98 -28
  252. package/.framework/src/pages/admin/TimerDetailPage.tsx +143 -33
  253. package/.framework/src/pages/admin/TimersPage.tsx +48 -20
  254. package/.framework/src/pages/admin/TriggerDetailPage.tsx +118 -30
  255. package/.framework/src/pages/admin/TriggersPage.tsx +64 -19
  256. package/.framework/src/pages/admin/TypeDetailPage.tsx +138 -15
  257. package/.framework/src/pages/admin/TypesPage.tsx +48 -20
  258. package/.framework/src/pages/admin/cil/CILAgentPage.tsx +465 -0
  259. package/.framework/src/pages/admin/cil/CILChunkDetailPage.tsx +787 -0
  260. package/.framework/src/pages/admin/cil/CILDashboardPage.tsx +699 -0
  261. package/.framework/src/pages/admin/cil/CILFileListPage.tsx +647 -0
  262. package/.framework/src/pages/admin/cil/CILGraphPage.tsx +616 -0
  263. package/.framework/src/pages/admin/cil/CILIngestionPage.tsx +1402 -0
  264. package/.framework/src/pages/admin/cil/CILProjectsPage.tsx +614 -0
  265. package/.framework/src/pages/admin/cil/CILSearchPage.tsx +571 -0
  266. package/.framework/src/pages/admin/cil/FlexiblePipelinePage.tsx +731 -0
  267. package/.framework/src/pages/admin/kb/KBAdminPage.tsx +64 -0
  268. package/.framework/src/pages/auth/AuthConfirmPage.tsx +52 -51
  269. package/.framework/src/pages/auth/ForgotPasswordPage.tsx +31 -22
  270. package/.framework/src/pages/auth/LoginPage.tsx +50 -23
  271. package/.framework/src/pages/auth/PendingApprovalPage.tsx +31 -15
  272. package/.framework/src/pages/auth/RegisterPage.tsx +281 -317
  273. package/.framework/src/pages/spine-framework/APIPage.tsx +33 -0
  274. package/.framework/src/pages/spine-framework/CLIPage.tsx +33 -0
  275. package/.framework/src/types/auth.ts +128 -71
  276. package/.framework/src/types/types.ts +373 -157
  277. package/custom/.devin/skills/spine-workbench-discovery/SKILL.md +39 -0
  278. package/custom/.devin/skills/spine-workbench-idea/SKILL.md +36 -0
  279. package/custom/.devin/skills/spine-workbench-implement/SKILL.md +46 -0
  280. package/custom/.devin/skills/spine-workbench-initiative/SKILL.md +28 -0
  281. package/custom/.devin/skills/spine-workbench-plan/SKILL.md +38 -0
  282. package/docs/README.md +9 -130
  283. package/netlify.toml +5 -2
  284. package/package.json +13 -25
  285. package/scripts/kb-ingest-docs.ts +203 -0
  286. package/spine.config.json +65 -4
  287. package/.framework/migrations/001_schema.sql +0 -425
  288. package/.framework/migrations/002_rls_and_functions.sql +0 -763
  289. package/.framework/migrations/004_enhancements.sql +0 -43
  290. package/.framework/migrations/005_auth_user_trigger.sql +0 -105
  291. package/.framework/migrations/006_fix_current_actor_id.sql +0 -21
  292. package/.framework/migrations/016_invites.sql +0 -27
  293. package/.framework/migrations/017_fix_get_account_apps.sql +0 -78
  294. package/.framework/migrations/018_apps_required_roles.sql +0 -17
  295. package/.framework/migrations/019_triggers_unique_constraint.sql +0 -13
  296. package/.framework/migrations/020_get_account_apps_role_filter.sql +0 -117
  297. package/.framework/migrations/021_app_domains.sql +0 -112
  298. package/.framework/migrations/022_route_prefix_domain_unique.sql +0 -21
  299. package/.framework/migrations/023_match_embeddings_rpc.sql +0 -78
  300. package/.framework/migrations/024_messages_account_id.sql +0 -9
  301. package/.framework/migrations/025_retype_threads_messages.sql +0 -240
  302. package/.framework/migrations/026_fix_rls_policies.sql +0 -47
  303. package/.framework/migrations/027_embedding_lifecycle.sql +0 -176
  304. package/.framework/migrations/028_fix_message_data_permissions.sql +0 -49
  305. package/.framework/migrations/029_tighten_support_triage_prompt.sql +0 -41
  306. package/.framework/migrations/030_messages_updated_by.sql +0 -6
  307. package/.framework/migrations/031_seed_upsert_constraints.sql +0 -35
  308. package/docs/APPS.md +0 -232
  309. package/docs/ARCHITECTURE.md +0 -246
  310. package/docs/INSTALL.md +0 -155
  311. package/docs/design-schema-spec.md +0 -1837
  312. package/docs/dev-guides/01-create-an-app.md +0 -183
  313. package/docs/dev-guides/02-create-a-function.md +0 -178
  314. package/docs/dev-guides/03-create-a-component.md +0 -178
  315. package/docs/dev-guides/04-webhook-handlers.md +0 -129
  316. package/docs/dev-guides/05-testing.md +0 -168
  317. package/docs/dev-guides/README.md +0 -40
  318. package/docs/npm-packages-plan.md +0 -288
  319. package/docs/spine-core--api-reference.md +0 -822
@@ -1,549 +1,125 @@
1
+ // CIL[BEGIN chunk_id="agent-runner_module_header_1784488696" type="module_header" identity="module_header" parent="none"]
2
+ // CIL[DOC-MACRO purpose="Agent runner module header with imports and type contracts"]
3
+ // CIL[DOC-RESPONSIBILITY responsibility="Declare module dependencies and the core TypeScript interfaces used by agent-runner"]
4
+ // CIL[DOC-CONTEXT module="_shared/agent-runner" layer="infrastructure" related_files=".framework/functions/_shared/db.ts,.framework/src/components/ui/ItemListView.tsx,.framework/functions/apps.ts,.framework/functions/integration-routes.ts,.framework/functions/system.ts,.framework/functions/auth.ts,.framework/cli/commands/agents.ts,.framework/functions/types.ts,.framework/cli/commands/migrate.ts"]
5
+ // CIL[DOC-CONTRACT inputs="none" outputs="imports and TypeScript interfaces" errors="none" side_effects="none"]
6
+ // CIL[DOC-MICRO logic_summary="Imports CoreContext, adminDb, emitAudit, createRecord and defines AgentConfig, InferenceResult, ToolCall, ToolResult" notable_decisions="[]"]
7
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
8
+ // CIL[DOC-STABILITY stability="stable"]
9
+ // CIL[DOC-AUDIENCE audience="developer"]
10
+ // CIL[DOC-VECTOR type="context" content="Layer: infrastructure. Direct callers: none. Direct callees: adminDb(.framework/functions/_shared/db.ts),imports(.framework/src/components/ui/ItemListView.tsx),create(.framework/functions/apps.ts),interfaces(.framework/functions/integration-routes.ts),types(.framework/functions/system.ts),context(.framework/functions/auth.ts). Related files: .framework/functions/_shared/db.ts,.framework/src/components/ui/ItemListView.tsx,.framework/functions/apps.ts,.framework/functions/integration-routes.ts,.framework/functions/system.ts,.framework/functions/auth.ts,.framework/cli/commands/agents.ts,.framework/functions/types.ts,.framework/cli/commands/migrate.ts." status="ready"]
11
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: none. Total affected: 0." status="ready"]
12
+ // CIL[DOC-VECTOR type="configuration" content="Config dependencies: none. Test paths: none." status="ready"]
13
+ // CIL[DOC-VECTOR type="constraint" content="Driven by chunk inputs; callers [none] and callees [adminDb(.framework/functions/_shared/db.ts),imports(.framework/src/components/ui/ItemListView.tsx),create(.framework/functions/apps.ts),interfaces(.framework/functions/integration-routes.ts),types(.framework/functions/system.ts),context(.framework/functions/auth.ts)] shape downstream behavior." status="ready"]
14
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
15
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
1
16
  /// <reference types="node" />
2
- /**
3
- * @module agent-runner
4
- * @audience both
5
- * @layer shared-core
6
- * @stability stable
7
- *
8
- * AI agent inference orchestrator with RAG, tool dispatch, and confidence-based
9
- * escalation. All agent behavior is defined via JSONB configuration stored in
10
- * existing schema tables — no dedicated migrations are needed.
11
- *
12
- * Configuration resolution chain (highest to lowest priority):
13
- * 1. `thread.data.agent_id` → `thread.type.design_schema.default_agent_id`
14
- * 2. `thread.data.prompt_config_id` → `agent.metadata.default_prompt_config_id`
15
- *
16
- * Inference loop (per `runAgent` call):
17
- * 1. `resolveAgentConfig` — resolve agent + prompt_config from thread
18
- * 2. Save user message to `messages` table
19
- * 3. `executeAgentInference` — iterative tool-call loop (max 5 iterations):
20
- * a. `buildContext` — assemble system prompt + RAG + history + tools
21
- * b. `callInference` — call OpenAI-compatible API (or return mock)
22
- * c. `dispatchTools` — execute any tool_calls via actions table
23
- * d. Rebuild context with tool results and repeat
24
- * 4. Confidence check — if below threshold, `handleEscalation`
25
- * 5. Save agent response to `messages` table
26
- * 6. Emit `agent.inference.completed` audit log
27
- *
28
- * Environment variables used by `callInference`:
29
- * - `OPENAI_API_KEY` / `ANTHROPIC_API_KEY` / `LLM_API_KEY` — LLM auth
30
- * - `OPENAI_BASE_URL` / `LLM_BASE_URL` — API base URL (default: OpenAI)
31
- * - `LLM_DEFAULT_MODEL` — model name fallback (default: 'gpt-4o')
32
- *
33
- * INVARIANT: if no API key is set, `callInference` returns a mock response
34
- * instead of throwing — safe for local development without credentials.
35
- * INVARIANT: `runAgent` throws on critical failures (config missing, inference
36
- * error) — callers must handle the rejection.
37
- * INVARIANT: `resolveAgentConfig` returns null (not throws) on missing config;
38
- * `runAgent` converts this to a throw.
39
- *
40
- * @seeAlso pipeline-runner.ts (tool dispatch calls runPipeline for run_pipeline tool)
41
- * @seeAlso audit.ts (emitAudit for agent.inference.* events)
42
- * @seeAlso index.ts (runAgent, resolveAgentConfig re-exported)
43
- */
44
-
17
+ import { createHash } from 'crypto'
45
18
  import { CoreContext } from './middleware'
46
19
  import { adminDb } from './db'
47
- import { emitAudit } from './audit'
20
+ import { emitAudit, emitSpan } from './audit'
21
+ import { emitOutcome } from './outcomes'
22
+ import { withSpan } from './tracing'
48
23
  import { create as createRecord } from '../admin-data'
49
-
50
- // ─── TYPES ───────────────────────────────────────────────────────────────
51
-
52
- /**
53
- * Resolved agent configuration bundle. Output of `resolveAgentConfig`.
54
- * Passed to `executeAgentInference` and `buildContext`.
55
- *
56
- * @outputSpec agent: ai_agents row (system_prompt, model_config, metadata)
57
- * @outputSpec promptConfig: prompt_configs row (context_template,
58
- * knowledge_sources, available_tools, confidence_threshold, escalation_*)
59
- * @outputSpec thread: threads row with joined type record
60
- * @outputSpec threadType: types row (design_schema.default_agent_id)
61
- */
62
- export interface AgentConfig {
63
- agent: any
64
- promptConfig: any
65
- thread: any
66
- threadType: any
67
- }
68
-
69
- /**
70
- * Structured result from a single LLM inference call.
71
- *
72
- * @outputSpec content: string — agent response text
73
- * @outputSpec confidence: number — 0-1 score; derived from logprobs or 0.70 default
74
- * @outputSpec tool_calls: ToolCall[] | undefined — tools the model wants to call
75
- * @outputSpec metadata: { model, usage, finish_reason } | undefined
76
- */
77
- export interface InferenceResult {
78
- content: string
79
- confidence: number
80
- tool_calls?: ToolCall[]
81
- metadata?: Record<string, any>
82
- }
83
-
84
- /**
85
- * A single tool invocation requested by the LLM in an `InferenceResult`.
86
- *
87
- * @inputSpec tool: string — action.slug to look up in the actions table
88
- * @inputSpec params: Record<string, any> — parsed from OpenAI function call arguments
89
- * @inputSpec id: string — opaque tool_call ID from the LLM response
90
- */
91
- export interface ToolCall {
92
- tool: string
93
- params: Record<string, any>
94
- id: string
95
- }
96
-
97
- /**
98
- * Result of executing a single `ToolCall`.
99
- *
100
- * @outputSpec tool: string — mirrors ToolCall.tool
101
- * @outputSpec result: any — handler return value on success; null on error
102
- * @outputSpec error: string | undefined — error message if execution failed
103
- * @outputSpec id: string — mirrors ToolCall.id for correlation
104
- */
105
- export interface ToolResult {
106
- tool: string
107
- result: any
108
- error?: string
109
- id: string
110
- }
111
-
112
- // ─── PRIMARY EXPORTS ────────────────────────────────────────────────────────────
113
-
114
- /**
115
- * Main entry point: run a full agent inference cycle for a user message.
116
- *
117
- * Saves the user message, runs the inference loop (with tool calls), checks
118
- * confidence, saves the agent response, and emits audit logs.
119
- *
120
- * @param threadId - UUID of the thread to run inference on
121
- * @param userMessage - Raw message text from the user
122
- * @param ctx - CoreContext with accountId, principal, requestId
123
- * @returns Promise<any> — the saved agent message record from the `messages` table
124
- * @throws Error — if agent config cannot be resolved, or LLM inference fails
125
- * @inputSpec threadId: string — valid UUID in threads table
126
- * @inputSpec userMessage: string — non-empty message text
127
- * @inputSpec ctx.accountId: string | null — used to scope DB lookups
128
- * @outputSpec messages row — the inserted agent reply with content and metadata
129
- * @sideEffects DB write: inserts 2 messages rows (user + agent)
130
- * @sideEffects DB write: emitAudit (agent.inference.completed or agent.inference.failed)
131
- * @sideEffects HTTP call: callInference (LLM API)
132
- * @sideEffects DB write (conditional): handleEscalation if confidence < threshold
133
- * @calledBy functions/ai-agents.ts handler for POST ?action=run
134
- * @calledBy v2-custom/ import callers
135
- * @calls resolveAgentConfig, saveMessage, executeAgentInference,
136
- * handleEscalation, emitAudit
137
- * @testUnit tests/unit/agent-runner.test.ts
138
- * @testIntegration tests/integration/agent-runner.test.ts
139
- *
140
- * @example API handler usage
141
- * ```ts
142
- * import { runAgent } from './_shared/index'
143
- * const agentMsg = await runAgent(body.thread_id, body.message, ctx)
144
- * return agentMsg
145
- * ```
146
- */
147
- export async function runAgent(
148
- threadId: string,
24
+ import { callOpenAIEmbeddings, callOpenAIChat } from './llm'
25
+ import { execute as executeOutbound } from '../integration-outbound'
26
+ // CIL[END chunk_id="agent-runner_module_header_1784488696"]
27
+ // CIL[BEGIN chunk_id="agent-runner_buildcontext_1784488696" type="function" identity="buildContext" parent="none"]
28
+ // CIL[DOC-MACRO purpose="Assemble the full prompt context for an inference call"]
29
+ // CIL[DOC-RESPONSIBILITY responsibility="Build context string from system prompt, template, RAG, history, tool results, tools list, and user message"]
30
+ // CIL[DOC-CONTEXT module="_shared/agent-runner" layer="infrastructure" related_files=".framework/src/types/auth.ts,.framework/functions/auth.ts,custom/apps/cortex/functions/custom_kb-chunker-test.ts,.framework/functions/item-progress.ts,.framework/src/components/runtime/DataDetailPage.tsx,custom/apps/cortex/pages/community/CommunityPage.tsx,.framework/cli/context.ts"]
31
+ // CIL[DOC-CONTRACT inputs="config: AgentConfig\nuserMessage: string\ntoolResults: ToolResult[]\nctx: CoreContext" outputs="{ context, retrievedSources, meanSimilarity }" errors="catches retrieveKnowledge errors" side_effects="DB reads for knowledge and history"]
32
+ // CIL[DOC-MICRO logic_summary="Concatenates system prompt, optional context template, retrieved knowledge, conversation history, tool results, available tools, and current message" notable_decisions="[]"]
33
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
34
+ // CIL[DOC-STABILITY stability="stable"]
35
+ // CIL[DOC-VECTOR type="context" content="Layer: infrastructure. Direct callers: executeAgentInference,runAgent. Direct callees: User(.framework/src/types/auth.ts),context(.framework/functions/auth.ts),failed(custom/apps/cortex/functions/custom_kb-chunker-test.ts),retrieveKnowledge,list(.framework/functions/item-progress.ts),getConversationHistory,title(.framework/src/components/runtime/DataDetailPage.tsx),Message(custom/apps/cortex/pages/community/CommunityPage.tsx). Related files: .framework/src/types/auth.ts,.framework/functions/auth.ts,custom/apps/cortex/functions/custom_kb-chunker-test.ts,.framework/functions/item-progress.ts,.framework/src/components/runtime/DataDetailPage.tsx,custom/apps/cortex/pages/community/CommunityPage.tsx,.framework/cli/context.ts." status="ready"]
36
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: executeAgentInference,runAgent. Total affected: 2." status="ready"]
37
+ // CIL[DOC-VECTOR type="configuration" content="Config dependencies: none. Test paths: none." status="ready"]
38
+ // CIL[DOC-VECTOR type="constraint" content="Driven by chunk inputs; callers [executeAgentInference,runAgent] and callees [User(.framework/src/types/auth.ts),context(.framework/functions/auth.ts),failed(custom/apps/cortex/functions/custom_kb-chunker-test.ts),retrieveKnowledge,list(.framework/functions/item-progress.ts),getConversationHistory,title(.framework/src/components/runtime/DataDetailPage.tsx),Message(custom/apps/cortex/pages/community/CommunityPage.tsx)] shape downstream behavior." status="ready"]
39
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
40
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
41
+ async function buildContext(
42
+ config: AgentConfig,
149
43
  userMessage: string,
44
+ toolResults: ToolResult[],
150
45
  ctx: CoreContext
151
- ): Promise<any> {
152
- const startTime = Date.now()
153
-
154
- try {
155
- // 1. Resolve configurations (thread → agent → prompt config)
156
- const config = await resolveAgentConfig(threadId, ctx)
157
- if (!config) {
158
- throw new Error(`Could not resolve agent configuration for thread ${threadId}`)
46
+ ): Promise<{ context: string; retrievedSources: any[]; meanSimilarity: number }> {
47
+ return withSpan(ctx, 'agent.build_context', async (ctx) => {
48
+ const { agent, promptConfig, thread } = config
49
+ let context = `${agent.system_prompt || 'You are a helpful assistant.'}\n\n`
50
+ if (promptConfig.context_template) {
51
+ context += `${promptConfig.context_template}\n\n`
159
52
  }
160
-
161
- const { agent, promptConfig, thread, threadType } = config
162
-
163
- // Resolve the message type from the thread type so support/community threads
164
- // save replies with the correct type (and therefore correct permissions).
165
- const replyTypeSlug = getReplyTypeSlug(threadType?.slug)
166
-
167
- // 2. Save user message to thread
168
- const userMsg = await saveMessage(threadId, userMessage, 'human', null, ctx, replyTypeSlug)
169
-
170
- // 3. Execute agent with full context
171
- const agentResponse = await executeAgentInference(
172
- config,
173
- userMessage,
174
- ctx
175
- )
176
-
177
- // 4. Check confidence and escalate if needed
178
- const isEscalated = agentResponse.confidence < (promptConfig.confidence_threshold || 0)
179
- if (isEscalated) {
180
- await handleEscalation(
181
- threadId,
182
- agentResponse.confidence,
183
- promptConfig,
53
+ let retrievedSources: any[] = []
54
+ let meanSimilarity = 0
55
+ const retrievalPolicy = promptConfig.metadata?.retrieval_policy || {}
56
+ try {
57
+ const { docs, meanSimilarity: ms } = await retrieveKnowledge(
58
+ userMessage,
59
+ promptConfig.knowledge_sources || [],
60
+ ctx.accountId!,
61
+ retrievalPolicy,
184
62
  ctx
185
63
  )
186
- }
187
-
188
- // 5. Save agent response — include retrieved_sources + retrieval_confidence
189
- // so the feedback capture pipeline and the Portal UI can access them.
190
- const agentMsg = await saveMessage(
191
- threadId,
192
- agentResponse.content,
193
- 'agent',
194
- {
195
- confidence: agentResponse.confidence,
196
- retrieval_confidence: agentResponse.retrievalConfidence,
197
- retrieved_sources: (agentResponse.retrievedSources || []).map((s: any) => ({
198
- document_id: s.document_id,
199
- title: s.title,
200
- similarity: s.similarity,
201
- item_type: s.item_type,
202
- vector_type: s.vector_type,
203
- })),
204
- tool_calls: agentResponse.tool_calls,
205
- agent_id: agent.id,
206
- prompt_config_id: promptConfig.id,
207
- sender_type: 'agent',
208
- escalated: isEscalated,
209
- },
210
- ctx,
211
- replyTypeSlug
212
- )
213
-
214
- // 6. Emit audit log
215
- await emitAudit(ctx, 'agent.inference.completed', {
216
- type: 'agent_message',
217
- id: agentMsg.id,
218
- account_id: ctx.accountId ?? undefined
219
- }, {
220
- thread_id: threadId,
221
- agent_id: agent.id,
222
- confidence: agentResponse.confidence,
223
- retrieval_confidence: agentResponse.retrievalConfidence,
224
- sources_count: agentResponse.retrievedSources?.length || 0,
225
- has_tool_calls: !!agentResponse.tool_calls?.length,
226
- duration_ms: Date.now() - startTime
227
- })
228
-
229
- return agentMsg
230
-
231
- } catch (error: any) {
232
- // Log failure
233
- await emitAudit(ctx, 'agent.inference.failed', {
234
- type: 'agent_message',
235
- id: 'failed',
236
- account_id: ctx.accountId ?? undefined
237
- }, {
238
- thread_id: threadId,
239
- error: error.message
240
- })
241
-
242
- throw error
243
- }
244
- }
245
-
246
- /**
247
- * Resolves the agent, prompt config, thread, and thread type for a given thread.
248
- *
249
- * Resolution priority:
250
- * - `agent_id`: `thread.data.agent_id` → `thread.type.design_schema.default_agent_id`
251
- * - `prompt_config_id`: `thread.data.prompt_config_id` →
252
- * `agent.metadata.default_prompt_config_id`
253
- *
254
- * Returns `null` (does not throw) when any required record is missing. `runAgent`
255
- * converts a null return to a thrown Error.
256
- *
257
- * @param threadId - UUID of the thread
258
- * @param ctx - CoreContext (requestId used for error logging)
259
- * @returns Promise<AgentConfig | null> — null if config cannot be resolved
260
- * @throws never — errors logged to console, returns null
261
- * @inputSpec threadId: string — valid UUID in threads table
262
- * @outputSpec AgentConfig | null
263
- * @sideEffects DB reads: threads (with type join), ai_agents, prompt_configs
264
- * @calledBy runAgent
265
- * @testUnit tests/unit/agent-runner.test.ts — 'resolveAgentConfig'
266
- */
267
- export async function resolveAgentConfig(
268
- threadId: string,
269
- ctx: CoreContext
270
- ): Promise<AgentConfig | null> {
271
- // Load thread with its type
272
- const { data: thread, error: threadError } = await adminDb
273
- .from('threads')
274
- .select('*, type:types(*)')
275
- .eq('id', threadId)
276
- .single()
277
-
278
- if (threadError || !thread) {
279
- console.error(`Thread not found: ${threadId}`, threadError)
280
- return null
281
- }
282
-
283
- const threadType = thread.type
284
- const threadData = thread.data || {}
285
-
286
- // Resolve agent_id: thread.data > thread.type.design_schema.default_agent_id
287
- let agentId = threadData.agent_id
288
- if (!agentId && threadType?.design_schema?.default_agent_id) {
289
- agentId = threadType.design_schema.default_agent_id
290
- }
291
-
292
- if (!agentId) {
293
- console.error(`No agent assigned to thread ${threadId}`)
294
- return null
295
- }
296
-
297
- // Load agent
298
- const { data: agent, error: agentError } = await adminDb
299
- .from('ai_agents')
300
- .select('*')
301
- .eq('id', agentId)
302
- .eq('is_active', true)
303
- .single()
304
-
305
- if (agentError || !agent) {
306
- console.error(`Agent not found: ${agentId}`, agentError)
307
- return null
308
- }
309
-
310
- const agentMetadata = agent.metadata || {}
311
-
312
- // Resolve prompt_config_id: thread.data > agent.metadata.default_prompt_config_id
313
- let promptConfigId = threadData.prompt_config_id
314
- if (!promptConfigId && agentMetadata.default_prompt_config_id) {
315
- promptConfigId = agentMetadata.default_prompt_config_id
316
- }
317
-
318
- if (!promptConfigId) {
319
- console.error(`No prompt config for thread ${threadId}`)
320
- return null
321
- }
322
-
323
- // Load prompt config
324
- const { data: promptConfig, error: configError } = await adminDb
325
- .from('prompt_configs')
326
- .select('*')
327
- .eq('id', promptConfigId)
328
- .eq('is_active', true)
329
- .single()
330
-
331
- if (configError || !promptConfig) {
332
- console.error(`Prompt config not found: ${promptConfigId}`, configError)
333
- return null
334
- }
335
-
336
- return { agent, promptConfig, thread, threadType }
337
- }
338
-
339
- // ─── INFERENCE LOOP ─────────────────────────────────────────────────────────────
340
-
341
- /**
342
- * Runs the iterative inference loop: build context → call LLM → dispatch tools
343
- * → rebuild context → repeat (up to `maxToolIterations`).
344
- *
345
- * Returns early when the LLM response has no tool_calls. Stops on last
346
- * iteration with a note appended if any tool failed.
347
- *
348
- * @param config - Resolved AgentConfig
349
- * @param userMessage - Original user message text
350
- * @param ctx - CoreContext
351
- * @param maxToolIterations - Maximum tool-call loops (default: 5)
352
- * @returns Promise<InferenceResult> — final LLM response with content and confidence
353
- * @throws Error('Max tool iterations reached') if loop exhausted without convergence
354
- * @sideEffects HTTP calls: callInference (per iteration)
355
- * @sideEffects DB reads: messages (conversation history), embeddings (RAG)
356
- * @calledBy runAgent
357
- * @calls buildContext, callInference, dispatchTools
358
- */
359
- async function executeAgentInference(
360
- config: AgentConfig,
361
- userMessage: string,
362
- ctx: CoreContext,
363
- maxToolIterations: number = 5
364
- ): Promise<InferenceResult & { retrievedSources: any[]; retrievalConfidence: number }> {
365
- const { agent, promptConfig } = config
366
-
367
- // Build initial context — captures retrieved sources for feedback capture
368
- let { context, retrievedSources, meanSimilarity } = await buildContext(config, userMessage, [], ctx)
369
-
370
- for (let iteration = 0; iteration < maxToolIterations; iteration++) {
371
- const inferenceResult = await callInference(context, agent, promptConfig, ctx)
372
-
373
- if (!inferenceResult.tool_calls || inferenceResult.tool_calls.length === 0) {
374
- // Composite confidence: 60% LLM logprobs + 40% retrieval similarity
375
- // When logprobs are absent, be conservative: default 0.70 instead of 0.85
376
- // If mean retrieval similarity < 0.40, cap composite at 0.60
377
- // If mean retrieval similarity < 0.25, cap composite at 0.45 (very poor retrieval)
378
- const llmConf = inferenceResult.confidence ?? 0.70
379
- let compositeConf: number
380
- if (meanSimilarity < 0.25 && meanSimilarity > 0) {
381
- compositeConf = Math.min(llmConf * 0.6 + meanSimilarity * 0.4, 0.45)
382
- } else if (meanSimilarity < 0.40 && meanSimilarity > 0) {
383
- compositeConf = Math.min(llmConf * 0.6 + meanSimilarity * 0.4, 0.60)
384
- } else {
385
- compositeConf = llmConf * 0.6 + Math.min(meanSimilarity, 1) * 0.4
386
- }
387
-
388
- return {
389
- ...inferenceResult,
390
- confidence: Math.min(Math.max(compositeConf, 0), 1),
391
- retrievedSources,
392
- retrievalConfidence: meanSimilarity,
64
+ retrievedSources = docs
65
+ meanSimilarity = ms
66
+ if (docs.length > 0) {
67
+ context += '## Relevant Information\n'
68
+ docs.forEach((doc, i) => {
69
+ const sourceLabel = doc.title ? `[${i + 1}] ${doc.title}` : `[${i + 1}]`
70
+ context += `${sourceLabel}\n${doc.content}\n\n`
71
+ })
393
72
  }
73
+ } catch (err) {
74
+ console.warn('retrieveKnowledge failed — proceeding without context:', err)
394
75
  }
395
-
396
- const toolResults = await dispatchTools(inferenceResult.tool_calls, ctx)
397
- const rebuilt = await buildContext(config, userMessage, toolResults, ctx)
398
- context = rebuilt.context
399
- if (rebuilt.retrievedSources.length) {
400
- retrievedSources = rebuilt.retrievedSources
401
- meanSimilarity = rebuilt.meanSimilarity
76
+ const history = await getConversationHistory(thread.id, promptConfig.max_history_messages || 20)
77
+ if (history.length > 0) {
78
+ context += '## Conversation History\n'
79
+ history.forEach(msg => {
80
+ const role = msg.data?.message_type === 'human' ? 'User' : 'Assistant'
81
+ context += `${role}: ${msg.content}\n`
82
+ })
83
+ context += '\n'
402
84
  }
403
-
404
- const hasErrors = toolResults.some(r => r.error)
405
- if (hasErrors && iteration === maxToolIterations - 1) {
406
- inferenceResult.content += '\n\n[Note: Some tools failed to execute.]'
407
- return { ...inferenceResult, retrievedSources, retrievalConfidence: meanSimilarity }
85
+ if (toolResults.length > 0) {
86
+ context += '## Tool Results\n'
87
+ toolResults.forEach(result => {
88
+ if (result.error) {
89
+ context += `Tool "${result.tool}" failed: ${result.error}\n`
90
+ } else {
91
+ context += `Tool "${result.tool}" result: ${JSON.stringify(result.result)}\n`
92
+ }
93
+ })
94
+ context += '\n'
408
95
  }
409
- }
410
-
411
- throw new Error('Max tool iterations reached')
412
- }
413
-
414
- /**
415
- * Assembles the full prompt context string for a single inference call.
416
- *
417
- * Context sections (in order):
418
- * 1. `agent.system_prompt` (or default 'You are a helpful assistant.')
419
- * 2. `promptConfig.context_template` (if set)
420
- * 3. Retrieved knowledge via `retrieveKnowledge` (RAG, if knowledge_sources set)
421
- * 4. Conversation history via `getConversationHistory`
422
- * 5. Tool results from previous iteration (if any)
423
- * 6. Available tools list from `promptConfig.available_tools`
424
- * 7. Current user message
425
- *
426
- * @param config - AgentConfig with agent, promptConfig, thread
427
- * @param userMessage - Current user message
428
- * @param toolResults - Results from previous tool dispatch iteration
429
- * @param ctx - CoreContext
430
- * @returns Promise<string> — assembled prompt context string
431
- * @throws never — returns partial context on sub-call failure
432
- * @sideEffects DB reads: embeddings (retrieveKnowledge), messages (history)
433
- * @calledBy executeAgentInference (per iteration)
434
- * @calls retrieveKnowledge, getConversationHistory
435
- */
436
- async function buildContext(
437
- config: AgentConfig,
438
- userMessage: string,
439
- toolResults: ToolResult[],
440
- ctx: CoreContext
441
- ): Promise<{ context: string; retrievedSources: any[]; meanSimilarity: number }> {
442
- const { agent, promptConfig, thread } = config
443
-
444
- // 1. System prompt (from agent)
445
- let context = `${agent.system_prompt || 'You are a helpful assistant.'}\n\n`
446
-
447
- // 2. Context template (from prompt config)
448
- if (promptConfig.context_template) {
449
- context += `${promptConfig.context_template}\n\n`
450
- }
451
-
452
- // 3. Retrieved knowledge (RAG)
453
- // Always attempt retrieval — even with empty knowledge_sources (full scope search)
454
- let retrievedSources: any[] = []
455
- let meanSimilarity = 0
456
- const retrievalPolicy = promptConfig.metadata?.retrieval_policy || {}
457
-
458
- try {
459
- const { docs, meanSimilarity: ms } = await retrieveKnowledge(
460
- userMessage,
461
- promptConfig.knowledge_sources || [],
462
- ctx.accountId!,
463
- retrievalPolicy
464
- )
465
- retrievedSources = docs
466
- meanSimilarity = ms
467
-
468
- if (docs.length > 0) {
469
- context += '## Relevant Information\n'
470
- docs.forEach((doc, i) => {
471
- const sourceLabel = doc.title ? `[${i + 1}] ${doc.title}` : `[${i + 1}]`
472
- context += `${sourceLabel}\n${doc.content}\n\n`
96
+ if (promptConfig.available_tools && promptConfig.available_tools.length > 0) {
97
+ context += '## Available Tools\n'
98
+ promptConfig.available_tools.forEach((tool: string) => {
99
+ context += `- ${tool}\n`
473
100
  })
101
+ context += '\n'
474
102
  }
475
- } catch (err) {
476
- console.warn('retrieveKnowledge failed — proceeding without context:', err)
477
- }
478
-
479
- // 4. Conversation history
480
- const history = await getConversationHistory(thread.id, promptConfig.max_history_messages || 20)
481
- if (history.length > 0) {
482
- context += '## Conversation History\n'
483
- history.forEach(msg => {
484
- const role = msg.data?.message_type === 'human' ? 'User' : 'Assistant'
485
- context += `${role}: ${msg.content}\n`
486
- })
487
- context += '\n'
488
- }
489
-
490
- // 5. Tool results (if any)
491
- if (toolResults.length > 0) {
492
- context += '## Tool Results\n'
493
- toolResults.forEach(result => {
494
- if (result.error) {
495
- context += `Tool "${result.tool}" failed: ${result.error}\n`
496
- } else {
497
- context += `Tool "${result.tool}" result: ${JSON.stringify(result.result)}\n`
498
- }
499
- })
500
- context += '\n'
501
- }
502
-
503
- // 6. Available tools (if configured)
504
- if (promptConfig.available_tools && promptConfig.available_tools.length > 0) {
505
- context += '## Available Tools\n'
506
- promptConfig.available_tools.forEach((tool: string) => {
507
- context += `- ${tool}\n`
508
- })
509
- context += '\n'
510
- }
511
-
512
- // 7. Current user message
513
- context += `## Current Message\nUser: ${userMessage}\n\nAssistant: `
514
-
515
- return { context, retrievedSources, meanSimilarity }
103
+ context += `## Current Message\nUser: ${userMessage}\n\nAssistant: `
104
+ return { context, retrievedSources, meanSimilarity }
105
+ }, { onEnd: emitSpan })
516
106
  }
517
-
518
- // ─── KNOWLEDGE & HISTORY ───────────────────────────────────────────────────────────
519
-
520
- /**
521
- * Retrieves relevant documents from the `embeddings` table for RAG.
522
- *
523
- * Strategy (tiered):
524
- * 1. Generate a query embedding via the OpenAI embeddings API.
525
- * 2. Call the `match_embeddings` Postgres RPC (vector cosine similarity).
526
- * 3. Deduplicate by document_id — keep best-matching chunk per article.
527
- * 4. Enrich with item titles from the `items` table.
528
- * 5. Fall back to full-text search if the embedding API is unavailable or
529
- * the RPC call fails (guarantees graceful degradation in dev/test).
530
- *
531
- * Account scoping:
532
- * - Always includes the requesting account's embeddings.
533
- * - Always includes the platform KB account (spine-system), excluding
534
- * rows where metadata.security_level = 'restricted'.
535
- * - `knowledgeSources` optionally narrows to specific document UUIDs
536
- * (prompt_config.knowledge_sources); omit or pass [] for full scope.
537
- *
538
- * @param query - User message text to embed and search
539
- * @param knowledgeSources - Optional document UUIDs to restrict search scope
540
- * @param accountId - Requesting account for scoped access
541
- * @returns Promise<any[]> — enriched result rows (id, title, content, similarity)
542
- * @throws never — falls back to text search or [] on any failure
543
- * @sideEffects HTTP call to OpenAI embeddings API (if API key present)
544
- * @sideEffects DB read: match_embeddings RPC + items table
545
- * @calledBy buildContext (if promptConfig.knowledge_sources is non-empty)
546
- */
107
+ // CIL[END chunk_id="agent-runner_buildcontext_1784488696"]
108
+ // CIL[BEGIN chunk_id="agent-runner_retrieveknowledge_1784488696" type="function" identity="retrieveKnowledge" parent="none"]
109
+ // CIL[DOC-MACRO purpose="Retrieve relevant documents from the embeddings table for RAG"]
110
+ // CIL[DOC-RESPONSIBILITY responsibility="Run vector similarity search via OpenAI embeddings and match_embeddings RPC, with security filtering and text fallback"]
111
+ // CIL[DOC-CONTEXT module="_shared/agent-runner" layer="infrastructure" related_files=".framework/functions/item-progress.ts,.framework/migrations/001_schema.sql,.framework/functions/_shared/db.ts,.framework/src/pages/admin/TypesPage.tsx,custom/apps/cortex/functions/custom_cil-embeddings.ts,.framework/src/components/runtime/DataDetailPage.tsx,custom/apps/cortex/functions/custom_kb-chunker-test.ts,.framework/functions/auth.ts,.framework/migrations/027_embedding_lifecycle.sql,.framework/functions/embeddings.ts"]
112
+ // CIL[DOC-CONTRACT inputs="query: string\nknowledgeSources: string[]\naccountId: string\nretrievalPolicy?: { item_types?: string[], vector_types?: string[], max_security_level?: string, require_approved?: boolean, project_ids?: string[] }\nctx?: CoreContext" outputs="{ docs, meanSimilarity }" errors="returns empty docs on any failure" side_effects="HTTP call to OpenAI embeddings API; DB RPC and items read"]
113
+ // CIL[DOC-MICRO logic_summary="Generates query embedding, runs multi-vector match_embeddings (larger match_count when project_ids are filtered), filters by security level and optional CIL project_id, deduplicates by document, and falls back to full-text search" notable_decisions="[]"]
114
+ // CIL[DOC-LEARNING topic="agent-runner:retrieval_security_level" content="CIL chunks are stored with security_level 'internal'. A prompt_config retrieval_policy with max_security_level='external' filters them out, causing the agent to see no codebase context. Set max_security_level to 'internal' or higher for CIL-augmented support agents."]
115
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
116
+ // CIL[DOC-STABILITY stability="stable"]
117
+ // CIL[DOC-VECTOR type="context" content="Layer: infrastructure. Direct callers: buildContext,executeAgentInference,runAgent. Direct callees: get(.framework/functions/item-progress.ts),embeddings(.framework/migrations/001_schema.sql),adminDb(.framework/functions/_shared/db.ts),Type(.framework/src/pages/admin/TypesPage.tsx),rank(custom/apps/cortex/functions/custom_cil-embeddings.ts),title(.framework/src/components/runtime/DataDetailPage.tsx),failed(custom/apps/cortex/functions/custom_kb-chunker-test.ts),context(.framework/functions/auth.ts). Related files: .framework/functions/item-progress.ts,.framework/migrations/001_schema.sql,.framework/functions/_shared/db.ts,.framework/src/pages/admin/TypesPage.tsx,custom/apps/cortex/functions/custom_cil-embeddings.ts,.framework/src/components/runtime/DataDetailPage.tsx,custom/apps/cortex/functions/custom_kb-chunker-test.ts,.framework/functions/auth.ts,.framework/migrations/027_embedding_lifecycle.sql,.framework/functions/embeddings.ts." status="ready"]
118
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: buildContext,executeAgentInference,runAgent. Total affected: 3." status="ready"]
119
+ // CIL[DOC-VECTOR type="configuration" content="Config dependencies: none. Test paths: none." status="ready"]
120
+ // CIL[DOC-VECTOR type="constraint" content="Driven by chunk inputs; callers [buildContext,executeAgentInference,runAgent] and callees [get(.framework/functions/item-progress.ts),embeddings(.framework/migrations/001_schema.sql),adminDb(.framework/functions/_shared/db.ts),Type(.framework/src/pages/admin/TypesPage.tsx),rank(custom/apps/cortex/functions/custom_cil-embeddings.ts),title(.framework/src/components/runtime/DataDetailPage.tsx),failed(custom/apps/cortex/functions/custom_kb-chunker-test.ts),context(.framework/functions/auth.ts)] shape downstream behavior." status="ready"]
121
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
122
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
547
123
  async function retrieveKnowledge(
548
124
  query: string,
549
125
  knowledgeSources: string[],
@@ -553,47 +129,33 @@ async function retrieveKnowledge(
553
129
  vector_types?: string[]
554
130
  max_security_level?: 'external' | 'internal' | 'restricted'
555
131
  require_approved?: boolean
556
- }
132
+ project_ids?: string[]
133
+ },
134
+ ctx?: CoreContext
557
135
  ): Promise<{ docs: any[]; meanSimilarity: number }> {
136
+ return withSpan(ctx || ({ trace: undefined } as CoreContext), 'agent.retrieve_knowledge', async (ctx) => {
558
137
  const SIMILARITY_THRESHOLD = 0.15
559
138
  const MATCH_COUNT = 8
560
139
  const policy = retrievalPolicy || {}
561
140
  const vectorTypesToSearch = policy.vector_types?.length ? policy.vector_types : ['semantic']
562
141
  const maxSecurityLevel = policy.max_security_level || 'internal'
563
-
564
- // Security level ordering: external < internal < restricted
565
142
  const SECURITY_ORDER: Record<string, number> = { external: 0, internal: 1, restricted: 2 }
566
143
  const maxSecurityRank = SECURITY_ORDER[maxSecurityLevel] ?? 1
567
-
568
- // ── Tier 1: vector similarity via match_embeddings RPC ──────────────────
144
+ const projectIds = policy.project_ids || []
145
+ const hasProjectFilter = projectIds.length > 0
569
146
  try {
570
- const apiKey = process.env.OPENAI_API_KEY || process.env.LLM_API_KEY
571
- const baseUrl = process.env.OPENAI_BASE_URL || 'https://api.openai.com/v1'
572
- if (!apiKey) throw new Error('no_api_key')
573
-
574
- // Resolve platform account — always include spine-system KB
575
147
  const { data: platformAccount } = await adminDb
576
148
  .from('accounts').select('id').eq('slug', 'spine-system').maybeSingle()
577
149
  const accountIds: string[] = [accountId]
578
150
  if (platformAccount?.id && platformAccount.id !== accountId) accountIds.push(platformAccount.id)
579
-
580
- // Generate query embedding
581
- const embedRes = await fetch(`${baseUrl}/embeddings`, {
582
- method: 'POST',
583
- headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}` },
584
- body: JSON.stringify({ model: 'text-embedding-3-small', input: query }),
585
- })
586
- if (!embedRes.ok) throw new Error(`embed_api_${embedRes.status}`)
587
- const embedJson: any = await embedRes.json()
151
+ const embedJson = await callOpenAIEmbeddings(ctx, [query])
588
152
  const queryEmbedding: number[] = embedJson.data?.[0]?.embedding || []
589
153
  if (!queryEmbedding.length) throw new Error('empty_embedding')
590
-
591
- // Multi-vector search: run all declared vector types and merge
592
154
  const allMatches: any[] = []
593
155
  for (const vt of vectorTypesToSearch) {
594
156
  const { data: matches, error: rpcError } = await adminDb.rpc('match_embeddings', {
595
157
  query_embedding: `[${queryEmbedding.join(',')}]`,
596
- match_count: MATCH_COUNT * 3,
158
+ match_count: MATCH_COUNT * (hasProjectFilter ? 5 : 3),
597
159
  filter_account_ids: accountIds,
598
160
  filter_vector_type: vt,
599
161
  similarity_threshold: SIMILARITY_THRESHOLD,
@@ -602,23 +164,26 @@ async function retrieveKnowledge(
602
164
  if (rpcError) throw new Error(`rpc_${rpcError.message}`)
603
165
  allMatches.push(...(matches || []))
604
166
  }
605
-
606
- // Hard security filter
607
167
  const filtered = allMatches.filter((r: any) => {
608
168
  if (r.similarity < SIMILARITY_THRESHOLD) return false
609
169
  const secLevel = r.metadata?.security_level || 'internal'
610
170
  const rank = SECURITY_ORDER[secLevel] ?? 1
611
171
  if (rank > maxSecurityRank) return false
612
172
  if (r.account_id !== accountId && secLevel === 'restricted') return false
173
+ // When project_ids are specified, only keep cil_chunk embeddings whose
174
+ // metadata.project_id is in the allowed list. Non-cil_chunk items pass.
175
+ if (hasProjectFilter) {
176
+ const itemType = r.metadata?.item_type
177
+ if (itemType === 'cil_chunk') {
178
+ const projectId = r.metadata?.project_id
179
+ if (!projectId || !projectIds.includes(projectId)) return false
180
+ }
181
+ }
613
182
  return true
614
183
  })
615
-
616
- // Narrow to knowledgeSources if specified
617
184
  const scoped = knowledgeSources.length > 0
618
185
  ? filtered.filter((r: any) => knowledgeSources.includes(r.document_id))
619
186
  : filtered
620
-
621
- // Deduplicate — best chunk per document across all vector types
622
187
  const bestByDoc = new Map<string, any>()
623
188
  for (const r of scoped) {
624
189
  const existing = bestByDoc.get(r.document_id)
@@ -627,14 +192,11 @@ async function retrieveKnowledge(
627
192
  const deduped = [...bestByDoc.values()]
628
193
  .sort((a, b) => b.similarity - a.similarity)
629
194
  .slice(0, MATCH_COUNT)
630
-
631
195
  if (!deduped.length) return { docs: [], meanSimilarity: 0 }
632
-
633
- // If top result < 0.50 and summary not already included, try summary fallback
634
196
  if (deduped[0]?.similarity < 0.50 && !vectorTypesToSearch.includes('summary')) {
635
197
  const { data: summaryMatches } = await adminDb.rpc('match_embeddings', {
636
198
  query_embedding: `[${queryEmbedding.join(',')}]`,
637
- match_count: MATCH_COUNT,
199
+ match_count: MATCH_COUNT * (hasProjectFilter ? 5 : 1),
638
200
  filter_account_ids: accountIds,
639
201
  filter_vector_type: 'summary',
640
202
  similarity_threshold: SIMILARITY_THRESHOLD,
@@ -649,12 +211,9 @@ async function retrieveKnowledge(
649
211
  const finalDocs = [...bestByDoc.values()]
650
212
  .sort((a, b) => b.similarity - a.similarity)
651
213
  .slice(0, MATCH_COUNT)
652
-
653
- // Enrich with item titles
654
214
  const { data: items } = await adminDb
655
215
  .from('items').select('id, title').in('id', finalDocs.map((r: any) => r.document_id))
656
216
  const itemMap = new Map((items || []).map((i: any) => [i.id, i]))
657
-
658
217
  const docs = finalDocs.map((r: any) => ({
659
218
  document_id: r.document_id,
660
219
  content: r.content,
@@ -664,46 +223,51 @@ async function retrieveKnowledge(
664
223
  item_type: r.metadata?.item_type || '',
665
224
  vector_type: r.metadata?.vector_type || '',
666
225
  }))
667
-
668
226
  const meanSimilarity = docs.length
669
227
  ? docs.reduce((sum: number, d: any) => sum + d.similarity, 0) / docs.length
670
228
  : 0
671
-
672
229
  return { docs, meanSimilarity }
673
-
674
230
  } catch (vectorErr: any) {
675
- // ── Tier 2: full-text fallback ──────────────────────────────────────
676
231
  if (vectorErr.message !== 'no_api_key') {
677
232
  console.warn('retrieveKnowledge vector search failed, falling back to text search:', vectorErr.message)
678
233
  }
679
- const baseQuery = adminDb
234
+ let baseQuery = adminDb
680
235
  .from('embeddings')
681
236
  .select('document_id, content, metadata, account_id')
682
237
  .eq('account_id', accountId)
683
238
  .eq('metadata->>vector_type', 'semantic')
684
- .not('revoked_at', 'is', null)
239
+ .is('revoked_at', null)
685
240
  .textSearch('content', query, { type: 'websearch', config: 'english' })
686
241
  .limit(5)
242
+ if (policy.item_types?.length) {
243
+ baseQuery = baseQuery.in('metadata->>item_type', policy.item_types)
244
+ }
245
+ if (hasProjectFilter) {
246
+ const ids = projectIds.map((id: string) => `${id}`).join(',')
247
+ baseQuery = baseQuery.or(`metadata->>item_type.neq.cil_chunk,metadata->>project_id.in.(${ids})`)
248
+ }
687
249
  const q = knowledgeSources.length > 0 ? baseQuery.in('document_id', knowledgeSources) : baseQuery
688
250
  const { data: docs, error } = await q
689
251
  if (error) { console.error('Knowledge retrieval fallback failed:', error); return { docs: [], meanSimilarity: 0 } }
690
252
  return { docs: docs || [], meanSimilarity: 0 }
691
253
  }
254
+ }, { onEnd: emitSpan })
692
255
  }
693
-
694
- /**
695
- * Loads the most recent N messages from a thread, ordered chronologically.
696
- *
697
- * Returns messages in ascending order (oldest first) for context assembly.
698
- * Returns empty array on error (never throws).
699
- *
700
- * @param threadId - UUID of the thread
701
- * @param limit - Maximum number of messages to return (from promptConfig.max_history_messages)
702
- * @returns Promise<any[]> — array of messages rows ordered oldest-first
703
- * @throws never — returns [] on failure
704
- * @sideEffects DB read: messages table
705
- * @calledBy buildContext
706
- */
256
+ // CIL[END chunk_id="agent-runner_retrieveknowledge_1784488696"]
257
+ // CIL[BEGIN chunk_id="agent-runner_getconversationhistory_1784488696" type="function" identity="getConversationHistory" parent="none"]
258
+ // CIL[DOC-MACRO purpose="Load recent messages for a thread in chronological order"]
259
+ // CIL[DOC-RESPONSIBILITY responsibility="Fetch messages from the messages table and return oldest-first"]
260
+ // CIL[DOC-CONTEXT module="_shared/agent-runner" layer="infrastructure" related_files=".framework/functions/_shared/db.ts,.framework/functions/auth.ts,.framework/migrations/001_schema.sql,.framework/src/apps/admin/index.tsx,.framework/src/components/ui/chart.tsx"]
261
+ // CIL[DOC-CONTRACT inputs="threadId: string\nlimit: number" outputs="messages[] ordered oldest-first" errors="returns [] on failure" side_effects="DB read messages table"]
262
+ // CIL[DOC-MICRO logic_summary="Queries messages by thread_id, orders descending, reverses to ascending, and returns result" notable_decisions="[]"]
263
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
264
+ // CIL[DOC-STABILITY stability="stable"]
265
+ // CIL[DOC-VECTOR type="context" content="Layer: infrastructure. Direct callers: buildContext,executeAgentInference,runAgent. Direct callees: adminDb(.framework/functions/_shared/db.ts),context(.framework/functions/auth.ts). Related files: .framework/functions/_shared/db.ts,.framework/functions/auth.ts,.framework/migrations/001_schema.sql,.framework/src/apps/admin/index.tsx,.framework/src/components/ui/chart.tsx." status="ready"]
266
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: buildContext,executeAgentInference,runAgent. Total affected: 3." status="ready"]
267
+ // CIL[DOC-VECTOR type="configuration" content="Config dependencies: none. Test paths: none." status="ready"]
268
+ // CIL[DOC-VECTOR type="constraint" content="Driven by chunk inputs; callers [buildContext,executeAgentInference,runAgent] and callees [adminDb(.framework/functions/_shared/db.ts),context(.framework/functions/auth.ts)] shape downstream behavior." status="ready"]
269
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
270
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
707
271
  async function getConversationHistory(
708
272
  threadId: string,
709
273
  limit: number
@@ -714,101 +278,67 @@ async function getConversationHistory(
714
278
  .eq('thread_id', threadId)
715
279
  .order('created_at', { ascending: false })
716
280
  .limit(limit)
717
-
718
281
  if (error) {
719
282
  console.error('Failed to load history:', error)
720
283
  return []
721
284
  }
722
-
723
285
  return messages?.reverse() || []
724
286
  }
725
-
726
- // ─── INFERENCE CALL ────────────────────────────────────────────────────────────
727
-
728
- /**
729
- * Calls an OpenAI-compatible chat completions API with the assembled context.
730
- *
731
- * Credentials and base URL are loaded from environment variables:
732
- * - `OPENAI_API_KEY` / `ANTHROPIC_API_KEY` / `LLM_API_KEY`
733
- * - `OPENAI_BASE_URL` / `LLM_BASE_URL` (default: 'https://api.openai.com/v1')
734
- * - `LLM_DEFAULT_MODEL` (default: 'gpt-4o')
735
- *
736
- * If no API key is found, returns a mock response instead of throwing.
737
- * This allows local development without LLM credentials.
738
- *
739
- * Tool calls are extracted from `message.tool_calls` and mapped to `ToolCall[]`.
740
- * Confidence is derived from `logprobs` if available, otherwise defaults to 0.85.
741
- *
742
- * @param context - Assembled context string from buildContext
743
- * @param agent - Agent record with model_config (model, temperature, max_tokens, tools)
744
- * @param promptConfig - Prompt config record (unused here beyond model overrides)
745
- * @param ctx - CoreContext (requestId for logging)
746
- * @returns Promise<InferenceResult> — content, confidence, tool_calls, metadata
747
- * @throws Error('Inference failed: <status>') on non-2xx HTTP response
748
- * @sideEffects HTTP call to `${OPENAI_BASE_URL}/chat/completions`
749
- * @calledBy executeAgentInference (per iteration)
750
- */
287
+ // CIL[END chunk_id="agent-runner_getconversationhistory_1784488696"]
288
+ // CIL[BEGIN chunk_id="agent-runner_callinference_1784488696" type="function" identity="callInference" parent="none"]
289
+ // CIL[DOC-MACRO purpose="Call an OpenAI-compatible chat completions endpoint"]
290
+ // CIL[DOC-RESPONSIBILITY responsibility="Send assembled context to the LLM and return content, confidence, tool_calls, and metadata"]
291
+ // CIL[DOC-CONTEXT module="_shared/agent-runner" layer="infrastructure" related_files=".framework/src/pages/admin/TypesPage.tsx,.framework/src/components/runtime/DataDetailPage.tsx,.framework/functions/auth.ts,custom/apps/cortex/functions/custom_kb-chunker-test.ts,custom/apps/cortex/functions/custom_cil-callmap.ts,.framework/cli/commands/system.ts"]
292
+ // CIL[DOC-CONTRACT inputs="context: string\nagent: any\npromptConfig: any\nctx: CoreContext" outputs="InferenceResult" errors="throws on non-2xx response" side_effects="HTTP POST to chat completions API"]
293
+ // CIL[DOC-MICRO logic_summary="Builds messages array from context, calls completions, extracts tool_calls and confidence from logprobs or default" notable_decisions="[]"]
294
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
295
+ // CIL[DOC-STABILITY stability="stable"]
296
+ // CIL[DOC-VECTOR type="context" content="Layer: infrastructure. Direct callers: executeAgentInference,runAgent. Direct callees: Type(.framework/src/pages/admin/TypesPage.tsx),params(.framework/src/components/runtime/DataDetailPage.tsx),context(.framework/functions/auth.ts),failed(custom/apps/cortex/functions/custom_kb-chunker-test.ts). Related files: .framework/src/pages/admin/TypesPage.tsx,.framework/src/components/runtime/DataDetailPage.tsx,.framework/functions/auth.ts,custom/apps/cortex/functions/custom_kb-chunker-test.ts,custom/apps/cortex/functions/custom_cil-callmap.ts,.framework/cli/commands/system.ts." status="ready"]
297
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: executeAgentInference,runAgent. Total affected: 2." status="ready"]
298
+ // CIL[DOC-VECTOR type="configuration" content="Config dependencies: none. Test paths: none." status="ready"]
299
+ // CIL[DOC-VECTOR type="constraint" content="Driven by chunk inputs; callers [executeAgentInference,runAgent] and callees [Type(.framework/src/pages/admin/TypesPage.tsx),params(.framework/src/components/runtime/DataDetailPage.tsx),context(.framework/functions/auth.ts),failed(custom/apps/cortex/functions/custom_kb-chunker-test.ts)] shape downstream behavior." status="ready"]
300
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
301
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
751
302
  async function callInference(
752
303
  context: string,
753
304
  agent: any,
754
305
  promptConfig: any,
755
306
  ctx: CoreContext
756
307
  ): Promise<InferenceResult> {
308
+ return withSpan(ctx, 'agent.inference', async (ctx) => {
757
309
  const modelConfig = agent.model_config || {}
758
-
759
- // Get LLM credentials from environment (safer than DB storage)
760
- const apiKey = process.env.OPENAI_API_KEY || process.env.ANTHROPIC_API_KEY || process.env.LLM_API_KEY
761
- const baseUrl = process.env.OPENAI_BASE_URL || process.env.LLM_BASE_URL || 'https://api.openai.com/v1'
762
-
763
- if (!apiKey) {
764
- // Fallback: return mock response for development
765
- console.warn('No LLM API key found in environment. Set OPENAI_API_KEY or LLM_API_KEY.')
766
- return {
767
- content: `[Mock Response] Received ${context.length} chars of context. Set OPENAI_API_KEY in .env for live inference.`,
768
- confidence: 0.9,
769
- tool_calls: undefined,
770
- metadata: { mock: true }
771
- }
772
- }
773
-
774
310
  const model = modelConfig.model || process.env.LLM_DEFAULT_MODEL || 'gpt-4o'
775
311
  const temperature = modelConfig.temperature ?? 0.7
776
312
  const maxTokens = modelConfig.max_tokens ?? 4000
313
+ const messages = [
314
+ { role: 'system', content: context.split('\n\n')[0] },
315
+ { role: 'user', content: context.split('\n\n').slice(1).join('\n\n') }
316
+ ]
777
317
 
778
- // Call OpenAI-compatible API
779
- const response = await fetch(`${baseUrl}/chat/completions`, {
780
- method: 'POST',
781
- headers: {
782
- 'Content-Type': 'application/json',
783
- 'Authorization': `Bearer ${apiKey}`
784
- },
785
- body: JSON.stringify({
786
- model,
787
- messages: [
788
- { role: 'system', content: context.split('\n\n')[0] }, // First paragraph as system
789
- { role: 'user', content: context.split('\n\n').slice(1).join('\n\n') } // Rest as user
790
- ],
318
+ let result: any
319
+ try {
320
+ result = await callOpenAIChat(ctx, messages, model, {
791
321
  temperature,
792
322
  max_tokens: maxTokens,
793
323
  ...(modelConfig.tools?.length ? { tools: modelConfig.tools, tool_choice: 'auto' } : {})
794
324
  })
795
- })
796
-
797
- if (!response.ok) {
798
- const errorBody = await response.text()
799
- throw new Error(`Inference failed: ${response.status} ${response.statusText} - ${errorBody}`)
325
+ } catch (err: any) {
326
+ if (err?.message?.includes('not found') || err?.message?.includes('credential')) {
327
+ console.warn('No OpenAI integration or credential configured, returning mock inference response')
328
+ return {
329
+ content: `[Mock Response] Received ${context.length} chars of context. Set OPENAI_API_KEY in .env or configure the openai integration for live inference.`,
330
+ confidence: 0.9,
331
+ tool_calls: undefined,
332
+ metadata: { mock: true }
333
+ }
334
+ }
335
+ throw err
800
336
  }
801
337
 
802
- const result: any = await response.json()
803
338
  const message = result.choices?.[0]?.message
804
-
805
- // Extract confidence from logprobs if available, otherwise estimate
806
- // Default is conservative (0.70) when logprobs are absent — retrieval quality
807
- // should drive the final composite confidence, not an arbitrary high default.
808
339
  const confidence = result.choices?.[0]?.logprobs?.content?.[0]?.logprob
809
340
  ? Math.exp(result.choices[0].logprobs.content[0].logprob)
810
341
  : 0.70
811
-
812
342
  return {
813
343
  content: message?.content || '',
814
344
  confidence,
@@ -823,260 +353,253 @@ async function callInference(
823
353
  finish_reason: result.choices?.[0]?.finish_reason
824
354
  }
825
355
  }
356
+ }, { onEnd: emitSpan })
826
357
  }
827
-
828
- /**
829
- * Legacy webhook-based inference handler. Calls an arbitrary URL with the
830
- * config as JSON body. Returns a mock if `config.url` is not set.
831
- *
832
- * @deprecated Use `callInference` with environment-based credentials instead.
833
- * @throws Error on non-2xx HTTP response
834
- * @sideEffects HTTP call to config.url
835
- * @calledBy unused (kept for backward compatibility)
836
- */
358
+ // CIL[END chunk_id="agent-runner_callinference_1784488696"]
359
+ // CIL[BEGIN chunk_id="agent-runner_executeinferencehandler_1784488696" type="function" identity="executeInferenceHandler" parent="none"]
360
+ // CIL[DOC-MACRO purpose="Legacy webhook-based inference handler"]
361
+ // CIL[DOC-RESPONSIBILITY responsibility="Dispatch inference to an arbitrary webhook URL for backward compatibility"]
362
+ // CIL[DOC-CONTEXT module="_shared/agent-runner" layer="infrastructure" related_files=".framework/functions/tests.ts,.framework/src/pages/admin/TypesPage.tsx,custom/apps/cortex/functions/custom_kb-chunker-test.ts,.framework/functions/auth.ts,.framework/functions/_shared/webhook-registry.ts,.framework/functions/invites.ts,.framework/functions/item-progress.ts"]
363
+ // CIL[DOC-CONTRACT inputs="config: any\nctx: CoreContext" outputs="JSON response" errors="throws on non-2xx" side_effects="HTTP call to config.url"]
364
+ // CIL[DOC-MICRO logic_summary="Returns mock if url is unset; otherwise POSTs config JSON to url" notable_decisions="[]"]
365
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
366
+ // CIL[DOC-STABILITY stability="deprecated"]
367
+ // CIL[DOC-VECTOR type="context" content="Layer: infrastructure. Direct callers: none. Direct callees: handler(.framework/functions/tests.ts),Type(.framework/src/pages/admin/TypesPage.tsx),failed(custom/apps/cortex/functions/custom_kb-chunker-test.ts),context(.framework/functions/auth.ts). Related files: .framework/functions/tests.ts,.framework/src/pages/admin/TypesPage.tsx,custom/apps/cortex/functions/custom_kb-chunker-test.ts,.framework/functions/auth.ts,.framework/functions/_shared/webhook-registry.ts,.framework/functions/invites.ts,.framework/functions/item-progress.ts." status="ready"]
368
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: none. Total affected: 0." status="ready"]
369
+ // CIL[DOC-VECTOR type="configuration" content="Config dependencies: none. Test paths: none." status="ready"]
370
+ // CIL[DOC-VECTOR type="constraint" content="Driven by chunk inputs; callers [none] and callees [handler(.framework/functions/tests.ts),Type(.framework/src/pages/admin/TypesPage.tsx),failed(custom/apps/cortex/functions/custom_kb-chunker-test.ts),context(.framework/functions/auth.ts)] shape downstream behavior." status="ready"]
371
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
372
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
837
373
  async function executeInferenceHandler(
838
374
  config: any,
839
375
  ctx: CoreContext
840
376
  ): Promise<any> {
841
377
  const { url, method = 'POST', headers = {} } = config
842
-
843
378
  if (!url) {
844
- // Fallback: return mock response for development
845
379
  console.warn('No inference URL configured, returning mock response')
846
380
  return {
847
381
  content: `[Mock] I received your message. In production, this would call ${config.model} via webhook.`,
848
382
  confidence: 0.9
849
383
  }
850
384
  }
851
-
852
- const response = await fetch(url, {
853
- method,
854
- headers: {
855
- 'Content-Type': 'application/json',
856
- ...headers
857
- },
858
- body: JSON.stringify(config)
385
+ const result = await executeOutbound(ctx, {
386
+ integration_slug: 'generic-http',
387
+ action_config: { url, method: method || 'POST', headers, body: config }
859
388
  })
860
-
861
- if (!response.ok) {
862
- throw new Error(`Inference failed: ${response.status} ${response.statusText}`)
389
+ if (!result.success) {
390
+ throw new Error(`Inference failed: ${result.response_status} ${result.response_body}`)
863
391
  }
864
-
865
- return await response.json()
392
+ return result.response_body
866
393
  }
867
-
868
- // ─── TOOL DISPATCH ────────────────────────────────────────────────────────────
869
-
870
- /**
871
- * Executes a batch of tool calls in sequence, returning results for each.
872
- *
873
- * For each tool call:
874
- * 1. Look up action by `call.tool` (action.slug) in the actions table
875
- * 2. Call `executeToolAction` to dispatch to the correct handler
876
- * 3. Push `ToolResult` with success output or error message
877
- *
878
- * Individual tool failures are captured in `ToolResult.error` and do NOT
879
- * halt the batch — all tool calls are attempted.
880
- *
881
- * @param toolCalls - Array of ToolCall objects from an InferenceResult
882
- * @param ctx - CoreContext
883
- * @returns Promise<ToolResult[]> — one result per input tool call
884
- * @throws never — per-tool errors captured in result.error
885
- * @sideEffects DB read: actions table (per tool call)
886
- * @sideEffects Calls executeToolAction (DB writes, HTTP calls per handler)
887
- * @calledBy executeAgentInference (after each inference call with tool_calls)
888
- * @calls executeToolAction
889
- */
394
+ // CIL[END chunk_id="agent-runner_executeinferencehandler_1784488696"]
395
+ // CIL[BEGIN chunk_id="agent-runner_dispatchtools_1784488696" type="function" identity="dispatchTools" parent="none"]
396
+ // CIL[DOC-MACRO purpose="Execute a batch of tool calls in sequence"]
397
+ // CIL[DOC-RESPONSIBILITY responsibility="Look up each tool action and dispatch to executeToolAction, collecting ToolResult entries"]
398
+ // CIL[DOC-CONTEXT module="_shared/agent-runner" layer="infrastructure" related_files=".framework/functions/tests.ts,.framework/src/components/runtime/DataDetailPage.tsx,.framework/functions/_shared/db.ts,.framework/functions/auth.ts"]
399
+ // CIL[DOC-CONTRACT inputs="toolCalls: ToolCall[]\nctx: CoreContext" outputs="ToolResult[]" errors="none; per-tool errors captured in result.error" side_effects="DB reads actions table; calls handlers; resolves built-in CIL tools directly"]
400
+ // CIL[DOC-MICRO logic_summary="Iterates toolCalls, routes CIL tool slugs directly to executeToolAction, loads other actions by slug, executes handler, and pushes success or error results" notable_decisions="[]"]
401
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
402
+ // CIL[DOC-STABILITY stability="stable"]
403
+ // CIL[DOC-VECTOR type="context" content="Layer: infrastructure. Direct callers: executeAgentInference,runAgent. Direct callees: handler(.framework/functions/tests.ts),executeToolAction,params(.framework/src/components/runtime/DataDetailPage.tsx),adminDb(.framework/functions/_shared/db.ts),context(.framework/functions/auth.ts). Related files: .framework/functions/tests.ts,.framework/src/components/runtime/DataDetailPage.tsx,.framework/functions/_shared/db.ts,.framework/functions/auth.ts." status="ready"]
404
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: executeAgentInference,runAgent. Total affected: 2." status="ready"]
405
+ // CIL[DOC-VECTOR type="configuration" content="Config dependencies: none. Test paths: none." status="ready"]
406
+ // CIL[DOC-VECTOR type="constraint" content="Driven by chunk inputs; callers [executeAgentInference,runAgent] and callees [handler(.framework/functions/tests.ts),executeToolAction,params(.framework/src/components/runtime/DataDetailPage.tsx),adminDb(.framework/functions/_shared/db.ts),context(.framework/functions/auth.ts)] shape downstream behavior." status="ready"]
407
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
408
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
890
409
  async function dispatchTools(
891
410
  toolCalls: ToolCall[],
892
411
  ctx: CoreContext
893
412
  ): Promise<ToolResult[]> {
894
- const results: ToolResult[] = []
895
-
896
- for (const call of toolCalls) {
897
- try {
898
- // Lookup action for this tool
899
- const { data: action, error } = await adminDb
900
- .from('actions')
901
- .select('*')
902
- .eq('slug', call.tool)
903
- .eq('is_active', true)
904
- .single()
905
-
906
- if (error || !action) {
907
- results.push({
908
- tool: call.tool,
909
- id: call.id,
910
- result: null,
911
- error: `Tool "${call.tool}" not found`
912
- })
913
- continue
914
- }
915
-
916
- // Execute tool via pipeline-runner pattern
917
- const result = await executeToolAction(action, call.params, ctx)
918
-
919
- results.push({
920
- tool: call.tool,
921
- id: call.id,
922
- result
923
- })
924
-
925
- } catch (error: any) {
926
- results.push({
927
- tool: call.tool,
928
- id: call.id,
929
- result: null,
930
- error: error.message
931
- })
932
- }
933
- }
934
-
935
- return results
413
+ return withSpan(ctx, 'agent.dispatch_tools', async (ctx) => {
414
+ return Promise.all(toolCalls.map(async (call) => {
415
+ return withSpan(ctx, `agent.tool.${call.tool}`, async (ctx) => {
416
+ try {
417
+ // CIL tools are built into the runner and do not need an actions table row.
418
+ if (CIL_TOOL_SLUGS.has(call.tool)) {
419
+ const result = await executeToolAction({ handler: call.tool }, call.params, ctx)
420
+ return {
421
+ tool: call.tool,
422
+ id: call.id,
423
+ result
424
+ }
425
+ }
426
+ const { data: action, error } = await adminDb
427
+ .from('actions')
428
+ .select('*')
429
+ .eq('slug', call.tool)
430
+ .eq('is_active', true)
431
+ .single()
432
+ if (error || !action) {
433
+ return {
434
+ tool: call.tool,
435
+ id: call.id,
436
+ result: null,
437
+ error: `Tool "${call.tool}" not found`
438
+ }
439
+ }
440
+ const result = await executeToolAction(action, call.params, ctx)
441
+ return {
442
+ tool: call.tool,
443
+ id: call.id,
444
+ result
445
+ }
446
+ } catch (error: any) {
447
+ return {
448
+ tool: call.tool,
449
+ id: call.id,
450
+ result: null,
451
+ error: error.message
452
+ }
453
+ }
454
+ }, { onEnd: emitSpan })
455
+ }))
456
+ }, { onEnd: emitSpan })
936
457
  }
937
-
938
- /**
939
- * Dispatches a single tool action to its handler based on `action.handler`.
940
- *
941
- * Supported handlers (mirrors pipeline-runner stageHandlers):
942
- * - `search_knowledge` → `executeSearchKnowledge`
943
- * - `query_items` → `executeQueryItems`
944
- * - `create_record` → `executeCreateRecord`
945
- * - `update_item` → `executeUpdateItem`
946
- * - `run_pipeline` → `runPipeline` (dynamic import to avoid circular deps)
947
- * - `send_notification` → `executeSendNotification`
948
- *
949
- * @param action - Action record from the actions table
950
- * @param params - Tool call parameters (from LLM function_call.arguments)
951
- * @param ctx - CoreContext
952
- * @returns Promise<any> — handler output
953
- * @throws Error('Unknown tool handler') on unrecognised action.handler
954
- * @calledBy dispatchTools
955
- */
458
+ // CIL[END chunk_id="agent-runner_dispatchtools_1784488696"]
459
+ // CIL[BEGIN chunk_id="agent-runner_executetoolaction_1784488696" type="function" identity="executeToolAction" parent="none"]
460
+ // CIL[DOC-MACRO purpose="Dispatch a single tool action to its handler"]
461
+ // CIL[DOC-RESPONSIBILITY responsibility="Route action.handler to the correct tool handler implementation"]
462
+ // CIL[DOC-CONTEXT module="_shared/agent-runner" layer="infrastructure" related_files=".framework/src/components/ui/ItemListView.tsx,.framework/src/components/runtime/DataDetailPage.tsx,.framework/functions/tests.ts,.framework/functions/_shared/index.ts,.framework/functions/auth.ts,custom/apps/cortex/functions/custom_cil-callmap.ts"]
463
+ // CIL[DOC-CONTRACT inputs="action: any\nparams: any\nctx: CoreContext" outputs="handler output" errors="throws Unknown tool handler" side_effects="May dynamically import pipeline-runner and call handlers; calls CIL tool handlers"]
464
+ // CIL[DOC-MICRO logic_summary="Dynamically imports pipeline-runner, switches on action.handler, and awaits the matching handler (including built-in CIL tools)" notable_decisions="[]"]
465
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
466
+ // CIL[DOC-STABILITY stability="stable"]
467
+ // CIL[DOC-VECTOR type="context" content="Layer: infrastructure. Direct callers: dispatchTools,executeAgentInference,runAgent. Direct callees: executeSearchKnowledge,executeSendNotification,imports(.framework/src/components/ui/ItemListView.tsx),executeUpdateItem,executeQueryItems,executeCreateRecord,params(.framework/src/components/runtime/DataDetailPage.tsx),handler(.framework/functions/tests.ts),pipeline(.framework/functions/_shared/index.ts),context(.framework/functions/auth.ts). Related files: .framework/src/components/ui/ItemListView.tsx,.framework/src/components/runtime/DataDetailPage.tsx,.framework/functions/tests.ts,.framework/functions/_shared/index.ts,.framework/functions/auth.ts,custom/apps/cortex/functions/custom_cil-callmap.ts." status="ready"]
468
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: dispatchTools,executeAgentInference,runAgent. Total affected: 3." status="ready"]
469
+ // CIL[DOC-VECTOR type="configuration" content="Config dependencies: none. Test paths: none." status="ready"]
470
+ // CIL[DOC-VECTOR type="constraint" content="Driven by chunk inputs; callers [dispatchTools,executeAgentInference,runAgent] and callees [executeSearchKnowledge,executeSendNotification,imports(.framework/src/components/ui/ItemListView.tsx),executeUpdateItem,executeQueryItems,executeCreateRecord,params(.framework/src/components/runtime/DataDetailPage.tsx),handler(.framework/functions/tests.ts),pipeline(.framework/functions/_shared/index.ts),context(.framework/functions/auth.ts)] shape downstream behavior." status="ready"]
471
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
472
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
956
473
  async function executeToolAction(
957
474
  action: any,
958
475
  params: any,
959
476
  ctx: CoreContext
960
477
  ): Promise<any> {
961
- // Import pipeline-runner handlers dynamically to avoid circular deps
962
- const { runPipeline } = await import('./pipeline-runner')
963
-
964
- switch (action.handler) {
965
- case 'search_knowledge':
966
- return await executeSearchKnowledge(params, ctx)
967
-
968
- case 'query_items':
969
- return await executeQueryItems(params, ctx)
970
-
971
- case 'create_record':
972
- return await executeCreateRecord(params, ctx)
973
-
974
- case 'update_item':
975
- return await executeUpdateItem(params, ctx)
976
-
977
- case 'run_pipeline':
978
- const result = await runPipeline(params.pipeline_id, params.trigger_data || {}, ctx)
979
- return { success: result.status === 'completed', result }
980
-
981
- case 'send_notification':
982
- return await executeSendNotification(params, ctx)
983
-
984
- default:
985
- throw new Error(`Unknown tool handler: ${action.handler}`)
986
- }
478
+ return withSpan(ctx, `agent.tool_action.${action.handler || 'unknown'}`, async (ctx) => {
479
+ const { runPipeline } = await import('./pipeline-runner')
480
+ switch (action.handler) {
481
+ case 'search_knowledge':
482
+ return await executeSearchKnowledge(params, ctx)
483
+ case 'query_items':
484
+ return await executeQueryItems(params, ctx)
485
+ case 'create_record':
486
+ return await executeCreateRecord(params, ctx)
487
+ case 'update_item':
488
+ return await executeUpdateItem(params, ctx)
489
+ case 'run_pipeline':
490
+ const result = await runPipeline(params.pipeline_id, params.trigger_data || {}, ctx)
491
+ return { success: result.status === 'completed', result }
492
+ case 'send_notification':
493
+ return await executeSendNotification(params, ctx)
494
+ case 'cil_chunk_lookup':
495
+ return await executeCilChunkLookup(params, ctx)
496
+ case 'cil_get_contract':
497
+ return await executeCilGetContract(params, ctx)
498
+ case 'cil_blast_radius':
499
+ return await executeCilBlastRadius(params, ctx)
500
+ case 'cil_hash_validate':
501
+ return await executeCilHashValidate(params, ctx)
502
+ case 'cil_project_status':
503
+ return await executeCilProjectStatus(params, ctx)
504
+ case 'search_logs':
505
+ return await executeSearchLogs(params, ctx)
506
+ case 'search_observability':
507
+ return await executeSearchObservability(params, ctx)
508
+ default:
509
+ throw new Error(`Unknown tool handler: ${action.handler}`)
510
+ }
511
+ }, { onEnd: emitSpan })
987
512
  }
988
-
989
- // ─── TOOL HANDLERS ────────────────────────────────────────────────────────────
990
-
991
- /**
992
- * Tool: search_knowledge — vector similarity search via `search_similar_embeddings`
993
- * RPC, falling back to text search on error.
994
- *
995
- * @param params.query: string — search query text
996
- * @param params.embedding: number[] | undefined — pre-computed embedding vector
997
- * @param params.limit: number (default 5)
998
- * @param params.threshold: number (default 0.7)
999
- * @returns { results[], method: 'vector'|'text_fallback' }
1000
- * @throws never — falls back to text search on RPC failure
1001
- * @sideEffects DB: search_similar_embeddings RPC or embeddings text search
1002
- */
513
+ // CIL[END chunk_id="agent-runner_executetoolaction_1784488696"]
514
+ // CIL[BEGIN chunk_id="agent-runner_executesearchknowledge_1784488696" type="function" identity="executeSearchKnowledge" parent="none"]
515
+ // CIL[DOC-MACRO purpose="Tool handler: search_knowledge via vector similarity"]
516
+ // CIL[DOC-RESPONSIBILITY responsibility="Run search_similar_embeddings and fall back to text search"]
517
+ // CIL[DOC-CONTEXT module="_shared/agent-runner" layer="infrastructure" related_files=".framework/migrations/001_schema.sql,.framework/src/components/runtime/DataDetailPage.tsx,.framework/functions/_shared/db.ts,.framework/functions/tests.ts,.framework/functions/auth.ts"]
518
+ // CIL[DOC-CONTRACT inputs="params: any\nctx: CoreContext" outputs="{ results, method }" errors="returns text_fallback results on RPC failure" side_effects="DB RPC or text search on embeddings table"]
519
+ // CIL[DOC-MICRO logic_summary="Calls search_similar_embeddings with account scoping; on error runs textSearch on embeddings.content" notable_decisions="[]"]
520
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
521
+ // CIL[DOC-STABILITY stability="stable"]
522
+ // CIL[DOC-VECTOR type="context" content="Layer: infrastructure. Direct callers: executeToolAction,dispatchTools,executeAgentInference. Direct callees: embeddings(.framework/migrations/001_schema.sql),params(.framework/src/components/runtime/DataDetailPage.tsx),adminDb(.framework/functions/_shared/db.ts),handler(.framework/functions/tests.ts),context(.framework/functions/auth.ts). Related files: .framework/migrations/001_schema.sql,.framework/src/components/runtime/DataDetailPage.tsx,.framework/functions/_shared/db.ts,.framework/functions/tests.ts,.framework/functions/auth.ts." status="ready"]
523
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: executeToolAction,dispatchTools,executeAgentInference. Total affected: 3." status="ready"]
524
+ // CIL[DOC-VECTOR type="configuration" content="Config dependencies: none. Test paths: none." status="ready"]
525
+ // CIL[DOC-VECTOR type="constraint" content="Driven by chunk inputs; callers [executeToolAction,dispatchTools,executeAgentInference] and callees [embeddings(.framework/migrations/001_schema.sql),params(.framework/src/components/runtime/DataDetailPage.tsx),adminDb(.framework/functions/_shared/db.ts),handler(.framework/functions/tests.ts),context(.framework/functions/auth.ts)] shape downstream behavior." status="ready"]
526
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
527
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
1003
528
  async function executeSearchKnowledge(
1004
529
  params: any,
1005
530
  ctx: CoreContext
1006
531
  ): Promise<any> {
1007
532
  const { query, knowledge_sources, limit = 5 } = params
1008
-
1009
533
  const { data: results, error } = await adminDb.rpc('search_similar_embeddings', {
1010
534
  p_account_id: ctx.accountId,
1011
535
  p_model_id: params.model_id || 'text-embedding-ada-002',
1012
- p_query_embedding: params.embedding, // If pre-computed
536
+ p_query_embedding: params.embedding,
1013
537
  p_limit: limit,
1014
538
  p_threshold: params.threshold || 0.7
1015
539
  })
1016
-
1017
540
  if (error) {
1018
- // Fallback to text search
1019
541
  const { data: fallback } = await adminDb
1020
542
  .from('embeddings')
1021
543
  .select('*')
1022
544
  .eq('account_id', ctx.accountId)
1023
545
  .textSearch('content', query)
1024
546
  .limit(limit)
1025
-
1026
547
  return { results: fallback || [], method: 'text_fallback' }
1027
548
  }
1028
-
1029
549
  return { results: results || [], method: 'vector' }
1030
550
  }
1031
-
1032
- /**
1033
- * Tool: query_items — filtered SELECT on any table scoped to ctx.accountId.
1034
- *
1035
- * @param params.entity: string — table name
1036
- * @param params.filters: Record<string, any> (default {})
1037
- * @param params.limit: number (default 10)
1038
- * @returns { items[] }
1039
- * @throws Error on DB failure
1040
- * @sideEffects DB read: params.entity table
1041
- */
551
+ // CIL[END chunk_id="agent-runner_executesearchknowledge_1784488696"]
552
+ // CIL[BEGIN chunk_id="agent-runner_executequeryitems_1784488696" type="function" identity="executeQueryItems" parent="none"]
553
+ // CIL[DOC-MACRO purpose="Tool handler: query_items filtered SELECT"]
554
+ // CIL[DOC-RESPONSIBILITY responsibility="Run an account-scoped, filtered SELECT on a specified table"]
555
+ // CIL[DOC-CONTEXT module="_shared/agent-runner" layer="infrastructure" related_files="custom/apps/cortex/functions/custom_kb-chunker-test.ts,.framework/src/components/runtime/DataDetailPage.tsx,.framework/functions/_shared/db.ts,.framework/functions/tests.ts,.framework/functions/auth.ts"]
556
+ // CIL[DOC-CONTRACT inputs="params: any\nctx: CoreContext" outputs="{ items }" errors="throws on DB failure" side_effects="DB read of params.entity table"]
557
+ // CIL[DOC-MICRO logic_summary="Builds adminDb query with account filter and optional key-value filters, then returns rows" notable_decisions="[]"]
558
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
559
+ // CIL[DOC-STABILITY stability="stable"]
560
+ // CIL[DOC-VECTOR type="context" content="Layer: infrastructure. Direct callers: executeToolAction,dispatchTools,executeAgentInference. Direct callees: failed(custom/apps/cortex/functions/custom_kb-chunker-test.ts),params(.framework/src/components/runtime/DataDetailPage.tsx),adminDb(.framework/functions/_shared/db.ts),handler(.framework/functions/tests.ts),context(.framework/functions/auth.ts). Related files: custom/apps/cortex/functions/custom_kb-chunker-test.ts,.framework/src/components/runtime/DataDetailPage.tsx,.framework/functions/_shared/db.ts,.framework/functions/tests.ts,.framework/functions/auth.ts." status="ready"]
561
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: executeToolAction,dispatchTools,executeAgentInference. Total affected: 3." status="ready"]
562
+ // CIL[DOC-VECTOR type="configuration" content="Config dependencies: none. Test paths: none." status="ready"]
563
+ // CIL[DOC-VECTOR type="constraint" content="Driven by chunk inputs; callers [executeToolAction,dispatchTools,executeAgentInference] and callees [failed(custom/apps/cortex/functions/custom_kb-chunker-test.ts),params(.framework/src/components/runtime/DataDetailPage.tsx),adminDb(.framework/functions/_shared/db.ts),handler(.framework/functions/tests.ts),context(.framework/functions/auth.ts)] shape downstream behavior." status="ready"]
564
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
565
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
1042
566
  async function executeQueryItems(
1043
567
  params: any,
1044
568
  ctx: CoreContext
1045
569
  ): Promise<any> {
1046
570
  const { entity, filters = {}, limit = 10 } = params
1047
-
1048
571
  let query = adminDb
1049
572
  .from(entity)
1050
573
  .select('*')
1051
574
  .eq('account_id', ctx.accountId)
1052
575
  .limit(limit)
1053
-
1054
- // Apply filters
1055
576
  Object.entries(filters).forEach(([key, value]) => {
1056
577
  query = query.eq(key, value)
1057
578
  })
1058
-
1059
579
  const { data, error } = await query
1060
-
1061
580
  if (error) throw new Error(`Query failed: ${error.message}`)
1062
581
  return { items: data || [] }
1063
582
  }
1064
-
1065
- /**
1066
- * Tool: create_record — inserts a record into any table.
1067
- *
1068
- * @param params.entity: string — table name
1069
- * @param params.data: Record<string, any> — field values
1070
- * @returns { record } — the inserted row
1071
- * @throws Error on DB failure
1072
- * @sideEffects DB write: params.entity table
1073
- */
583
+ // CIL[END chunk_id="agent-runner_executequeryitems_1784488696"]
584
+ // CIL[BEGIN chunk_id="agent-runner_executecreaterecord_1784488696" type="function" identity="executeCreateRecord" parent="none"]
585
+ // CIL[DOC-MACRO purpose="Tool handler: create_record insert"]
586
+ // CIL[DOC-RESPONSIBILITY responsibility="Insert a record into any table, stamping account_id and audit fields"]
587
+ // CIL[DOC-CONTEXT module="_shared/agent-runner" layer="infrastructure" related_files=".framework/src/components/runtime/DataDetailPage.tsx,.framework/functions/_shared/db.ts,.framework/functions/tests.ts,custom/apps/cortex/functions/custom_kb-chunker-test.ts,.framework/functions/auth.ts,.framework/functions/admin-data.ts,.framework/functions/types.ts"]
588
+ // CIL[DOC-CONTRACT inputs="params: any\nctx: CoreContext" outputs="{ record }" errors="throws on DB failure" side_effects="DB write to params.entity table"]
589
+ // CIL[DOC-MICRO logic_summary="Inserts data with account_id, created_by and timestamps, then selects single row" notable_decisions="[]"]
590
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
591
+ // CIL[DOC-STABILITY stability="stable"]
592
+ // CIL[DOC-VECTOR type="context" content="Layer: infrastructure. Direct callers: executeToolAction,dispatchTools,executeAgentInference. Direct callees: params(.framework/src/components/runtime/DataDetailPage.tsx),adminDb(.framework/functions/_shared/db.ts),handler(.framework/functions/tests.ts),failed(custom/apps/cortex/functions/custom_kb-chunker-test.ts),context(.framework/functions/auth.ts). Related files: .framework/src/components/runtime/DataDetailPage.tsx,.framework/functions/_shared/db.ts,.framework/functions/tests.ts,custom/apps/cortex/functions/custom_kb-chunker-test.ts,.framework/functions/auth.ts,.framework/functions/admin-data.ts,.framework/functions/types.ts." status="ready"]
593
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: executeToolAction,dispatchTools,executeAgentInference. Total affected: 3." status="ready"]
594
+ // CIL[DOC-VECTOR type="configuration" content="Config dependencies: none. Test paths: none." status="ready"]
595
+ // CIL[DOC-VECTOR type="constraint" content="Driven by chunk inputs; callers [executeToolAction,dispatchTools,executeAgentInference] and callees [params(.framework/src/components/runtime/DataDetailPage.tsx),adminDb(.framework/functions/_shared/db.ts),handler(.framework/functions/tests.ts),failed(custom/apps/cortex/functions/custom_kb-chunker-test.ts),context(.framework/functions/auth.ts)] shape downstream behavior." status="ready"]
596
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
597
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
1074
598
  async function executeCreateRecord(
1075
599
  params: any,
1076
600
  ctx: CoreContext
1077
601
  ): Promise<any> {
1078
602
  const { entity, data } = params
1079
-
1080
603
  const { data: result, error } = await adminDb
1081
604
  .from(entity)
1082
605
  .insert({
@@ -1088,27 +611,29 @@ async function executeCreateRecord(
1088
611
  })
1089
612
  .select()
1090
613
  .single()
1091
-
1092
614
  if (error) throw new Error(`Create failed: ${error.message}`)
1093
615
  return { record: result }
1094
616
  }
1095
-
1096
- /**
1097
- * Tool: update_item — updates a record by ID in any table.
1098
- *
1099
- * @param params.entity: string — table name
1100
- * @param params.record_id: string — UUID of the record
1101
- * @param params.data: Record<string, any> — fields to update
1102
- * @returns { record } — the updated row
1103
- * @throws Error on DB failure
1104
- * @sideEffects DB write: params.entity table
1105
- */
617
+ // CIL[END chunk_id="agent-runner_executecreaterecord_1784488696"]
618
+ // CIL[BEGIN chunk_id="agent-runner_executeupdateitem_1784488696" type="function" identity="executeUpdateItem" parent="none"]
619
+ // CIL[DOC-MACRO purpose="Tool handler: update_item by ID"]
620
+ // CIL[DOC-RESPONSIBILITY responsibility="Update a record by ID, stamping updated_at and updated_by"]
621
+ // CIL[DOC-CONTEXT module="_shared/agent-runner" layer="infrastructure" related_files=".framework/functions/timers.ts,.framework/functions/_shared/db.ts,.framework/functions/tests.ts,custom/apps/cortex/functions/custom_kb-chunker-test.ts,.framework/src/components/runtime/DataDetailPage.tsx,.framework/functions/auth.ts"]
622
+ // CIL[DOC-CONTRACT inputs="params: any\nctx: CoreContext" outputs="{ record }" errors="throws on DB failure" side_effects="DB write to params.entity table"]
623
+ // CIL[DOC-MICRO logic_summary="Updates by id with merged data, updated_at, updated_by, and returns single row" notable_decisions="[]"]
624
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
625
+ // CIL[DOC-STABILITY stability="stable"]
626
+ // CIL[DOC-VECTOR type="context" content="Layer: infrastructure. Direct callers: executeToolAction,dispatchTools,executeAgentInference. Direct callees: update(.framework/functions/timers.ts),adminDb(.framework/functions/_shared/db.ts),handler(.framework/functions/tests.ts),failed(custom/apps/cortex/functions/custom_kb-chunker-test.ts),params(.framework/src/components/runtime/DataDetailPage.tsx),context(.framework/functions/auth.ts). Related files: .framework/functions/timers.ts,.framework/functions/_shared/db.ts,.framework/functions/tests.ts,custom/apps/cortex/functions/custom_kb-chunker-test.ts,.framework/src/components/runtime/DataDetailPage.tsx,.framework/functions/auth.ts." status="ready"]
627
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: executeToolAction,dispatchTools,executeAgentInference. Total affected: 3." status="ready"]
628
+ // CIL[DOC-VECTOR type="configuration" content="Config dependencies: none. Test paths: none." status="ready"]
629
+ // CIL[DOC-VECTOR type="constraint" content="Driven by chunk inputs; callers [executeToolAction,dispatchTools,executeAgentInference] and callees [update(.framework/functions/timers.ts),adminDb(.framework/functions/_shared/db.ts),handler(.framework/functions/tests.ts),failed(custom/apps/cortex/functions/custom_kb-chunker-test.ts),params(.framework/src/components/runtime/DataDetailPage.tsx),context(.framework/functions/auth.ts)] shape downstream behavior." status="ready"]
630
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
631
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
1106
632
  async function executeUpdateItem(
1107
633
  params: any,
1108
634
  ctx: CoreContext
1109
635
  ): Promise<any> {
1110
636
  const { entity, record_id, data } = params
1111
-
1112
637
  const { data: result, error } = await adminDb
1113
638
  .from(entity)
1114
639
  .update({
@@ -1119,28 +644,29 @@ async function executeUpdateItem(
1119
644
  .eq('id', record_id)
1120
645
  .select()
1121
646
  .single()
1122
-
1123
647
  if (error) throw new Error(`Update failed: ${error.message}`)
1124
648
  return { record: result }
1125
649
  }
1126
-
1127
- /**
1128
- * Tool: send_notification — inserts rows into the `watchers` table.
1129
- *
1130
- * @param params.message: string — notification text
1131
- * @param params.recipients: string[] — person UUIDs to notify
1132
- * @param params.entity_type: string | undefined
1133
- * @param params.entity_id: string | undefined
1134
- * @returns { notified_count }
1135
- * @throws Error on DB failure
1136
- * @sideEffects DB write: watchers table
1137
- */
650
+ // CIL[END chunk_id="agent-runner_executeupdateitem_1784488696"]
651
+ // CIL[BEGIN chunk_id="agent-runner_executesendnotification_1784488696" type="function" identity="executeSendNotification" parent="none"]
652
+ // CIL[DOC-MACRO purpose="Tool handler: send_notification inserts watchers"]
653
+ // CIL[DOC-RESPONSIBILITY responsibility="Create watcher rows for each recipient"]
654
+ // CIL[DOC-CONTEXT module="_shared/agent-runner" layer="infrastructure" related_files=".framework/migrations/001_schema.sql,.framework/functions/tests.ts,custom/apps/cortex/functions/custom_kb-chunker-test.ts,.framework/functions/_shared/db.ts,.framework/src/components/runtime/DataDetailPage.tsx,.framework/functions/auth.ts"]
655
+ // CIL[DOC-CONTRACT inputs="params: any\nctx: CoreContext" outputs="{ notified_count }" errors="throws on DB failure" side_effects="DB write watchers table"]
656
+ // CIL[DOC-MICRO logic_summary="Maps recipients to watcher rows and inserts batch, returning count" notable_decisions="[]"]
657
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
658
+ // CIL[DOC-STABILITY stability="stable"]
659
+ // CIL[DOC-VECTOR type="context" content="Layer: infrastructure. Direct callers: executeToolAction,dispatchTools,executeAgentInference. Direct callees: watchers(.framework/migrations/001_schema.sql),handler(.framework/functions/tests.ts),failed(custom/apps/cortex/functions/custom_kb-chunker-test.ts),adminDb(.framework/functions/_shared/db.ts),params(.framework/src/components/runtime/DataDetailPage.tsx),context(.framework/functions/auth.ts). Related files: .framework/migrations/001_schema.sql,.framework/functions/tests.ts,custom/apps/cortex/functions/custom_kb-chunker-test.ts,.framework/functions/_shared/db.ts,.framework/src/components/runtime/DataDetailPage.tsx,.framework/functions/auth.ts." status="ready"]
660
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: executeToolAction,dispatchTools,executeAgentInference. Total affected: 3." status="ready"]
661
+ // CIL[DOC-VECTOR type="configuration" content="Config dependencies: none. Test paths: none." status="ready"]
662
+ // CIL[DOC-VECTOR type="constraint" content="Driven by chunk inputs; callers [executeToolAction,dispatchTools,executeAgentInference] and callees [watchers(.framework/migrations/001_schema.sql),handler(.framework/functions/tests.ts),failed(custom/apps/cortex/functions/custom_kb-chunker-test.ts),adminDb(.framework/functions/_shared/db.ts),params(.framework/src/components/runtime/DataDetailPage.tsx),context(.framework/functions/auth.ts)] shape downstream behavior." status="ready"]
663
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
664
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
1138
665
  async function executeSendNotification(
1139
666
  params: any,
1140
667
  ctx: CoreContext
1141
668
  ): Promise<any> {
1142
669
  const { message, recipients = [], entity_type, entity_id } = params
1143
-
1144
670
  const notifications = recipients.map((recipientId: string) => ({
1145
671
  account_id: ctx.accountId,
1146
672
  person_id: recipientId,
@@ -1150,59 +676,48 @@ async function executeSendNotification(
1150
676
  is_read: false,
1151
677
  created_at: new Date().toISOString()
1152
678
  }))
1153
-
1154
679
  if (notifications.length > 0) {
1155
680
  const { error } = await adminDb.from('watchers').insert(notifications)
1156
681
  if (error) throw new Error(`Notification failed: ${error.message}`)
1157
682
  }
1158
-
1159
683
  return { notified_count: notifications.length }
1160
684
  }
1161
-
1162
- // ─── ESCALATION ────────────────────────────────────────────────────────────
1163
-
1164
- /**
1165
- * Handles confidence-based escalation when inference confidence falls below
1166
- * `promptConfig.confidence_threshold`.
1167
- *
1168
- * Actions taken:
1169
- * 1. Attempt to set `threads.data.escalation_status = 'pending'` (via jsonb_set RPC)
1170
- * 2. Emit `agent.inference.low_confidence` audit log
1171
- * 3. If `promptConfig.escalation_action === 'pipeline'` and
1172
- * `promptConfig.escalation_target` is set, run the escalation pipeline
1173
- * via dynamic import of `runPipeline`
1174
- *
1175
- * @param threadId - UUID of the thread being escalated
1176
- * @param confidence - The confidence score that triggered escalation
1177
- * @param promptConfig - Prompt config with escalation settings
1178
- * @param ctx - CoreContext
1179
- * @returns Promise<void> — always resolves
1180
- * @throws never — DB errors are silently dropped (best-effort)
1181
- * @sideEffects DB write: threads.data (jsonb_set for escalation_status)
1182
- * @sideEffects DB write: emitAudit (agent.inference.low_confidence)
1183
- * @sideEffects Calls runPipeline (if escalation_action === 'pipeline')
1184
- * @calledBy runAgent (when agentResponse.confidence < threshold)
1185
- */
685
+ // CIL[END chunk_id="agent-runner_executesendnotification_1784488696"]
686
+ // CIL[BEGIN chunk_id="agent-runner_handleescalation_1784488696" type="function" identity="handleEscalation" parent="none"]
687
+ // CIL[DOC-MACRO purpose="Handle low-confidence escalation for a thread"]
688
+ // CIL[DOC-RESPONSIBILITY responsibility="Mark thread as pending escalation, set target item to human_assigned, emit audit, and run escalation pipeline if configured"]
689
+ // CIL[DOC-CONTEXT module="_shared/agent-runner" layer="infrastructure" related_files=".framework/functions/_shared/index.ts,.framework/functions/_shared/db.ts,.framework/functions/timers.ts,.framework/functions/auth.ts,.framework/docs/runtime-types.md,.framework/migrations/001_schema.sql"]
690
+ // CIL[DOC-CONTRACT inputs="threadId: string\nconfidence: number\npromptConfig: any\nctx: CoreContext" outputs="void" errors="none; DB and pipeline failures are logged, not thrown" side_effects="DB writes threads/items/audit; may call runPipeline"]
691
+ // CIL[DOC-MICRO logic_summary="Updates thread.data.escalation_status, sets item status to human_assigned, emits low_confidence audit, and conditionally runs escalation_target pipeline (resolving slug to UUID and swallowing pipeline errors)" notable_decisions="[]"]
692
+ // CIL[DOC-LEARNING topic="agent-runner:escalation_pipeline_lookup" content="The pipelines table has a 'slug' column used as the human-readable pipeline identifier. handleEscalation resolves escalation_target (which may be a slug or name) by querying on slug or name and then merges the resolved pipeline_id into thread.data for subsequent runs."]
693
+ // CIL[DOC-STABILITY stability="stable"]
694
+ // CIL[DOC-VECTOR type="context" content="Layer: infrastructure. Direct callers: runAgent. Direct callees: pipeline(.framework/functions/_shared/index.ts),adminDb(.framework/functions/_shared/db.ts),update(.framework/functions/timers.ts),context(.framework/functions/auth.ts). Related files: .framework/functions/_shared/index.ts,.framework/functions/_shared/db.ts,.framework/functions/timers.ts,.framework/functions/auth.ts,.framework/docs/runtime-types.md,.framework/migrations/001_schema.sql." status="ready"]
695
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: runAgent. Total affected: 1." status="ready"]
696
+ // CIL[DOC-VECTOR type="configuration" content="Config dependencies: none. Test paths: none." status="ready"]
697
+ // CIL[DOC-VECTOR type="constraint" content="Driven by chunk inputs; callers [runAgent] and callees [pipeline(.framework/functions/_shared/index.ts),adminDb(.framework/functions/_shared/db.ts),update(.framework/functions/timers.ts),context(.framework/functions/auth.ts)] shape downstream behavior." status="ready"]
698
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
699
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
1186
700
  async function handleEscalation(
1187
701
  threadId: string,
1188
702
  confidence: number,
1189
703
  promptConfig: any,
1190
704
  ctx: CoreContext
1191
705
  ): Promise<void> {
1192
- // Update thread escalation status
706
+ return withSpan(ctx, 'agent.escalation', async (ctx) => {
707
+ const { data: currentThread } = await adminDb
708
+ .from('threads')
709
+ .select('data')
710
+ .eq('id', threadId)
711
+ .single()
1193
712
  await adminDb
1194
713
  .from('threads')
1195
714
  .update({
1196
- data: adminDb.rpc('jsonb_set', {
1197
- target: adminDb.from('threads').select('data').eq('id', threadId).single(),
1198
- path: '{escalation_status}',
1199
- new_value: '"pending"'
1200
- })
715
+ data: {
716
+ ...(currentThread?.data || {}),
717
+ escalation_status: 'pending'
718
+ }
1201
719
  })
1202
720
  .eq('id', threadId)
1203
-
1204
- // If the thread targets an item (e.g., a support ticket), mark that item as
1205
- // human-assigned so it shows up in the human queue and stops AI responses.
1206
721
  const { data: thread } = await adminDb
1207
722
  .from('threads')
1208
723
  .select('target_type, target_id')
@@ -1228,8 +743,6 @@ async function handleEscalation(
1228
743
  .eq('id', thread.target_id)
1229
744
  }
1230
745
  }
1231
-
1232
- // Fire trigger event
1233
746
  await emitAudit(ctx, 'agent.inference.low_confidence', {
1234
747
  type: 'thread',
1235
748
  id: threadId,
@@ -1240,84 +753,906 @@ async function handleEscalation(
1240
753
  escalation_action: promptConfig.escalation_action,
1241
754
  escalation_target: promptConfig.escalation_target
1242
755
  })
1243
-
1244
- // If escalation pipeline configured, run it
1245
756
  if (promptConfig.escalation_action === 'pipeline' && promptConfig.escalation_target) {
1246
757
  const { runPipeline } = await import('./pipeline-runner')
1247
- await runPipeline(
1248
- promptConfig.escalation_target,
1249
- {
1250
- thread_id: threadId,
1251
- confidence,
1252
- threshold: promptConfig.confidence_threshold,
1253
- reason: 'low_confidence'
1254
- },
1255
- ctx
1256
- )
1257
- }
1258
- }
758
+ let pipelineId = promptConfig.escalation_target
759
+
760
+ // escalation_target is usually stored as a slug/name (e.g. "support_escalation"),
761
+ // but runPipeline expects a UUID. Resolve the slug if needed.
762
+ const isUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
763
+ if (!isUuid.test(pipelineId)) {
764
+ const { data: pipeline, error: pipelineLookupError } = await adminDb
765
+ .from('pipelines')
766
+ .select('id')
767
+ .or(`slug.eq.${pipelineId},name.eq.${pipelineId}`)
768
+ .eq('is_active', true)
769
+ .maybeSingle()
1259
770
 
1260
- // ─── MESSAGE PERSISTENCE ───────────────────────────────────────────────────────────
771
+ if (pipelineLookupError) {
772
+ console.warn(`Escalation pipeline lookup failed for '${pipelineId}': ${pipelineLookupError.message}`)
773
+ return
774
+ }
775
+ if (!pipeline) {
776
+ console.warn(`Escalation pipeline not found: ${pipelineId}`)
777
+ return
778
+ }
779
+ pipelineId = pipeline.id
780
+ }
1261
781
 
1262
- /**
1263
- * Inserts a message row into the `messages` table for a thread.
1264
- *
1265
- * For human messages: `person_id` is set to `ctx.principal.id`.
1266
- * For agent/system/tool messages: `person_id` is null.
1267
- * The `data` JSONB field carries `message_type` and any additional metadata.
1268
- *
1269
- * @param threadId - UUID of the parent thread
1270
- * @param content - Message text content
1271
- * @param messageType - Role classifier for the message
1272
- * @param data - Additional JSONB metadata (e.g. confidence, tool_calls, agent_id)
1273
- * @param ctx - CoreContext with accountId and principal
1274
- * @returns Promise<any> — the inserted messages row
1275
- * @throws Error('Failed to save message') on DB insert failure
1276
- * @inputSpec messageType: 'human'|'agent'|'system'|'tool_call'|'tool_result'
1277
- * @sideEffects DB write: messages table
1278
- * @calledBy runAgent (user message + agent response)
1279
- */
1280
- // Map a thread type slug to the corresponding message/reply type slug.
782
+ try {
783
+ await runPipeline(
784
+ pipelineId,
785
+ {
786
+ thread_id: threadId,
787
+ confidence,
788
+ threshold: promptConfig.confidence_threshold,
789
+ reason: 'low_confidence'
790
+ },
791
+ ctx
792
+ )
793
+ } catch (err: any) {
794
+ console.warn(`Escalation pipeline '${promptConfig.escalation_target}' failed: ${err.message}`)
795
+ }
796
+ }
797
+ }, { onEnd: emitSpan })
798
+ }
799
+ // CIL[END chunk_id="agent-runner_handleescalation_1784488696"]
800
+ // CIL[BEGIN chunk_id="agent-runner_getreplytypeslug_1784488696" type="function" identity="getReplyTypeSlug" parent="none"]
801
+ // CIL[DOC-MACRO purpose="Map a thread type slug to its reply message type slug"]
802
+ // CIL[DOC-RESPONSIBILITY responsibility="Resolve support/community reply type slugs for message persistence"]
803
+ // CIL[DOC-CONTEXT module="_shared/agent-runner" layer="infrastructure" related_files=".framework/functions/system.ts,.framework/functions/auth.ts,.framework/functions/types.ts,.framework/functions/_shared/resolve-ids.ts"]
804
+ // CIL[DOC-CONTRACT inputs="threadTypeSlug: string" outputs="string" errors="none" side_effects="none"]
805
+ // CIL[DOC-MICRO logic_summary="Returns support_reply or community_reply for matching thread types, otherwise message" notable_decisions="[]"]
806
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
807
+ // CIL[DOC-STABILITY stability="stable"]
808
+ // CIL[DOC-VECTOR type="context" content="Layer: infrastructure. Direct callers: runAgent. Direct callees: types(.framework/functions/system.ts),context(.framework/functions/auth.ts). Related files: .framework/functions/system.ts,.framework/functions/auth.ts,.framework/functions/types.ts,.framework/functions/_shared/resolve-ids.ts." status="ready"]
809
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: runAgent. Total affected: 1." status="ready"]
810
+ // CIL[DOC-VECTOR type="configuration" content="Config dependencies: none. Test paths: none." status="ready"]
811
+ // CIL[DOC-VECTOR type="constraint" content="Driven by chunk inputs; callers [runAgent] and callees [types(.framework/functions/system.ts),context(.framework/functions/auth.ts)] shape downstream behavior." status="ready"]
812
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
813
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
814
+ // CIL[DOC-VECTOR type="semantic" content="Maps thread type slugs to reply message type slugs. engineering ticket threads (eng_ticket_thread) produce eng_ticket_comment replies, support threads produce support_reply, community threads produce community_reply, and all other thread types fall back to message." status="ready"]
1281
815
  function getReplyTypeSlug(threadTypeSlug: string): string {
1282
816
  if (threadTypeSlug === 'support_thread') return 'support_reply'
1283
817
  if (threadTypeSlug === 'community_thread') return 'community_reply'
818
+ if (threadTypeSlug === 'eng_ticket_thread') return 'eng_ticket_comment'
1284
819
  return 'message'
1285
820
  }
1286
-
821
+ // CIL[END chunk_id="agent-runner_getreplytypeslug_1784488696"]
822
+ // CIL[BEGIN chunk_id="agent-runner_savemessage_1784488696" type="function" identity="saveMessage" parent="none"]
823
+ // CIL[DOC-MACRO purpose="Persist a message row in the messages table"]
824
+ // CIL[DOC-RESPONSIBILITY responsibility="Insert a human, agent, system, or tool message with sequence, direction, visibility, and metadata"]
825
+ // CIL[DOC-CONTEXT module="_shared/agent-runner" layer="infrastructure" related_files=".framework/functions/_shared/db.ts,.framework/functions/auth.ts,.framework/migrations/001_schema.sql,.framework/functions/ai-interactions.ts,.framework/docs/runtime-types.md"]
826
+ // CIL[DOC-CONTRACT inputs="threadId: string\ncontent: string\nmessageType: string\ndata: any\nctx: CoreContext\nmessageTypeSlug: string\nvisibility?: string" outputs="message row" errors="throws if insert fails" side_effects="DB write messages table via createRecord"]
827
+ // CIL[DOC-MICRO logic_summary="Computes next sequence, direction, visibility, and calls createRecord with type_slug and data payload" notable_decisions=["Message visibility is inherited from the parent thread so an internal Solution AI workbench never accidentally produces public customer messages."]
828
+ // CIL[DOC-LEARNING topic="agent-runner:messages_schema" content="The messages table has no 'sender' column. Message provenance is captured via person_id for human messages and encoded in messageType/data for agent/system/tool messages. Inserting a 'sender' column fails with a 42703 error."]
829
+ // CIL[DOC-LEARNING topic="agent-runner:message_visibility" content="message visibility must be derived from thread.visibility, not from messageType. Agent messages in a private support AI thread should be internal; the same code path running on the public customer thread must remain external. Pass thread.visibility into saveMessage explicitly."]
830
+ // CIL[DOC-STABILITY stability="stable"]
831
+ // CIL[DOC-VECTOR type="context" content="Layer: infrastructure. Direct callers: runAgent. Direct callees: adminDb(.framework/functions/_shared/db.ts),context(.framework/functions/auth.ts). Related files: .framework/functions/_shared/db.ts,.framework/functions/auth.ts,.framework/migrations/001_schema.sql,.framework/functions/ai-interactions.ts,.framework/docs/runtime-types.md." status="ready"]
832
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: runAgent. Total affected: 1." status="ready"]
833
+ // CIL[DOC-VECTOR type="configuration" content="Config dependencies: none. Test paths: none." status="ready"]
834
+ // CIL[DOC-VECTOR type="constraint" content="Driven by chunk inputs; callers [runAgent] and callees [adminDb(.framework/functions/_shared/db.ts),context(.framework/functions/auth.ts)] shape downstream behavior." status="ready"]
835
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
836
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
1287
837
  async function saveMessage(
1288
838
  threadId: string,
1289
839
  content: string,
1290
840
  messageType: 'human' | 'agent' | 'system' | 'tool_call' | 'tool_result',
1291
841
  data: any,
1292
842
  ctx: CoreContext,
1293
- messageTypeSlug: string = 'message'
843
+ messageTypeSlug: string = 'message',
844
+ visibility?: string
1294
845
  ): Promise<any> {
1295
- const { count } = await adminDb
1296
- .from('messages')
1297
- .select('id', { count: 'exact', head: true })
1298
- .eq('thread_id', threadId)
1299
- const sequence = (count ?? 0) + 1
1300
- const direction = messageType === 'human' ? 'inbound' : 'outbound'
1301
- // human + agent messages are customer-visible; tool/system messages are internal
1302
- const visibility = (messageType === 'human' || messageType === 'agent') ? 'external' : 'internal'
846
+ return withSpan(ctx, 'agent.save_message', async (ctx) => {
847
+ const { count } = await adminDb
848
+ .from('messages')
849
+ .select('id', { count: 'exact', head: true })
850
+ .eq('thread_id', threadId)
851
+ const sequence = (count ?? 0) + 1
852
+ const direction = messageType === 'human' ? 'inbound' : 'outbound'
853
+ const resolvedVisibility = visibility || ((messageType === 'human' || messageType === 'agent') ? 'external' : 'internal')
854
+ const message = await createRecord(ctx, {
855
+ entity: 'messages',
856
+ type_slug: messageTypeSlug,
857
+ thread_id: threadId,
858
+ person_id: messageType === 'human' ? ctx.principal?.id : null,
859
+ content,
860
+ direction,
861
+ sequence,
862
+ visibility: resolvedVisibility,
863
+ data: {
864
+ message_type: messageType,
865
+ ...data
866
+ },
867
+ })
868
+ if (!message?.id) throw new Error('Failed to save message')
869
+ return message
870
+ }, { onEnd: emitSpan })
871
+ }
872
+ // CIL[END chunk_id="agent-runner_savemessage_1784488696"]
873
+ // CIL[BEGIN chunk_id="agent-runner_exports_1784488696" type="interface" identity="exports" parent="none"]
874
+ // CIL[DOC-MACRO purpose="Exported type contracts for agent runner"]
875
+ // CIL[DOC-RESPONSIBILITY responsibility="Define AgentConfig, InferenceResult, ToolCall, and ToolResult shapes"]
876
+ // CIL[DOC-CONTEXT module="_shared/agent-runner" layer="infrastructure" related_files=".framework/functions/integration-routes.ts,.framework/src/components/runtime/DataDetailPage.tsx,.framework/functions/auth.ts,.framework/docs/runtime-types.md"]
877
+ // CIL[DOC-CONTRACT inputs="none" outputs="TypeScript interfaces" errors="none" side_effects="none"]
878
+ // CIL[DOC-MICRO logic_summary="Declares configuration, result, tool call, and tool result structures used across the module" notable_decisions="[]"]
879
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
880
+ // CIL[DOC-STABILITY stability="stable"]
881
+ // CIL[DOC-VECTOR type="context" content="Layer: infrastructure. Direct callers: none. Direct callees: interfaces(.framework/functions/integration-routes.ts),params(.framework/src/components/runtime/DataDetailPage.tsx),context(.framework/functions/auth.ts). Related files: .framework/functions/integration-routes.ts,.framework/src/components/runtime/DataDetailPage.tsx,.framework/functions/auth.ts,.framework/docs/runtime-types.md." status="ready"]
882
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: none. Total affected: 0." status="ready"]
883
+ // CIL[DOC-VECTOR type="configuration" content="Config dependencies: none. Test paths: none." status="ready"]
884
+ // CIL[DOC-VECTOR type="constraint" content="Driven by chunk inputs; callers [none] and callees [interfaces(.framework/functions/integration-routes.ts),params(.framework/src/components/runtime/DataDetailPage.tsx),context(.framework/functions/auth.ts)] shape downstream behavior." status="ready"]
885
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
886
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
887
+ export interface AgentConfig {
888
+ agent: any
889
+ promptConfig: any
890
+ thread: any
891
+ threadType: any
892
+ }
893
+
894
+ export interface InferenceResult {
895
+ content: string
896
+ confidence: number
897
+ tool_calls?: ToolCall[]
898
+ metadata?: Record<string, any>
899
+ }
1303
900
 
1304
- // Use admin-data's create() so type_slug is resolved server-side and
1305
- // design_schema + validation_schema are stamped from the type record.
1306
- const message = await createRecord(ctx, {
1307
- entity: 'messages',
1308
- type_slug: messageTypeSlug,
1309
- thread_id: threadId,
1310
- person_id: messageType === 'human' ? ctx.principal?.id : null,
1311
- content,
1312
- direction,
1313
- sequence,
1314
- visibility,
1315
- data: {
1316
- message_type: messageType,
1317
- ...data
901
+ export interface ToolCall {
902
+ tool: string
903
+ params: Record<string, any>
904
+ id: string
905
+ }
906
+
907
+ export interface ToolResult {
908
+ tool: string
909
+ result: any
910
+ error?: string
911
+ id: string
912
+ }
913
+ // CIL[END chunk_id="agent-runner_exports_1784488696"]
914
+ // CIL[BEGIN chunk_id="agent-runner_ciltools_1784488696" type="constant" identity="ciltools" parent="none"]
915
+ // CIL[DOC-MACRO purpose="CIL tool definitions and helper utilities for the agent runner"]
916
+ // CIL[DOC-RESPONSIBILITY responsibility="Define OpenAI-style function tool schemas for CIL retrieval and provide normalise/compute helpers"]
917
+ // CIL[DOC-CONTEXT module="_shared/agent-runner" layer="infrastructure" related_files="custom/apps/cortex/functions/custom_cil-agent.ts,custom/apps/cortex/functions/custom_cil-callmap.ts"]
918
+ // CIL[DOC-CONTRACT inputs="none" outputs="CIL_TOOLS constant and helper functions" errors="none" side_effects="none"]
919
+ // CIL[DOC-MICRO logic_summary="Defines CIL tool schemas; exposes normaliseCilChunkContent and computeCilChunkHash to validate stored chunk hashes" notable_decisions="[]"]
920
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
921
+ // CIL[DOC-STABILITY stability="stable"]
922
+ // CIL[DOC-AUDIENCE audience="developer"]
923
+ // CIL[DOC-VECTOR type="context" content="Layer: infrastructure. Direct callers: resolveAgentConfig,dispatchTools,executeToolAction. Direct callees: adminDb(.framework/functions/_shared/db.ts). Related files: custom/apps/cortex/functions/custom_cil-agent.ts,custom/apps/cortex/functions/custom_cil-callmap.ts." status="ready"]
924
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: resolveAgentConfig,dispatchTools,executeToolAction. Total affected: 3." status="ready"]
925
+ // CIL[DOC-VECTOR type="configuration" content="Config dependencies: thread.data.cil_config.use_cil_tools. Test paths: none." status="ready"]
926
+ // CIL[DOC-VECTOR type="constraint" content="Driven by chunk inputs; callers [resolveAgentConfig,dispatchTools,executeToolAction] and callees [adminDb(.framework/functions/_shared/db.ts)] shape downstream behavior." status="ready"]
927
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
928
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
929
+
930
+ const CIL_TOOLS: any[] = [
931
+ {
932
+ name: 'cil_chunk_lookup',
933
+ description: 'Search for a CIL chunk by name, file path, or purpose. Returns chunk_id, chunk_name, file_path, and macro.',
934
+ parameters: {
935
+ type: 'object',
936
+ properties: {
937
+ q: { type: 'string', description: 'Search term' },
938
+ project_id: { type: 'string', description: 'Optional project UUID to scope the search' },
939
+ limit: { type: 'number', description: 'Max results to return (default 5)' },
940
+ },
941
+ required: ['q'],
1318
942
  },
943
+ },
944
+ {
945
+ name: 'cil_get_contract',
946
+ description: 'Get the full contract and metadata for a specific CIL chunk by its UUID.',
947
+ parameters: {
948
+ type: 'object',
949
+ properties: {
950
+ chunk_id: { type: 'string', description: 'UUID of the cil_chunk item' },
951
+ },
952
+ required: ['chunk_id'],
953
+ },
954
+ },
955
+ {
956
+ name: 'cil_blast_radius',
957
+ description: 'Find all chunks that transitively call or are called by a given chunk (imports, calls, exports, tested_by). Use before refactoring.',
958
+ parameters: {
959
+ type: 'object',
960
+ properties: {
961
+ chunk_id: { type: 'string', description: 'UUID of the chunk to check callers/callees for' },
962
+ max_depth: { type: 'number', description: 'Maximum BFS hop depth (default 3, max 6)' },
963
+ },
964
+ required: ['chunk_id'],
965
+ },
966
+ },
967
+ {
968
+ name: 'cil_hash_validate',
969
+ description: 'Check whether a CIL chunk\'s stored hash is current or has drifted (code was changed without updating CIL).',
970
+ parameters: {
971
+ type: 'object',
972
+ properties: {
973
+ chunk_id: { type: 'string', description: 'UUID of the chunk to validate' },
974
+ },
975
+ required: ['chunk_id'],
976
+ },
977
+ },
978
+ {
979
+ name: 'cil_project_status',
980
+ description: 'Get the full CIL pipeline status for a project: file count, chunk count, embedding coverage, and doc coverage.',
981
+ parameters: {
982
+ type: 'object',
983
+ properties: {
984
+ project_id: { type: 'string', description: 'UUID of the cil_project' },
985
+ },
986
+ required: ['project_id'],
987
+ },
988
+ },
989
+ ]
990
+
991
+ const CIL_TOOL_SLUGS = new Set(CIL_TOOLS.map(t => t.name))
992
+
993
+ function normaliseCilChunkContent(raw: string): string {
994
+ const lines = raw.replace(/\r\n/g, '\n').split('\n')
995
+ const filtered = lines.filter(line => {
996
+ const trimmed = line.trim()
997
+ return !trimmed.match(/^(?:(?:\/\/|--|#)\s*|<!--\s*)CIL\[(BEGIN|END)/) &&
998
+ !trimmed.match(/^(?:(?:\/\/|--|#)\s*|<!--\s*)CIL\[DOC-/)
1319
999
  })
1000
+ return filtered.join('\n').trim()
1001
+ }
1002
+
1003
+ function computeCilChunkHash(content: string): string {
1004
+ return createHash('sha256').update(normaliseCilChunkContent(content), 'utf8').digest('hex').slice(0, 16)
1005
+ }
1006
+
1007
+ // CIL[END chunk_id="agent-runner_ciltools_1784488696"]
1008
+ // CIL[BEGIN chunk_id="agent-runner_runagent_1784488696" type="function" identity="runAgent" parent="none"]
1009
+ // CIL[DOC-MACRO purpose="Execute an AI agent conversation turn"]
1010
+ // CIL[DOC-RESPONSIBILITY responsibility="Orchestrate resolve, save user message, run inference, escalate if needed, save response, and audit"]
1011
+ // CIL[DOC-CONTEXT module="_shared/agent-runner" layer="infrastructure" related_files=".framework/migrations/001_schema.sql,custom/apps/cortex/functions/custom_kb-chunker-test.ts,.framework/functions/_shared/index.ts,.framework/src/components/runtime/DataDetailPage.tsx,.framework/functions/auth.ts,.framework/functions/ai-agents.ts,.framework/cli/commands/agents.ts"]
1012
+ // CIL[DOC-CONTRACT inputs="threadId: string\nuserMessage: string\nctx: CoreContext" outputs="agent message row" errors="throws on resolve or inference failure" side_effects="DB reads/writes, audit logs, possible escalation pipeline"]
1013
+ // CIL[DOC-MICRO logic_summary="Resolves config, saves user message, executes agent inference, escalates low confidence, saves response with sources, and emits audit" notable_decisions="[]"]
1014
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
1015
+ // CIL[DOC-STABILITY stability="stable"]
1016
+ // CIL[DOC-VECTOR type="context" content="Layer: infrastructure. Direct callers: none. Direct callees: executeAgentInference,getReplyTypeSlug,logs(.framework/migrations/001_schema.sql),failed(custom/apps/cortex/functions/custom_kb-chunker-test.ts),saveMessage,pipeline(.framework/functions/_shared/index.ts),resolveAgentConfig,handleEscalation,title(.framework/src/components/runtime/DataDetailPage.tsx),context(.framework/functions/auth.ts). Related files: .framework/migrations/001_schema.sql,custom/apps/cortex/functions/custom_kb-chunker-test.ts,.framework/functions/_shared/index.ts,.framework/src/components/runtime/DataDetailPage.tsx,.framework/functions/auth.ts,.framework/functions/ai-agents.ts,.framework/cli/commands/agents.ts." status="ready"]
1017
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: none. Total affected: 0." status="ready"]
1018
+ // CIL[DOC-VECTOR type="configuration" content="Config dependencies: none. Test paths: none." status="ready"]
1019
+ // CIL[DOC-VECTOR type="constraint" content="Driven by chunk inputs; callers [none] and callees [executeAgentInference,getReplyTypeSlug,logs(.framework/migrations/001_schema.sql),failed(custom/apps/cortex/functions/custom_kb-chunker-test.ts),saveMessage,pipeline(.framework/functions/_shared/index.ts),resolveAgentConfig,handleEscalation,title(.framework/src/components/runtime/DataDetailPage.tsx),context(.framework/functions/auth.ts)] shape downstream behavior." status="ready"]
1020
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
1021
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
1022
+ export async function runAgent(
1023
+ threadId: string,
1024
+ userMessage: string,
1025
+ ctx: CoreContext
1026
+ ): Promise<any> {
1027
+ return withSpan(ctx, 'agent.runAgent', async (ctx) => {
1028
+ const startTime = Date.now()
1029
+ try {
1030
+ const config = await resolveAgentConfig(threadId, ctx)
1031
+ if (!config) {
1032
+ throw new Error(`Could not resolve agent configuration for thread ${threadId}`)
1033
+ }
1034
+ const { agent, promptConfig, thread, threadType } = config
1035
+ const replyTypeSlug = getReplyTypeSlug(threadType?.slug)
1036
+ const threadVisibility = thread?.visibility || 'external'
1037
+ const userMsg = await saveMessage(threadId, userMessage, 'human', null, ctx, replyTypeSlug, threadVisibility)
1038
+ const maxToolIterations = promptConfig.metadata?.max_tool_iterations ?? 5
1039
+ const agentResponse = await executeAgentInference(
1040
+ config,
1041
+ userMessage,
1042
+ ctx,
1043
+ maxToolIterations
1044
+ )
1045
+ const isEscalated = agentResponse.confidence < (promptConfig.confidence_threshold || 0)
1046
+ if (isEscalated) {
1047
+ await handleEscalation(
1048
+ threadId,
1049
+ agentResponse.confidence,
1050
+ promptConfig,
1051
+ ctx
1052
+ )
1053
+ }
1054
+ const agentMsg = await saveMessage(
1055
+ threadId,
1056
+ agentResponse.content,
1057
+ 'agent',
1058
+ {
1059
+ confidence: agentResponse.confidence,
1060
+ retrieval_confidence: agentResponse.retrievalConfidence,
1061
+ retrieved_sources: (agentResponse.retrievedSources || []).map((s: any) => ({
1062
+ document_id: s.document_id,
1063
+ title: s.title,
1064
+ similarity: s.similarity,
1065
+ item_type: s.item_type,
1066
+ vector_type: s.vector_type,
1067
+ })),
1068
+ tool_calls: agentResponse.tool_calls,
1069
+ agent_id: agent.id,
1070
+ prompt_config_id: promptConfig.id,
1071
+ sender_type: 'agent',
1072
+ escalated: isEscalated,
1073
+ },
1074
+ ctx,
1075
+ replyTypeSlug,
1076
+ threadVisibility
1077
+ )
1078
+ await emitAudit(ctx, 'agent.inference.completed', {
1079
+ type: 'agent_message',
1080
+ id: agentMsg.id,
1081
+ account_id: ctx.accountId ?? undefined
1082
+ }, {
1083
+ thread_id: threadId,
1084
+ agent_id: agent.id,
1085
+ confidence: agentResponse.confidence,
1086
+ retrieval_confidence: agentResponse.retrievalConfidence,
1087
+ sources_count: agentResponse.retrievedSources?.length || 0,
1088
+ has_tool_calls: !!agentResponse.tool_calls?.length,
1089
+ duration_ms: Date.now() - startTime
1090
+ })
1091
+ await emitOutcome(ctx, 'agent.outcome', 'success', {
1092
+ thread_id: threadId,
1093
+ agent_id: agent.id,
1094
+ confidence: agentResponse.confidence,
1095
+ escalated: isEscalated
1096
+ })
1097
+ return agentMsg
1098
+ } catch (error: any) {
1099
+ await emitAudit(ctx, 'agent.inference.failed', {
1100
+ type: 'agent_message',
1101
+ id: 'failed',
1102
+ account_id: ctx.accountId ?? undefined
1103
+ }, {
1104
+ thread_id: threadId,
1105
+ error: error.message
1106
+ })
1107
+ await emitOutcome(ctx, 'agent.outcome', 'failure', {
1108
+ thread_id: threadId,
1109
+ error: error.message
1110
+ })
1111
+ throw error
1112
+ }
1113
+ }, { onEnd: emitSpan })
1114
+ }
1115
+ // CIL[END chunk_id="agent-runner_runagent_1784488696"]
1116
+ // CIL[BEGIN chunk_id="agent-runner_resolveagentconfig_1784488696" type="function" identity="resolveAgentConfig" parent="none"]
1117
+ // CIL[DOC-LEARNING topic="runtime-type:thread/default_agent" content="agent-runner resolves thread.data.agent_id first, then falls back to thread.type.design_schema.default_agent_id; this lets a thread type define its default AI agent without storing it on every thread."]
1118
+ // CIL[DOC-NOTE type="design_schema_caveat" content="If neither thread.data.agent_id nor the thread type's design_schema.default_agent_id is set, the runner logs an error and returns null."]
1119
+ // CIL[DOC-MACRO purpose="Resolve agent, prompt config, and thread type from a thread ID"]
1120
+ // CIL[DOC-RESPONSIBILITY responsibility="Load thread with type, resolve agent_id and prompt_config_id, then fetch agent and prompt config records"]
1121
+ // CIL[DOC-CONTEXT module="_shared/agent-runner" layer="infrastructure" related_files=".framework/migrations/001_schema.sql,.framework/functions/system.ts,.framework/functions/_shared/db.ts,custom/apps/cortex/pages/community/CommunityPage.tsx,.framework/functions/auth.ts,.framework/functions/_shared/resolve-ids.ts,.framework/docs/runtime-types.md"]
1122
+ // CIL[DOC-CONTRACT inputs="threadId: string\nctx: CoreContext" outputs="AgentConfig or null" errors="logs and returns null when thread, agent, or prompt config missing" side_effects="DB reads threads, ai_agents, prompt_configs, types; may mutate promptConfig.metadata and agent.model_config.tools with thread.data.cil_config"]
1123
+ // CIL[DOC-MICRO logic_summary="Fetches thread with type, chooses thread.data.agent_id or design_schema.default_agent_id, loads active agent, resolves prompt config, merges thread.data.cil_config into promptConfig.metadata.retrieval_policy when enabled, optionally injects CIL tool definitions into agent.model_config.tools and promptConfig.available_tools, and returns config object" notable_decisions="[]"]
1124
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
1125
+ // CIL[DOC-STABILITY stability="stable"]
1126
+ // CIL[DOC-VECTOR type="context" content="Layer: infrastructure. Direct callers: runAgent. Direct callees: ai_agents(.framework/migrations/001_schema.sql),types(.framework/functions/system.ts),adminDb(.framework/functions/_shared/db.ts),logs(.framework/migrations/001_schema.sql),Thread(custom/apps/cortex/pages/community/CommunityPage.tsx),context(.framework/functions/auth.ts). Related files: .framework/migrations/001_schema.sql,.framework/functions/system.ts,.framework/functions/_shared/db.ts,custom/apps/cortex/pages/community/CommunityPage.tsx,.framework/functions/auth.ts,.framework/functions/_shared/resolve-ids.ts,.framework/docs/runtime-types.md." status="ready"]
1127
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: runAgent. Total affected: 1." status="ready"]
1128
+ // CIL[DOC-VECTOR type="configuration" content="Config dependencies: none. Test paths: none." status="ready"]
1129
+ // CIL[DOC-VECTOR type="constraint" content="Driven by chunk inputs; callers [runAgent] and callees [ai_agents(.framework/migrations/001_schema.sql),types(.framework/functions/system.ts),adminDb(.framework/functions/_shared/db.ts),logs(.framework/migrations/001_schema.sql),Thread(custom/apps/cortex/pages/community/CommunityPage.tsx),context(.framework/functions/auth.ts)] shape downstream behavior." status="ready"]
1130
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
1131
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
1132
+ export async function resolveAgentConfig(
1133
+ threadId: string,
1134
+ ctx: CoreContext
1135
+ ): Promise<AgentConfig | null> {
1136
+ const { data: thread, error: threadError } = await adminDb
1137
+ .from('threads')
1138
+ .select('*, type:types(*)')
1139
+ .eq('id', threadId)
1140
+ .single()
1141
+ if (threadError || !thread) {
1142
+ console.error(`Thread not found: ${threadId}`, threadError)
1143
+ return null
1144
+ }
1145
+ const threadType = thread.type
1146
+ const threadData = thread.data || {}
1147
+ let agentId = threadData.agent_id
1148
+ if (!agentId && threadType?.design_schema?.default_agent_id) {
1149
+ agentId = threadType.design_schema.default_agent_id
1150
+ }
1151
+ if (!agentId) {
1152
+ console.error(`No agent assigned to thread ${threadId}`)
1153
+ return null
1154
+ }
1155
+ const { data: agent, error: agentError } = await adminDb
1156
+ .from('ai_agents')
1157
+ .select('*')
1158
+ .eq('id', agentId)
1159
+ .eq('is_active', true)
1160
+ .single()
1161
+ if (agentError || !agent) {
1162
+ console.error(`Agent not found: ${agentId}`, agentError)
1163
+ return null
1164
+ }
1165
+ const agentMetadata = agent.metadata || {}
1166
+ let promptConfigId = threadData.prompt_config_id
1167
+ if (!promptConfigId && agentMetadata.default_prompt_config_id) {
1168
+ promptConfigId = agentMetadata.default_prompt_config_id
1169
+ }
1170
+ if (!promptConfigId) {
1171
+ console.error(`No prompt config for thread ${threadId}`)
1172
+ return null
1173
+ }
1174
+ const { data: promptConfig, error: configError } = await adminDb
1175
+ .from('prompt_configs')
1176
+ .select('*')
1177
+ .eq('id', promptConfigId)
1178
+ .eq('is_active', true)
1179
+ .single()
1180
+ if (configError || !promptConfig) {
1181
+ console.error(`Prompt config not found: ${promptConfigId}`, configError)
1182
+ return null
1183
+ }
1184
+
1185
+ // Merge optional CIL augmentation config from the thread into the prompt config
1186
+ // retrieval policy. This lets app-level manifest config (e.g. portal) augment
1187
+ // support AI with CIL chunks without changing the ai_agents/prompt_configs rows.
1188
+ if (threadData.cil_config?.enabled) {
1189
+ const existingMetadata = promptConfig.metadata || {}
1190
+ const existingPolicy = existingMetadata.retrieval_policy || {}
1191
+ const existingItemTypes = existingPolicy.item_types
1192
+ // If the prompt config already constrains item_types, add cil_chunk to the
1193
+ // list. If it does not, leave item_types unset so match_embeddings searches
1194
+ // all item types; project_ids will still scope CIL chunks to the configured
1195
+ // projects. This preserves KB/article retrieval and augments with CIL.
1196
+ const newItemTypes = existingItemTypes?.length
1197
+ ? Array.from(new Set([...existingItemTypes, 'cil_chunk']))
1198
+ : undefined
1199
+ promptConfig.metadata = {
1200
+ ...existingMetadata,
1201
+ retrieval_policy: {
1202
+ ...existingPolicy,
1203
+ ...(newItemTypes ? { item_types: newItemTypes } : {}),
1204
+ project_ids: threadData.cil_config.project_ids || [],
1205
+ },
1206
+ cil_config: threadData.cil_config,
1207
+ }
1208
+
1209
+ // When the portal/app config asks for CIL tools, inject them into the agent
1210
+ // and prompt config so the LLM can dispatch CIL lookups during the run.
1211
+ if (threadData.cil_config.use_cil_tools) {
1212
+ agent.model_config = agent.model_config || {}
1213
+ const existingTools = agent.model_config.tools || []
1214
+ const cilToolDefs = CIL_TOOLS.map((t: any) => ({ type: 'function', function: t }))
1215
+ agent.model_config.tools = [...existingTools, ...cilToolDefs]
1216
+ promptConfig.available_tools = [...(promptConfig.available_tools || []), ...CIL_TOOLS.map((t: any) => t.name)]
1217
+ }
1218
+ }
1320
1219
 
1321
- if (!message?.id) throw new Error('Failed to save message')
1322
- return message
1220
+ return { agent, promptConfig, thread, threadType }
1221
+ }
1222
+ // CIL[END chunk_id="agent-runner_resolveagentconfig_1784488696"]
1223
+ // CIL[BEGIN chunk_id="agent-runner_executeagentinference_1784488696" type="function" identity="executeAgentInference" parent="none"]
1224
+ // CIL[DOC-MACRO purpose="Run agent inference with optional tool-calling loop"]
1225
+ // CIL[DOC-RESPONSIBILITY responsibility="Build context, call LLM, dispatch tool calls, and compute composite confidence from logprobs and retrieval similarity"]
1226
+ // CIL[DOC-CONTEXT module="_shared/agent-runner" layer="infrastructure" related_files=".framework/functions/auth.ts,custom/apps/cortex/functions/custom_kb-chunker-test.ts,custom/apps/portal/hooks/useTickets.ts"]
1227
+ // CIL[DOC-CONTRACT inputs="config: AgentConfig\nuserMessage: string\nctx: CoreContext\nmaxToolIterations?: number" outputs="InferenceResult with retrievedSources and retrievalConfidence" errors="throws on max iterations or sub-call failure" side_effects="Calls buildContext, callInference, dispatchTools"]
1228
+ // CIL[DOC-MICRO logic_summary="Builds context, loops up to max iterations calling inference and dispatching tools, blends logprob and retrieval similarity for final confidence" notable_decisions="[]"]
1229
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
1230
+ // CIL[DOC-STABILITY stability="stable"]
1231
+ // CIL[DOC-VECTOR type="context" content="Layer: infrastructure. Direct callers: runAgent. Direct callees: callInference,dispatchTools,buildContext,context(.framework/functions/auth.ts),failed(custom/apps/cortex/functions/custom_kb-chunker-test.ts). Related files: .framework/functions/auth.ts,custom/apps/cortex/functions/custom_kb-chunker-test.ts,custom/apps/portal/hooks/useTickets.ts." status="ready"]
1232
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: runAgent. Total affected: 1." status="ready"]
1233
+ // CIL[DOC-VECTOR type="configuration" content="Config dependencies: none. Test paths: none." status="ready"]
1234
+ // CIL[DOC-VECTOR type="constraint" content="Driven by chunk inputs; callers [runAgent] and callees [callInference,dispatchTools,buildContext,context(.framework/functions/auth.ts),failed(custom/apps/cortex/functions/custom_kb-chunker-test.ts)] shape downstream behavior." status="ready"]
1235
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
1236
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
1237
+ // CIL[DOC-NOTE type="tool_loop_limit" content="When the last configured tool iteration still produces tool calls, the runner injects a system note asking the LLM to synthesize a final answer instead of throwing an error. This prevents 'Max tool iterations reached' failures while still capping tool usage via max_tool_iterations."]
1238
+ async function executeAgentInference(
1239
+ config: AgentConfig,
1240
+ userMessage: string,
1241
+ ctx: CoreContext,
1242
+ maxToolIterations: number = 5
1243
+ ): Promise<InferenceResult & { retrievedSources: any[]; retrievalConfidence: number }> {
1244
+ return withSpan(ctx, 'agent.execute_inference', async (ctx) => {
1245
+ const { agent, promptConfig } = config
1246
+ let { context, retrievedSources, meanSimilarity } = await buildContext(config, userMessage, [], ctx)
1247
+ const computeComposite = (llmConf: number): number => {
1248
+ if (meanSimilarity < 0.25 && meanSimilarity > 0) {
1249
+ return Math.min(llmConf * 0.6 + meanSimilarity * 0.4, 0.45)
1250
+ } else if (meanSimilarity < 0.40 && meanSimilarity > 0) {
1251
+ return Math.min(llmConf * 0.6 + meanSimilarity * 0.4, 0.60)
1252
+ }
1253
+ return llmConf * 0.6 + Math.min(meanSimilarity, 1) * 0.4
1254
+ }
1255
+ for (let iteration = 0; iteration < maxToolIterations; iteration++) {
1256
+ const inferenceResult = await callInference(context, agent, promptConfig, ctx)
1257
+ if (!inferenceResult.tool_calls || inferenceResult.tool_calls.length === 0) {
1258
+ const compositeConf = computeComposite(inferenceResult.confidence ?? 0.70)
1259
+ return {
1260
+ ...inferenceResult,
1261
+ confidence: Math.min(Math.max(compositeConf, 0), 1),
1262
+ retrievedSources,
1263
+ retrievalConfidence: meanSimilarity,
1264
+ }
1265
+ }
1266
+ const toolResults = await dispatchTools(inferenceResult.tool_calls, ctx)
1267
+ const rebuilt = await buildContext(config, userMessage, toolResults, ctx)
1268
+ context = rebuilt.context
1269
+ if (rebuilt.retrievedSources.length) {
1270
+ retrievedSources = rebuilt.retrievedSources
1271
+ meanSimilarity = rebuilt.meanSimilarity
1272
+ }
1273
+ if (iteration === maxToolIterations - 1) {
1274
+ const hasErrors = toolResults.some(r => r.error)
1275
+ const note = `\n\n[System note: You have reached the tool limit. Do not call any more tools. Synthesize your final answer based on the information and tool results already provided.${hasErrors ? ' Some tools failed.' : ''}]`
1276
+ const finalResult = await callInference(context + note, agent, promptConfig, ctx)
1277
+ if (finalResult.tool_calls?.length) {
1278
+ finalResult.content = (finalResult.content || '') + '\n\n[Note: I reached the tool limit and still attempted to call tools. The response may be incomplete.]'
1279
+ }
1280
+ const compositeConf = computeComposite(finalResult.confidence ?? 0.70)
1281
+ return {
1282
+ ...finalResult,
1283
+ confidence: Math.min(Math.max(compositeConf, 0), 1),
1284
+ retrievedSources,
1285
+ retrievalConfidence: meanSimilarity,
1286
+ }
1287
+ }
1288
+ }
1289
+ throw new Error('Max tool iterations reached')
1290
+ }, { onEnd: emitSpan })
1291
+ }
1292
+ // CIL[END chunk_id="agent-runner_executeagentinference_1784488696"]
1293
+ // CIL[BEGIN chunk_id="agent-runner_executecilchunklookup_1784488696" type="function" identity="executeCilChunkLookup" parent="agent-runner_ciltools_1784488696"]
1294
+ // CIL[DOC-MACRO purpose="Tool handler: cil_chunk_lookup"]
1295
+ // CIL[DOC-RESPONSIBILITY responsibility="Search CIL chunks by name, file path, or purpose"]
1296
+ // CIL[DOC-CONTEXT module="_shared/agent-runner" layer="infrastructure" related_files="custom/apps/cortex/functions/custom_cil-agent.ts"]
1297
+ // CIL[DOC-CONTRACT inputs="params: { q, project_id?, limit? }\nctx: CoreContext" outputs="{ results, count }" errors="throws on DB failure" side_effects="DB read on items table"]
1298
+ // CIL[DOC-MICRO logic_summary="Queries active cil_chunk items for the account, filters by project_id and search term, and returns a trimmed result list" notable_decisions="[]"]
1299
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
1300
+ // CIL[DOC-STABILITY stability="stable"]
1301
+ // CIL[DOC-AUDIENCE audience="developer"]
1302
+ // CIL[DOC-VECTOR type="context" content="Layer: infrastructure. Direct callers: executeToolAction. Direct callees: adminDb(.framework/functions/_shared/db.ts). Related files: custom/apps/cortex/functions/custom_cil-agent.ts." status="ready"]
1303
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: executeToolAction. Total affected: 1." status="ready"]
1304
+ // CIL[DOC-VECTOR type="configuration" content="Config dependencies: none. Test paths: none." status="ready"]
1305
+ // CIL[DOC-VECTOR type="constraint" content="Driven by chunk inputs; callers [executeToolAction] and callees [adminDb(.framework/functions/_shared/db.ts)] shape downstream behavior." status="ready"]
1306
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
1307
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
1308
+ async function executeCilChunkLookup(params: any, ctx: CoreContext): Promise<any> {
1309
+ const { q, project_id, limit = 5 } = params
1310
+ if (!q) throw new Error('q is required')
1311
+
1312
+ let query = adminDb
1313
+ .from('items')
1314
+ .select('id, title, data')
1315
+ .filter('data->>kb_type', 'eq', 'cil_chunk')
1316
+ .eq('account_id', ctx.accountId)
1317
+ .eq('is_active', true)
1318
+ .limit(50)
1319
+
1320
+ if (project_id) {
1321
+ query = query.filter('data->>project_id', 'eq', project_id)
1322
+ }
1323
+
1324
+ const { data, error } = await query
1325
+ if (error) throw error
1326
+
1327
+ const term = String(q).toLowerCase()
1328
+ const matches = (data || [])
1329
+ .filter((item: any) => {
1330
+ const d = item.data || {}
1331
+ const haystack = [
1332
+ item.title || '',
1333
+ d.chunk_name || '',
1334
+ d.file_path || '',
1335
+ d.macro || '',
1336
+ d.micro || '',
1337
+ ].join(' ').toLowerCase()
1338
+ return haystack.includes(term)
1339
+ })
1340
+ .slice(0, limit)
1341
+ .map((item: any) => {
1342
+ const d = item.data || {}
1343
+ return {
1344
+ chunk_id: item.id,
1345
+ chunk_name: d.chunk_name,
1346
+ file_path: d.file_path,
1347
+ chunk_type: d.chunk_type,
1348
+ macro: d.macro,
1349
+ stability: d.stability,
1350
+ }
1351
+ })
1352
+
1353
+ return { results: matches, count: matches.length }
1354
+ }
1355
+ // CIL[END chunk_id="agent-runner_executecilchunklookup_1784488696"]
1356
+ // CIL[BEGIN chunk_id="agent-runner_executecilgetcontract_1784488696" type="function" identity="executeCilGetContract" parent="agent-runner_ciltools_1784488696"]
1357
+ // CIL[DOC-MACRO purpose="Tool handler: cil_get_contract"]
1358
+ // CIL[DOC-RESPONSIBILITY responsibility="Return the full contract and metadata for a CIL chunk"]
1359
+ // CIL[DOC-CONTEXT module="_shared/agent-runner" layer="infrastructure" related_files="custom/apps/cortex/functions/custom_cil-agent.ts"]
1360
+ // CIL[DOC-CONTRACT inputs="params: { chunk_id }\nctx: CoreContext" outputs="chunk contract object" errors="throws if chunk not found" side_effects="DB read on items table"]
1361
+ // CIL[DOC-MICRO logic_summary="Fetches a cil_chunk item by UUID and returns its contract fields" notable_decisions="[]"]
1362
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
1363
+ // CIL[DOC-STABILITY stability="stable"]
1364
+ // CIL[DOC-AUDIENCE audience="developer"]
1365
+ // CIL[DOC-VECTOR type="context" content="Layer: infrastructure. Direct callers: executeToolAction. Direct callees: adminDb(.framework/functions/_shared/db.ts). Related files: custom/apps/cortex/functions/custom_cil-agent.ts." status="ready"]
1366
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: executeToolAction. Total affected: 1." status="ready"]
1367
+ // CIL[DOC-VECTOR type="configuration" content="Config dependencies: none. Test paths: none." status="ready"]
1368
+ // CIL[DOC-VECTOR type="constraint" content="Driven by chunk inputs; callers [executeToolAction] and callees [adminDb(.framework/functions/_shared/db.ts)] shape downstream behavior." status="ready"]
1369
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
1370
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
1371
+ async function executeCilGetContract(params: any, ctx: CoreContext): Promise<any> {
1372
+ const { chunk_id } = params
1373
+ if (!chunk_id) throw new Error('chunk_id is required')
1374
+
1375
+ const { data, error } = await adminDb
1376
+ .from('items')
1377
+ .select('id, title, data')
1378
+ .eq('id', chunk_id)
1379
+ .filter('data->>kb_type', 'eq', 'cil_chunk')
1380
+ .eq('account_id', ctx.accountId)
1381
+ .single()
1382
+
1383
+ if (error || !data) throw new Error(`CIL chunk not found: ${chunk_id}`)
1384
+
1385
+ const d = data.data || {}
1386
+ return {
1387
+ chunk_id: data.id,
1388
+ chunk_name: d.chunk_name,
1389
+ chunk_type: d.chunk_type,
1390
+ file_path: d.file_path,
1391
+ macro: d.macro,
1392
+ responsibility: d.responsibility,
1393
+ contract: {
1394
+ inputs: d.input_spec,
1395
+ outputs: d.output_spec,
1396
+ errors: d.error_spec,
1397
+ side_effects: d.side_effects,
1398
+ },
1399
+ micro: d.micro,
1400
+ stability: d.stability,
1401
+ confidence: d.confidence,
1402
+ audience: d.audience,
1403
+ }
1404
+ }
1405
+ // CIL[END chunk_id="agent-runner_executecilgetcontract_1784488696"]
1406
+ // CIL[BEGIN chunk_id="agent-runner_executecilblastradius_1784488696" type="function" identity="executeCilBlastRadius" parent="agent-runner_ciltools_1784488696"]
1407
+ // CIL[DOC-MACRO purpose="Tool handler: cil_blast_radius"]
1408
+ // CIL[DOC-RESPONSIBILITY responsibility="Find chunks transitively linked to a given chunk"]
1409
+ // CIL[DOC-CONTEXT module="_shared/agent-runner" layer="infrastructure" related_files="custom/apps/cortex/functions/custom_cil-callmap.ts"]
1410
+ // CIL[DOC-CONTRACT inputs="params: { chunk_id, max_depth? }\nctx: CoreContext" outputs="{ chunk_id, max_depth, affected }" errors="throws on DB failure" side_effects="DB reads on link_types and links tables"]
1411
+ // CIL[DOC-MICRO logic_summary="Resolves CIL link type IDs, performs BFS over links, and joins chunk metadata" notable_decisions="[]"]
1412
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
1413
+ // CIL[DOC-STABILITY stability="stable"]
1414
+ // CIL[DOC-AUDIENCE audience="developer"]
1415
+ // CIL[DOC-VECTOR type="context" content="Layer: infrastructure. Direct callers: executeToolAction. Direct callees: adminDb(.framework/functions/_shared/db.ts). Related files: custom/apps/cortex/functions/custom_cil-callmap.ts." status="ready"]
1416
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: executeToolAction. Total affected: 1." status="ready"]
1417
+ // CIL[DOC-VECTOR type="configuration" content="Config dependencies: none. Test paths: none." status="ready"]
1418
+ // CIL[DOC-VECTOR type="constraint" content="Driven by chunk inputs; callers [executeToolAction] and callees [adminDb(.framework/functions/_shared/db.ts)] shape downstream behavior." status="ready"]
1419
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
1420
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
1421
+ async function executeCilBlastRadius(params: any, ctx: CoreContext): Promise<any> {
1422
+ const { chunk_id, max_depth = 3 } = params
1423
+ if (!chunk_id) throw new Error('chunk_id is required')
1424
+
1425
+ const maxDepth = Math.min(parseInt(max_depth, 10) || 3, 6)
1426
+
1427
+ const { data: linkTypes, error: ltError } = await adminDb
1428
+ .from('link_types')
1429
+ .select('id, slug')
1430
+ .eq('account_id', ctx.accountId)
1431
+ .in('slug', ['cil_imports', 'cil_calls', 'cil_exports', 'cil_tested_by'])
1432
+
1433
+ if (ltError) throw ltError
1434
+ const linkTypeIds = (linkTypes || []).map((lt: any) => lt.id)
1435
+ if (linkTypeIds.length === 0) return { chunk_id, max_depth: maxDepth, affected: [] }
1436
+
1437
+ const visited = new Set<string>()
1438
+ const affected: any[] = []
1439
+ const frontier: { id: string; depth: number }[] = [{ id: chunk_id, depth: 0 }]
1440
+
1441
+ while (frontier.length > 0) {
1442
+ const current = frontier.shift()!
1443
+ if (visited.has(current.id)) continue
1444
+ visited.add(current.id)
1445
+
1446
+ const [sourceLinks, targetLinks] = await Promise.all([
1447
+ adminDb
1448
+ .from('links')
1449
+ .select('id, source_id, target_id, link_type_id, data')
1450
+ .eq('source_id', current.id)
1451
+ .in('link_type_id', linkTypeIds),
1452
+ adminDb
1453
+ .from('links')
1454
+ .select('id, source_id, target_id, link_type_id, data')
1455
+ .eq('target_id', current.id)
1456
+ .in('link_type_id', linkTypeIds),
1457
+ ])
1458
+
1459
+ const allLinks = [...(sourceLinks.data || []), ...(targetLinks.data || [])]
1460
+ for (const link of allLinks) {
1461
+ const neighborId = link.source_id === current.id ? link.target_id : link.source_id
1462
+ if (visited.has(neighborId)) continue
1463
+ affected.push({
1464
+ chunk_id: neighborId,
1465
+ link_type_id: link.link_type_id,
1466
+ depth: current.depth + 1,
1467
+ })
1468
+ if (current.depth + 1 < maxDepth) {
1469
+ frontier.push({ id: neighborId, depth: current.depth + 1 })
1470
+ }
1471
+ }
1472
+ }
1473
+
1474
+ const chunkIds = affected.map(a => a.chunk_id)
1475
+ const { data: items } = await adminDb
1476
+ .from('items')
1477
+ .select('id, title, data')
1478
+ .in('id', chunkIds)
1479
+ .filter('data->>kb_type', 'eq', 'cil_chunk')
1480
+ .eq('account_id', ctx.accountId)
1481
+
1482
+ const itemMap = new Map((items || []).map((i: any) => [i.id, i]))
1483
+
1484
+ return {
1485
+ chunk_id,
1486
+ max_depth: maxDepth,
1487
+ affected: affected.map((a: any) => {
1488
+ const item = itemMap.get(a.chunk_id)
1489
+ const d = item?.data || {}
1490
+ return {
1491
+ ...a,
1492
+ chunk_name: d.chunk_name,
1493
+ file_path: d.file_path,
1494
+ link_slug: linkTypes?.find((lt: any) => lt.id === a.link_type_id)?.slug,
1495
+ }
1496
+ }),
1497
+ }
1498
+ }
1499
+ // CIL[END chunk_id="agent-runner_executecilblastradius_1784488696"]
1500
+ // CIL[BEGIN chunk_id="agent-runner_executecilhashvalidate_1784488696" type="function" identity="executeCilHashValidate" parent="agent-runner_ciltools_1784488696"]
1501
+ // CIL[DOC-MACRO purpose="Tool handler: cil_hash_validate"]
1502
+ // CIL[DOC-RESPONSIBILITY responsibility="Compare a CIL chunk's stored hash to its stored code_content"]
1503
+ // CIL[DOC-CONTEXT module="_shared/agent-runner" layer="infrastructure" related_files=".framework/mcp/tools/cil/validate.ts"]
1504
+ // CIL[DOC-CONTRACT inputs="params: { chunk_id }\nctx: CoreContext" outputs="{ chunk_id, stored_hash, computed_hash, is_current, hash_updated_at }" errors="throws if chunk not found" side_effects="DB read on items table"]
1505
+ // CIL[DOC-MICRO logic_summary="Fetches cil_chunk by UUID, normalises its code_content, computes hash, and compares to stored_hash" notable_decisions="[]"]
1506
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
1507
+ // CIL[DOC-STABILITY stability="stable"]
1508
+ // CIL[DOC-AUDIENCE audience="developer"]
1509
+ // CIL[DOC-VECTOR type="context" content="Layer: infrastructure. Direct callers: executeToolAction. Direct callees: adminDb(.framework/functions/_shared/db.ts). Related files: .framework/mcp/tools/cil/validate.ts." status="ready"]
1510
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: executeToolAction. Total affected: 1." status="ready"]
1511
+ // CIL[DOC-VECTOR type="configuration" content="Config dependencies: none. Test paths: none." status="ready"]
1512
+ // CIL[DOC-VECTOR type="constraint" content="Driven by chunk inputs; callers [executeToolAction] and callees [adminDb(.framework/functions/_shared/db.ts)] shape downstream behavior." status="ready"]
1513
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
1514
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
1515
+ async function executeCilHashValidate(params: any, ctx: CoreContext): Promise<any> {
1516
+ const { chunk_id } = params
1517
+ if (!chunk_id) throw new Error('chunk_id is required')
1518
+
1519
+ const { data, error } = await adminDb
1520
+ .from('items')
1521
+ .select('id, title, data')
1522
+ .eq('id', chunk_id)
1523
+ .filter('data->>kb_type', 'eq', 'cil_chunk')
1524
+ .eq('account_id', ctx.accountId)
1525
+ .single()
1526
+
1527
+ if (error || !data) throw new Error(`CIL chunk not found: ${chunk_id}`)
1528
+
1529
+ const d = data.data || {}
1530
+ const codeContent = d.code_content || ''
1531
+ const storedHash = d.stored_hash
1532
+ const computedHash = computeCilChunkHash(codeContent)
1533
+
1534
+ return {
1535
+ chunk_id,
1536
+ stored_hash: storedHash,
1537
+ computed_hash: computedHash,
1538
+ is_current: storedHash === computedHash,
1539
+ hash_updated_at: d.hash_updated_at,
1540
+ }
1541
+ }
1542
+ // CIL[END chunk_id="agent-runner_executecilhashvalidate_1784488696"]
1543
+ // CIL[BEGIN chunk_id="agent-runner_executecilprojectstatus_1784488696" type="function" identity="executeCilProjectStatus" parent="agent-runner_ciltools_1784488696"]
1544
+ // CIL[DOC-MACRO purpose="Tool handler: cil_project_status"]
1545
+ // CIL[DOC-RESPONSIBILITY responsibility="Return CIL pipeline status for a project"]
1546
+ // CIL[DOC-CONTEXT module="_shared/agent-runner" layer="infrastructure" related_files="custom/apps/cortex/functions/custom_cil-project.ts"]
1547
+ // CIL[DOC-CONTRACT inputs="params: { project_id }\nctx: CoreContext" outputs="{ project_id, title, file_count, chunk_count, embedding_coverage_pct, doc_coverage_pct, last_ingested_at }" errors="throws if project not found" side_effects="DB reads on items table"]
1548
+ // CIL[DOC-MICRO logic_summary="Fetches the cil_project item; if summary fields are missing it computes live counts from related cil_file and cil_chunk items" notable_decisions="[]"]
1549
+ // CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
1550
+ // CIL[DOC-STABILITY stability="stable"]
1551
+ // CIL[DOC-AUDIENCE audience="developer"]
1552
+ // CIL[DOC-VECTOR type="context" content="Layer: infrastructure. Direct callers: executeToolAction. Direct callees: adminDb(.framework/functions/_shared/db.ts). Related files: custom/apps/cortex/functions/custom_cil-project.ts." status="ready"]
1553
+ // CIL[DOC-VECTOR type="blast_radius" content="Direct callers: executeToolAction. Total affected: 1." status="ready"]
1554
+ // CIL[DOC-VECTOR type="configuration" content="Config dependencies: none. Test paths: none." status="ready"]
1555
+ // CIL[DOC-VECTOR type="constraint" content="Driven by chunk inputs; callers [executeToolAction] and callees [adminDb(.framework/functions/_shared/db.ts)] shape downstream behavior." status="ready"]
1556
+ // CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
1557
+ // CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
1558
+ async function executeCilProjectStatus(params: any, ctx: CoreContext): Promise<any> {
1559
+ const { project_id } = params
1560
+ if (!project_id) throw new Error('project_id is required')
1561
+
1562
+ const { data: project, error: projectError } = await adminDb
1563
+ .from('items')
1564
+ .select('id, title, data')
1565
+ .eq('id', project_id)
1566
+ .filter('data->>kb_type', 'eq', 'cil_project')
1567
+ .eq('account_id', ctx.accountId)
1568
+ .single()
1569
+
1570
+ if (projectError || !project) throw new Error(`CIL project not found: ${project_id}`)
1571
+
1572
+ const d = project.data || {}
1573
+
1574
+ if (d.file_count !== undefined && d.chunk_count !== undefined) {
1575
+ return {
1576
+ project_id,
1577
+ title: project.title,
1578
+ file_count: d.file_count,
1579
+ chunk_count: d.chunk_count,
1580
+ embedding_coverage_pct: d.embedding_coverage_pct ?? 0,
1581
+ doc_coverage_pct: d.doc_coverage_pct ?? 0,
1582
+ last_ingested_at: d.last_ingested_at,
1583
+ }
1584
+ }
1585
+
1586
+ const [fileRes, chunkRes] = await Promise.all([
1587
+ adminDb
1588
+ .from('items')
1589
+ .select('*', { count: 'exact' })
1590
+ .filter('data->>project_id', 'eq', project_id)
1591
+ .filter('data->>kb_type', 'eq', 'cil_file')
1592
+ .eq('is_active', true),
1593
+ adminDb
1594
+ .from('items')
1595
+ .select('*', { count: 'exact' })
1596
+ .filter('data->>project_id', 'eq', project_id)
1597
+ .filter('data->>kb_type', 'eq', 'cil_chunk')
1598
+ .eq('is_active', true),
1599
+ ])
1600
+
1601
+ return {
1602
+ project_id,
1603
+ title: project.title,
1604
+ file_count: fileRes.count || 0,
1605
+ chunk_count: chunkRes.count || 0,
1606
+ embedding_coverage_pct: d.embedding_coverage_pct ?? 0,
1607
+ doc_coverage_pct: d.doc_coverage_pct ?? 0,
1608
+ last_ingested_at: d.last_ingested_at,
1609
+ }
1610
+ }
1611
+ // CIL[END chunk_id="agent-runner_executecilprojectstatus_1784488696"]
1612
+ // CIL[BEGIN chunk_id="agent-runner_executesearchlogs_1784488696" type="function" identity="executeSearchLogs" parent="none"]
1613
+ // CIL[DOC-MACRO purpose="Tool handler: search_logs for runtime and trace logs"]
1614
+ // CIL[DOC-RESPONSIBILITY responsibility="Query public.logs for the current account, optionally filtered by level and message substring"]
1615
+ // CIL[DOC-CONTEXT module="_shared/agent-runner" layer="infrastructure" related_files=".framework/migrations/005_core_observability.sql"]
1616
+ // CIL[DOC-CONTRACT inputs="params: { q, level, limit? }\nctx: CoreContext" outputs="{ results }" errors="throws on DB failure" side_effects="DB read on logs table"]
1617
+ // CIL[DOC-MICRO logic_summary="Text-search recent logs scoped to account, ordered by created_at desc" notable_decisions="[]"]
1618
+ // CIL[DOC-CONFIDENCE pass="first" score="0.85" flags="none"]
1619
+ // CIL[DOC-STABILITY stability="experimental"]
1620
+ // CIL[DOC-AUDIENCE audience="developer"]
1621
+ async function executeSearchLogs(params: any, ctx: CoreContext): Promise<any> {
1622
+ const { q, level, limit = 20 } = params
1623
+ let query = adminDb
1624
+ .from('logs')
1625
+ .select('id, app_id, type_id, level, message, context, metadata, created_at')
1626
+ .eq('account_id', ctx.accountId)
1627
+ .order('created_at', { ascending: false })
1628
+ .limit(limit)
1629
+ if (level) query = query.eq('level', level)
1630
+ if (q) query = query.or(`message.ilike.%${q}%,context->>trace.ilike.%${q}%`)
1631
+ const { data, error } = await query
1632
+ if (error) throw error
1633
+ return { results: data || [], count: (data || []).length }
1634
+ }
1635
+ // CIL[END chunk_id="agent-runner_executesearchlogs_1784488696"]
1636
+ // CIL[BEGIN chunk_id="agent-runner_executesearchobservability_1784488696" type="function" identity="executeSearchObservability" parent="none"]
1637
+ // CIL[DOC-MACRO purpose="Tool handler: search_observability for metrics and traces"]
1638
+ // CIL[DOC-RESPONSIBILITY responsibility="Query public.observability for the current account, optionally filtered by metric name"]
1639
+ // CIL[DOC-CONTEXT module="_shared/agent-runner" layer="infrastructure" related_files=".framework/migrations/005_core_observability.sql"]
1640
+ // CIL[DOC-CONTRACT inputs="params: { metric_name, limit? }\nctx: CoreContext" outputs="{ results }" errors="throws on DB failure" side_effects="DB read on observability table"]
1641
+ // CIL[DOC-MICRO logic_summary="Fetch recent observability metrics scoped to account, ordered by created_at desc" notable_decisions="[]"]
1642
+ // CIL[DOC-CONFIDENCE pass="first" score="0.85" flags="none"]
1643
+ // CIL[DOC-STABILITY stability="experimental"]
1644
+ // CIL[DOC-AUDIENCE audience="developer"]
1645
+ async function executeSearchObservability(params: any, ctx: CoreContext): Promise<any> {
1646
+ const { metric_name, limit = 20 } = params
1647
+ let query = adminDb
1648
+ .from('observability')
1649
+ .select('id, app_id, type_id, metric_name, metric_value, metric_unit, context, metadata, created_at')
1650
+ .eq('account_id', ctx.accountId)
1651
+ .order('created_at', { ascending: false })
1652
+ .limit(limit)
1653
+ if (metric_name) query = query.eq('metric_name', metric_name)
1654
+ const { data, error } = await query
1655
+ if (error) throw error
1656
+ return { results: data || [], count: (data || []).length }
1323
1657
  }
1658
+ // CIL[END chunk_id="agent-runner_executesearchobservability_1784488696"]