spine-framework 0.4.4 → 0.5.0-rc.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (305) hide show
  1. package/.env.example +13 -3
  2. package/.framework/cli/commands/bootstrap.ts +392 -0
  3. package/.framework/cli/commands/dev.ts +91 -218
  4. package/.framework/cli/commands/doctor.ts +132 -26
  5. package/.framework/cli/commands/generate.ts +38 -298
  6. package/.framework/cli/commands/init.ts +20 -1
  7. package/.framework/cli/commands/install-app.ts +183 -13
  8. package/.framework/cli/commands/migrate.ts +36 -30
  9. package/.framework/cli/commands/migrations.ts +16 -56
  10. package/.framework/cli/commands/upgrade.ts +35 -0
  11. package/.framework/cli/commands/validate-app.ts +127 -1
  12. package/.framework/cli/commands/validate-schema.ts +147 -0
  13. package/.framework/cli/context.ts +223 -110
  14. package/.framework/cli/index.ts +6 -0
  15. package/.framework/cli/lib/project-files.ts +241 -1
  16. package/.framework/functions/_shared/app-manifest.ts +2 -0
  17. package/.framework/functions/_shared/hash-api-key.ts +183 -79
  18. package/.framework/functions/_shared/logging.ts +562 -224
  19. package/.framework/functions/_shared/middleware.ts +47 -44
  20. package/.framework/functions/_shared/permissions.ts +4 -0
  21. package/.framework/functions/admin-data.ts +51 -28
  22. package/.framework/functions/ai-agents.ts +10 -6
  23. package/.framework/functions/api-keys.ts +301 -122
  24. package/.framework/functions/apps.ts +63 -29
  25. package/.framework/functions/cil-ingest.ts +48 -15
  26. package/.framework/functions/debug-auth.ts +14 -8
  27. package/.framework/functions/deliveries.ts +39 -12
  28. package/.framework/functions/embeddings.ts +149 -78
  29. package/.framework/functions/integration-outbound.ts +67 -27
  30. package/.framework/functions/integrations.ts +254 -127
  31. package/.framework/functions/logs.ts +273 -133
  32. package/.framework/functions/observability.ts +278 -148
  33. package/.framework/functions/pipeline-executions.ts +1 -1
  34. package/.framework/functions/pipelines.ts +14 -9
  35. package/.framework/functions/prompt-configs.ts +7 -4
  36. package/.framework/functions/roles.ts +8 -5
  37. package/.framework/functions/security.ts +30 -17
  38. package/.framework/functions/system-cron.ts +240 -127
  39. package/.framework/functions/tests.ts +7 -6
  40. package/.framework/functions/timers.ts +310 -107
  41. package/.framework/functions/triggers.ts +28 -13
  42. package/.framework/functions/types.ts +13 -10
  43. package/.framework/migrations/069_log_stats_window.sql +54 -0
  44. package/.framework/migrations/070_app_type_schema_rpcs.sql +166 -0
  45. package/.framework/migrations/071_timer_interval_keys.sql +96 -0
  46. package/.framework/migrations/072_slug_autofill.sql +77 -0
  47. package/.framework/migrations/073_seed_member_roles.sql +26 -0
  48. package/.framework/migrations/074_app_route_prefix_domains.sql +168 -0
  49. package/.framework/migrations/075_security_definer_grants.sql +157 -0
  50. package/.framework/src/apps/admin/index.tsx +1141 -775
  51. package/.framework/src/components/ErrorBoundary.tsx +136 -0
  52. package/.framework/src/components/admin/AdminListPage.tsx +81 -73
  53. package/.framework/src/components/admin/AdminSidebar.tsx +88 -79
  54. package/.framework/src/components/admin/EntitySelect.tsx +181 -0
  55. package/.framework/src/components/admin/PageInstructions.tsx +86 -127
  56. package/.framework/src/components/app-shell/GenericDetailPage.tsx +37 -13
  57. package/.framework/src/components/app-shell/GenericListPage.tsx +28 -32
  58. package/.framework/src/components/layout/Sidebar.tsx +300 -177
  59. package/.framework/src/components/runtime/DataDetailPage.tsx +67 -35
  60. package/.framework/src/components/runtime/DataHeader.tsx +96 -48
  61. package/.framework/src/components/runtime/DataListPage.tsx +3 -26
  62. package/.framework/src/components/runtime/DataStats.tsx +10 -10
  63. package/.framework/src/components/runtime/DataTable.tsx +12 -10
  64. package/.framework/src/components/ui/sheet.tsx +24 -26
  65. package/.framework/src/components/ui/textarea.tsx +74 -48
  66. package/.framework/src/contexts/AppContext.tsx +5 -12
  67. package/.framework/src/contexts/AuthContext.tsx +17 -9
  68. package/.framework/src/hooks/useApps.ts +18 -22
  69. package/.framework/src/hooks/useEntityList.ts +12 -9
  70. package/.framework/src/hooks/useListSchema.ts +108 -58
  71. package/.framework/src/instructions/README.md +74 -0
  72. package/.framework/src/instructions/accounts.md +97 -0
  73. package/.framework/src/instructions/ai-agents.md +69 -0
  74. package/.framework/src/instructions/alerts.md +80 -0
  75. package/.framework/src/instructions/api-keys.md +87 -0
  76. package/.framework/src/instructions/apps.md +77 -0
  77. package/.framework/src/instructions/concepts/ai-and-prompts.md +150 -0
  78. package/.framework/src/instructions/concepts/apps-and-renderers.md +228 -0
  79. package/.framework/src/instructions/concepts/auth-and-api-keys.md +133 -0
  80. package/.framework/src/instructions/concepts/automation.md +186 -0
  81. package/.framework/src/instructions/concepts/concurrency-and-idempotency.md +125 -0
  82. package/.framework/src/instructions/concepts/core-vs-custom.md +124 -0
  83. package/.framework/src/instructions/concepts/design-schema.md +152 -0
  84. package/.framework/src/instructions/concepts/integrations-and-webhooks.md +127 -0
  85. package/.framework/src/instructions/concepts/observability.md +116 -0
  86. package/.framework/src/instructions/concepts/permissions.md +150 -0
  87. package/.framework/src/instructions/concepts/runtime-records.md +111 -0
  88. package/.framework/src/instructions/concepts/scope-and-accounts.md +112 -0
  89. package/.framework/src/instructions/concepts/spine-way.md +172 -0
  90. package/.framework/src/instructions/deliveries.md +59 -0
  91. package/.framework/src/instructions/guide/00-orientation.md +106 -0
  92. package/.framework/src/instructions/guide/01-install-and-first-run.md +253 -0
  93. package/.framework/src/instructions/guide/02-project-map.md +147 -0
  94. package/.framework/src/instructions/guide/03-domain-model.md +223 -0
  95. package/.framework/src/instructions/guide/04-first-app.md +227 -0
  96. package/.framework/src/instructions/guide/05-glossary.md +183 -0
  97. package/.framework/src/instructions/integrations.md +96 -0
  98. package/.framework/src/instructions/internal/cil-markers.md +83 -0
  99. package/.framework/src/instructions/internal/docs-contract.md +158 -0
  100. package/.framework/src/instructions/invites.md +60 -0
  101. package/.framework/src/instructions/kb.md +80 -0
  102. package/.framework/src/instructions/logs.md +77 -0
  103. package/.framework/src/instructions/notifications.md +77 -0
  104. package/.framework/src/instructions/observability.md +61 -0
  105. package/.framework/src/instructions/operations/deployment.md +115 -0
  106. package/.framework/src/instructions/operations/migrations.md +125 -0
  107. package/.framework/src/instructions/operations/security-checklist.md +170 -0
  108. package/.framework/src/instructions/operations/troubleshooting.md +170 -0
  109. package/.framework/src/instructions/operations/upgrade.md +116 -0
  110. package/.framework/src/instructions/people.md +91 -0
  111. package/.framework/src/instructions/pipeline-executions.md +62 -0
  112. package/.framework/src/instructions/pipelines.md +78 -0
  113. package/.framework/src/instructions/prompts.md +95 -0
  114. package/.framework/src/instructions/reference/api/admin-data.md +162 -0
  115. package/.framework/src/instructions/reference/api/auth.md +113 -0
  116. package/.framework/src/instructions/reference/api/errors-and-envelopes.md +113 -0
  117. package/.framework/src/instructions/reference/app-manifest.md +184 -0
  118. package/.framework/src/instructions/reference/cli.md +124 -0
  119. package/.framework/src/instructions/reference/database.md +132 -0
  120. package/.framework/src/instructions/reference/design-schema.md +280 -0
  121. package/.framework/src/instructions/reference/env-vars.md +133 -0
  122. package/.framework/src/instructions/reference/frontend-sdk.md +207 -0
  123. package/.framework/src/instructions/reference/functions/account-nodes.md +75 -0
  124. package/.framework/src/instructions/reference/functions/admin-data.md +197 -0
  125. package/.framework/src/instructions/reference/functions/admin-funnel.md +90 -0
  126. package/.framework/src/instructions/reference/functions/admin-install-funnel.md +107 -0
  127. package/.framework/src/instructions/reference/functions/admin-signals.md +102 -0
  128. package/.framework/src/instructions/reference/functions/ai-agents.md +148 -0
  129. package/.framework/src/instructions/reference/functions/ai-interactions.md +156 -0
  130. package/.framework/src/instructions/reference/functions/api-keys.md +130 -0
  131. package/.framework/src/instructions/reference/functions/apps.md +140 -0
  132. package/.framework/src/instructions/reference/functions/auth.md +99 -0
  133. package/.framework/src/instructions/reference/functions/debug-auth.md +91 -0
  134. package/.framework/src/instructions/reference/functions/deliveries.md +110 -0
  135. package/.framework/src/instructions/reference/functions/embeddings.md +166 -0
  136. package/.framework/src/instructions/reference/functions/eval-harness.md +149 -0
  137. package/.framework/src/instructions/reference/functions/index.md +103 -0
  138. package/.framework/src/instructions/reference/functions/integration-outbound.md +200 -0
  139. package/.framework/src/instructions/reference/functions/integration-routes.md +151 -0
  140. package/.framework/src/instructions/reference/functions/integrations.md +130 -0
  141. package/.framework/src/instructions/reference/functions/invites.md +182 -0
  142. package/.framework/src/instructions/reference/functions/item-progress.md +107 -0
  143. package/.framework/src/instructions/reference/functions/kb.md +166 -0
  144. package/.framework/src/instructions/reference/functions/logs.md +141 -0
  145. package/.framework/src/instructions/reference/functions/observability.md +105 -0
  146. package/.framework/src/instructions/reference/functions/pipeline-executions.md +132 -0
  147. package/.framework/src/instructions/reference/functions/pipeline-runner-background.md +121 -0
  148. package/.framework/src/instructions/reference/functions/pipelines.md +136 -0
  149. package/.framework/src/instructions/reference/functions/prompt-configs.md +121 -0
  150. package/.framework/src/instructions/reference/functions/roles.md +121 -0
  151. package/.framework/src/instructions/reference/functions/security.md +140 -0
  152. package/.framework/src/instructions/reference/functions/system-cron.md +136 -0
  153. package/.framework/src/instructions/reference/functions/system.md +106 -0
  154. package/.framework/src/instructions/reference/functions/tests.md +97 -0
  155. package/.framework/src/instructions/reference/functions/timers.md +117 -0
  156. package/.framework/src/instructions/reference/functions/trigger-scheduler.md +102 -0
  157. package/.framework/src/instructions/reference/functions/triggers.md +140 -0
  158. package/.framework/src/instructions/reference/functions/types.md +146 -0
  159. package/.framework/src/instructions/reference/seeds.md +126 -0
  160. package/.framework/src/instructions/reference/shared-modules.md +127 -0
  161. package/.framework/src/instructions/reference/spine-config.md +114 -0
  162. package/.framework/src/instructions/roles.md +80 -0
  163. package/.framework/src/instructions/runtime-collaboration.md +106 -0
  164. package/.framework/src/instructions/runtime-detail.md +70 -0
  165. package/.framework/src/instructions/runtime-list.md +77 -0
  166. package/.framework/src/instructions/security.md +67 -0
  167. package/.framework/src/instructions/tasks/add-a-pipeline.md +110 -0
  168. package/.framework/src/instructions/tasks/add-a-role.md +96 -0
  169. package/.framework/src/instructions/tasks/add-a-timer.md +114 -0
  170. package/.framework/src/instructions/tasks/add-a-trigger.md +106 -0
  171. package/.framework/src/instructions/tasks/add-a-type.md +151 -0
  172. package/.framework/src/instructions/tasks/add-a-webhook.md +108 -0
  173. package/.framework/src/instructions/tasks/add-an-ai-agent.md +108 -0
  174. package/.framework/src/instructions/tasks/add-an-integration.md +124 -0
  175. package/.framework/src/instructions/tasks/build-a-custom-page.md +118 -0
  176. package/.framework/src/instructions/tasks/document-your-app.md +117 -0
  177. package/.framework/src/instructions/tasks/package-and-install-an-app.md +136 -0
  178. package/.framework/src/instructions/tasks/register-an-app.md +137 -0
  179. package/.framework/src/instructions/tasks/test-your-code.md +108 -0
  180. package/.framework/src/instructions/tasks/write-a-function.md +132 -0
  181. package/.framework/src/instructions/tasks/write-a-migration.md +110 -0
  182. package/.framework/src/instructions/testing.md +59 -0
  183. package/.framework/src/instructions/timers.md +84 -0
  184. package/.framework/src/instructions/triggers.md +69 -0
  185. package/.framework/src/instructions/types.md +93 -0
  186. package/.framework/src/lib/app-mount.ts +88 -0
  187. package/.framework/src/pages/NotFoundPage.tsx +62 -31
  188. package/.framework/src/pages/admin/AIAgentDetailPage.tsx +117 -106
  189. package/.framework/src/pages/admin/AIAgentsPage.tsx +86 -48
  190. package/.framework/src/pages/admin/APIKeyDetailPage.tsx +153 -152
  191. package/.framework/src/pages/admin/APIKeysPage.tsx +91 -90
  192. package/.framework/src/pages/admin/AlertsConfigPage.tsx +29 -18
  193. package/.framework/src/pages/admin/AppDetailPage.tsx +6 -60
  194. package/.framework/src/pages/admin/AppsPage.tsx +104 -66
  195. package/.framework/src/pages/admin/DeliveriesPage.tsx +5 -5
  196. package/.framework/src/pages/admin/EmbeddingDetailPage.tsx +5 -23
  197. package/.framework/src/pages/admin/EmbeddingsPage.tsx +5 -26
  198. package/.framework/src/pages/admin/IntegrationDetailPage.tsx +7 -7
  199. package/.framework/src/pages/admin/IntegrationsPage.tsx +85 -43
  200. package/.framework/src/pages/admin/LogsPage.tsx +144 -76
  201. package/.framework/src/pages/admin/ObservabilityDashboard.tsx +223 -140
  202. package/.framework/src/pages/admin/PipelineDetailPage.tsx +134 -113
  203. package/.framework/src/pages/admin/PipelineExecutionsPage.tsx +108 -45
  204. package/.framework/src/pages/admin/PipelinesPage.tsx +160 -115
  205. package/.framework/src/pages/admin/PromptConfigDetailPage.tsx +96 -98
  206. package/.framework/src/pages/admin/PromptConfigsPage.tsx +101 -59
  207. package/.framework/src/pages/admin/RoleDetailPage.tsx +90 -79
  208. package/.framework/src/pages/admin/RolesPage.tsx +86 -43
  209. package/.framework/src/pages/admin/SecurityPage.tsx +1 -1
  210. package/.framework/src/pages/admin/TestingDashboard.tsx +16 -28
  211. package/.framework/src/pages/admin/TimerDetailPage.tsx +117 -100
  212. package/.framework/src/pages/admin/TimersPage.tsx +90 -62
  213. package/.framework/src/pages/admin/TriggerDetailPage.tsx +90 -83
  214. package/.framework/src/pages/admin/TriggersPage.tsx +124 -99
  215. package/.framework/src/pages/admin/TypeDetailPage.tsx +152 -138
  216. package/.framework/src/pages/admin/TypesPage.tsx +90 -48
  217. package/.framework/src/pages/admin/cil/CILAgentPage.tsx +17 -25
  218. package/.framework/src/pages/admin/cil/CILChunkDetailPage.tsx +17 -27
  219. package/.framework/src/pages/admin/cil/CILDashboardPage.tsx +72 -40
  220. package/.framework/src/pages/admin/cil/CILFileListPage.tsx +321 -129
  221. package/.framework/src/pages/admin/cil/CILFileSelectPage.tsx +18 -29
  222. package/.framework/src/pages/admin/cil/CILGraphPage.tsx +26 -38
  223. package/.framework/src/pages/admin/cil/CILIngestionPage.tsx +34 -46
  224. package/.framework/src/pages/admin/cil/CILProjectsPage.tsx +189 -103
  225. package/.framework/src/pages/admin/cil/CILSearchPage.tsx +246 -136
  226. package/.framework/src/pages/admin/cil/CILSuggestionsPage.tsx +11 -22
  227. package/.framework/src/pages/admin/cil/CILVersionsPage.tsx +9 -20
  228. package/.framework/src/pages/admin/cil/FlexiblePipelinePage.tsx +26 -38
  229. package/.framework/src/pages/admin/kb/KBAdminPage.tsx +7 -26
  230. package/.framework/src/pages/auth/LoginPage.tsx +3 -2
  231. package/.framework/src/pages/auth/RegisterPage.tsx +16 -8
  232. package/.framework/templates/agent/AGENTS.md +167 -0
  233. package/.framework/templates/agent/skills/spine-add-ai-agent/SKILL.md +58 -0
  234. package/.framework/templates/agent/skills/spine-add-automation/SKILL.md +62 -0
  235. package/.framework/templates/agent/skills/spine-add-integration/SKILL.md +63 -0
  236. package/.framework/templates/agent/skills/spine-architect/SKILL.md +86 -0
  237. package/.framework/templates/agent/skills/spine-install-app/SKILL.md +79 -0
  238. package/.framework/templates/agent/skills/spine-migration/SKILL.md +75 -0
  239. package/.framework/templates/agent/skills/spine-new-app/SKILL.md +79 -0
  240. package/.framework/templates/agent/skills/spine-new-function/SKILL.md +71 -0
  241. package/.framework/templates/agent/skills/spine-new-type/SKILL.md +72 -0
  242. package/.framework/templates/agent/skills/spine-schema/SKILL.md +77 -0
  243. package/.framework/templates/agent/skills/spine-test/SKILL.md +67 -0
  244. package/.framework/templates/agent/skills/spine-troubleshoot/SKILL.md +74 -0
  245. package/.framework/templates/agent/skills/spine-upgrade/SKILL.md +69 -0
  246. package/.framework/templates/project.json +4 -1
  247. package/.framework/tests/unit/account-scoping-gates.test.ts +394 -0
  248. package/.framework/tests/unit/agent-files.test.ts +355 -0
  249. package/.framework/tests/unit/agent-runner.test.ts +479 -44
  250. package/.framework/tests/unit/api-keys-create-scope-gate.test.ts +160 -0
  251. package/.framework/tests/unit/api-keys-omit-secret-fields.test.ts +38 -0
  252. package/.framework/tests/unit/app-mount.test.ts +80 -0
  253. package/.framework/tests/unit/bootstrap.test.ts +242 -0
  254. package/.framework/tests/unit/cli-context-api-key.test.ts +114 -0
  255. package/.framework/tests/unit/debug-auth.test.ts +38 -0
  256. package/.framework/tests/unit/deliveries-account-scoping.test.ts +185 -0
  257. package/.framework/tests/unit/integration-outbound-credentials.test.ts +167 -0
  258. package/.framework/tests/unit/integrations-audit-redaction.test.ts +130 -0
  259. package/.framework/tests/unit/logging-app-id.test.ts +75 -0
  260. package/.framework/tests/unit/security-definer-grants.test.ts +154 -0
  261. package/.framework/tests/unit/system-context-denial.test.ts +145 -0
  262. package/.framework/tests/unit/system-cron.test.ts +240 -0
  263. package/.framework/tests/unit/timers.test.ts +159 -0
  264. package/README.md +125 -166
  265. package/config/tsconfig.cli.json +2 -2
  266. package/package.json +15 -4
  267. package/templates/project/package-lock.json +2 -2
  268. package/.framework/docs/admin-routes.md +0 -76
  269. package/.framework/docs/api-docs-progress.md +0 -38
  270. package/.framework/docs/api-governance.md +0 -146
  271. package/.framework/docs/api-testing-results.md +0 -212
  272. package/.framework/docs/assembly-launch-guide.md +0 -138
  273. package/.framework/docs/audit-results.md +0 -590
  274. package/.framework/docs/db-api-inventory.md +0 -95
  275. package/.framework/docs/gap-remediation-backlog.md +0 -103
  276. package/.framework/docs/ui-adoption-verification.md +0 -111
  277. package/.framework/docs/ui-api-coverage.md +0 -84
  278. package/.framework/docs/v2-compatibility-audit.md +0 -228
  279. package/.framework/mcp/index.ts +0 -133
  280. package/.framework/mcp/load-env.ts +0 -60
  281. package/.framework/mcp/shared/app-manifest.ts +0 -275
  282. package/.framework/mcp/shared/db.ts +0 -477
  283. package/.framework/mcp/shared/kb.ts +0 -118
  284. package/.framework/mcp/tools/architect.ts +0 -431
  285. package/.framework/mcp/tools/cil/context.ts +0 -345
  286. package/.framework/mcp/tools/cil/graph.ts +0 -242
  287. package/.framework/mcp/tools/cil/ingest.ts +0 -1217
  288. package/.framework/mcp/tools/cil/pipeline-orchestrator.ts +0 -693
  289. package/.framework/mcp/tools/cil/pipeline.ts +0 -767
  290. package/.framework/mcp/tools/cil/project.ts +0 -791
  291. package/.framework/mcp/tools/cil/retrieval.ts +0 -336
  292. package/.framework/mcp/tools/cil/search-core.ts +0 -454
  293. package/.framework/mcp/tools/cil/search.ts +0 -97
  294. package/.framework/mcp/tools/cil/suggest.ts +0 -186
  295. package/.framework/mcp/tools/cil/validate.ts +0 -1369
  296. package/.framework/mcp/tools/kb/delete.ts +0 -139
  297. package/.framework/mcp/tools/kb/index.ts +0 -52
  298. package/.framework/mcp/tools/kb/ingest.ts +0 -84
  299. package/.framework/mcp/tools/kb/list-types.ts +0 -102
  300. package/.framework/mcp/tools/kb/publish.ts +0 -100
  301. package/.framework/mcp/tools/kb/reindex.ts +0 -86
  302. package/.framework/mcp/tools/kb/revoke.ts +0 -85
  303. package/.framework/mcp/tools/kb/search.ts +0 -101
  304. package/.framework/mcp/tools/kb/upsert.ts +0 -125
  305. package/.framework/mcp/tools/observability.ts +0 -510
@@ -0,0 +1,114 @@
1
+ ---
2
+ title: Add a timer
3
+ audience: developers, agents
4
+ kind: task
5
+ verified_against: e13127d
6
+ verified_on: 2026-10-30
7
+ source_files:
8
+ - .framework/functions/timers.ts
9
+ - .framework/functions/system-cron.ts
10
+ - .framework/functions/trigger-scheduler.ts
11
+ - .framework/migrations/020_schema_reconciliation.sql
12
+ - .framework/migrations/068_cron_schedule_timer_rpcs.sql
13
+ - .framework/src/pages/admin/TimerDetailPage.tsx
14
+ ---
15
+
16
+ # Add a timer
17
+
18
+ ## Goal
19
+
20
+ Create a `public.timers` row that runs a pipeline every
21
+ `config.interval_minutes` via the system cron sweep.
22
+
23
+ ## Prerequisites
24
+
25
+ - A pipeline to point at ([Add a pipeline](./add-a-pipeline.md)) —
26
+ `get_due_timers` ignores timers with `pipeline_id` NULL.
27
+
28
+ ## Steps
29
+
30
+ 1. **Create the timer.** Admin UI → **Configs → Timers → New**
31
+ (`configs/timers/new`), or `POST /api/timers` — `name` and `timer_type`
32
+ required (`.framework/functions/timers.ts`). Set
33
+ `pipeline_id` and `config.interval_minutes`:
34
+
35
+ ```json
36
+ {
37
+ "name": "Hourly sync",
38
+ "timer_type": "interval",
39
+ "pipeline_id": "<pipeline-uuid>",
40
+ "config": { "interval_minutes": 60 }
41
+ }
42
+ ```
43
+
44
+ `timer_type` is a label (`interval` | `cron` | `once` in the admin
45
+ detail page, `.framework/src/pages/admin/TimerDetailPage.tsx`) — the
46
+ only field that actually reschedules is `config.interval_minutes`
47
+ (see below).
48
+
49
+ 2. **`next_execution` is armed for you.** On create or toggle-on, the API
50
+ computes `next_execution` from `config.interval_minutes` (or
51
+ `config.interval_seconds`, seconds ÷ 60); `once` timers arm from an
52
+ ISO `config.run_at`/`config.schedule`. You may also pass
53
+ `next_execution` explicitly. If `next_execution` ever ends up NULL on
54
+ an active timer it cannot fire — `get_due_timers` requires
55
+ `next_execution IS NOT NULL` — so check it first when a timer never
56
+ runs.
57
+
58
+ 3. **Understand the clock.** `trigger-scheduler` is the Netlify scheduled
59
+ function (`* * * * *` in `netlify.toml`); each minute it invokes the
60
+ `system-cron` handler under the system principal, which calls
61
+ `get_due_timers(now())`, then `runPipeline(timer.pipeline_id, …)` with a
62
+ `timer:<id>` machine principal, then `update_timer_after_run`
63
+ (`.framework/functions/system-cron.ts`).
64
+
65
+ **`netlify dev` does not run scheduled functions.** Clockwork only exists
66
+ on deployed Netlify — locally, `trigger-scheduler` is loaded but never
67
+ invoked on schedule. To emulate a tick, invoke it manually:
68
+
69
+ ```bash
70
+ curl -X POST http://localhost:8888/.netlify/functions/trigger-scheduler \
71
+ -H 'x-nf-event: schedule' \
72
+ -d "{\"next_run\": \"$(date -u +%Y-%m-%dT%H:%M:%S.000Z)\"}"
73
+ # or: npx netlify functions:invoke trigger-scheduler
74
+ ```
75
+
76
+ 4. **Rescheduling is automatic — or never.** `update_timer_after_run`
77
+ stamps `last_execution`, increments `execution_count` /
78
+ `success_count` / `failure_count`, and sets
79
+ `next_execution = now() + interval_minutes` when
80
+ `config.interval_minutes` is a positive number — otherwise `NULL`, so
81
+ the timer fires once and stops
82
+ (`.framework/migrations/068_cron_schedule_timer_rpcs.sql`).
83
+
84
+ ## Verify
85
+
86
+ - `SELECT id, next_execution, last_execution, execution_count FROM timers`
87
+ shows `next_execution` in the future, then advancing each tick.
88
+ - **Observability → Executions** shows pipeline runs with a
89
+ `timer:<id>` principal.
90
+ - `timer.execution_count` increments; `success_count` / `failure_count`
91
+ reflect outcomes.
92
+
93
+ ## Common mistakes
94
+
95
+ - **`interval_seconds` vs `interval_minutes`.** The admin detail page seeds
96
+ `config` with `{ "interval_seconds": 3600 }`, but `update_timer_after_run`
97
+ only reads `config.interval_minutes` — a timer saved with the default
98
+ config fires once (if `next_execution` was set) and never reschedules.
99
+ - **No `next_execution`.** The API can't set it; timers created through
100
+ `/api/timers` or the admin UI sit dormant until the column is seeded.
101
+ - **`toggle` is blind.** `?action=toggle` flips `is_active` to its negation
102
+ with no target-state argument — read the row first.
103
+ - **Cron expressions do nothing.** There is no cron-expression evaluator;
104
+ `timer_type: "cron"` is only a label — scheduling is interval arithmetic
105
+ on `next_execution`.
106
+ - **Timer with no `pipeline_id`** — filtered out of `get_due_timers`.
107
+
108
+ ## Reference
109
+
110
+ - [timers entity page](../timers.md) ·
111
+ [timers function reference](../reference/functions/timers.md) ·
112
+ [system-cron reference](../reference/functions/system-cron.md) ·
113
+ [trigger-scheduler reference](../reference/functions/trigger-scheduler.md)
114
+ - [Automation](../concepts/automation.md) — *Timers and the scheduler*
@@ -0,0 +1,106 @@
1
+ ---
2
+ title: Add a trigger
3
+ audience: developers, agents
4
+ kind: task
5
+ verified_against: e13127d
6
+ verified_on: 2026-10-30
7
+ source_files:
8
+ - .framework/functions/triggers.ts
9
+ - .framework/functions/_shared/trigger-engine.ts
10
+ - .framework/functions/admin-data.ts
11
+ - .framework/functions/pipeline-runner-background.ts
12
+ - .framework/src/pages/admin/TriggerDetailPage.tsx
13
+ ---
14
+
15
+ # Add a trigger
16
+
17
+ ## Goal
18
+
19
+ Create a `public.triggers` row so a record event (`item_created`,
20
+ `item_updated`, …) dispatches a pipeline execution.
21
+
22
+ ## Prerequisites
23
+
24
+ - The pipeline the trigger will point at ([Add a pipeline](./add-a-pipeline.md)).
25
+ - `INTERNAL_INVOKE_SECRET` set on the deployed functions — dispatch POSTs to
26
+ `pipeline-runner-background` with an `X-Internal-Secret` header and logs
27
+ `cannot dispatch trigger pipeline` (then silently no-ops) when it is
28
+ unset (`.framework/functions/_shared/trigger-engine.ts`). See
29
+ [Environment variables](../reference/env-vars.md).
30
+
31
+ ## Steps
32
+
33
+ 1. **Pick the `event_type`.** Snake_case `<entity>_<verb>` — the engine's
34
+ `EventType` union covers `item`, `account`, `person`, `thread`,
35
+ `message`, `attachment`, and `link` × `created`/`updated`/`deleted`
36
+ (`.framework/functions/_shared/trigger-engine.ts`). Use
37
+ `item_created`, not `item.created` — the admin UI's own placeholder text
38
+ (`e.g., item.created`) is misleading
39
+ (`.framework/src/pages/admin/TriggerDetailPage.tsx`).
40
+
41
+ Scheduled and webhook entry points are **not** trigger event types —
42
+ use a [timer](./add-a-timer.md) or an
43
+ [inbound webhook](./add-a-webhook.md) for those.
44
+
45
+ 2. **Add filter config (optional).** `config.entity_type` scopes the row to
46
+ one table; `config.type_slug` scopes it to one type (resolved from
47
+ `type_id` when the record carries only that);
48
+ `config.filters` supports `$eq`, `$ne`, `$gt`, `$gte`, `$lt`, `$lte`,
49
+ `$in`, `$nin`, `$exists` on `data->>'field'` paths; and
50
+ `config.fire_on_transition` fires only when the record moves *into* a
51
+ matching state (`.framework/functions/_shared/trigger-engine.ts`).
52
+
53
+ ```json
54
+ {
55
+ "name": "New ticket",
56
+ "event_type": "item_created",
57
+ "pipeline_id": "<pipeline-uuid>",
58
+ "config": { "entity_type": "items", "type_slug": "ticket" }
59
+ }
60
+ ```
61
+
62
+ 3. **Create the trigger.** Admin UI → **Configs → Triggers → New**, or
63
+ `POST /api/triggers` — `name`, `event_type`, and `pipeline_id` required
64
+ (`.framework/functions/triggers.ts`).
65
+
66
+ 4. **Keep it enabled.** `is_active: true` (default). Only active triggers
67
+ are matched on an event; `?action=toggle` flips it
68
+ (`.framework/functions/triggers.ts`). Deleting a trigger is a soft
69
+ delete (`is_active = false`) — execution history survives.
70
+
71
+ ## Verify
72
+
73
+ - Create or update a matching record via `/api/admin-data` — creates and
74
+ updates call `fireCreateTriggers` / `fireUpdateTriggers` after the write
75
+ (`.framework/functions/admin-data.ts`).
76
+ - A `pipeline_executions` row appears in `queued`, then `running` /
77
+ `completed` as `pipeline-runner-background` picks it up — watch
78
+ **Observability → Executions**.
79
+ - The `record_trigger_fire` RPC bumps `triggers.trigger_count` /
80
+ `last_triggered`, and `trigger.fired` / `trigger.failed` audit rows land
81
+ in `public.logs`.
82
+
83
+ ## Common mistakes
84
+
85
+ - **Dotted event names.** `item.created` matches nothing — event names are
86
+ `item_created` etc.
87
+ - **Expecting `item_deleted`.** The enum includes it, but the `admin-data`
88
+ delete path never invokes `fireDeleteTriggers` — only create and update
89
+ fire (`.framework/functions/admin-data.ts`;
90
+ `.framework/functions/_shared/trigger-engine.ts`). Use an update
91
+ (`is_active = false`) if you need delete-like notification.
92
+ - **Missing `INTERNAL_INVOKE_SECRET`.** The trigger inserts the queued
93
+ execution row, but the dispatch to `pipeline-runner-background` cannot be
94
+ authenticated, so nothing runs — check function logs for the
95
+ `INTERNAL_INVOKE_SECRET not set` error.
96
+ - **Filtering on `type_slug` without realizing the resolution.** Records
97
+ carry `type_id`, not the slug; the engine resolves the slug once per
98
+ event when any trigger filters on `config.type_slug`
99
+ (`.framework/functions/_shared/trigger-engine.ts`).
100
+
101
+ ## Reference
102
+
103
+ - [triggers entity page](../triggers.md) · [Automation](../concepts/automation.md)
104
+ - [trigger-scheduler reference](../reference/functions/trigger-scheduler.md) ·
105
+ [pipeline-runner-background reference](../reference/functions/pipeline-runner-background.md)
106
+ - [Add a pipeline](./add-a-pipeline.md) · [Add a timer](./add-a-timer.md)
@@ -0,0 +1,151 @@
1
+ ---
2
+ title: Add a type
3
+ audience: developers, agents
4
+ kind: task
5
+ verified_against: e13127d
6
+ verified_on: 2026-10-30
7
+ source_files:
8
+ - .framework/functions/types.ts
9
+ - .framework/functions/_shared/schema-utils.ts
10
+ - .framework/functions/_shared/permissions.ts
11
+ - .framework/src/pages/admin/TypeDetailPage.tsx
12
+ - .framework/migrations/015_types_kind_slug_unique.sql
13
+ ---
14
+
15
+ # Add a type
16
+
17
+ ## Goal
18
+
19
+ Create a `public.types` row whose `design_schema` drives validation,
20
+ permissions, and the generic list/detail UI for a new record shape. Records
21
+ created through `/api/admin-data` stamp this schema onto themselves at write
22
+ time, so a broken schema propagates into every record.
23
+
24
+ ## Prerequisites
25
+
26
+ - A `system_admin` login or an API key with `*:*` scope — `POST /api/types`
27
+ throws `System admin required` for anyone else
28
+ (`.framework/functions/types.ts`).
29
+ - The target `kind` decided up front (`item`, `thread`, `message`, `link`,
30
+ `attachment`, `watcher`, `person`, `account`). The kind chooses the runtime
31
+ table and cannot be implied later — see
32
+ [Runtime records](../concepts/runtime-records.md).
33
+
34
+ ## Steps
35
+
36
+ 1. **Draft the `design_schema`.** Work through the checklist in
37
+ [Design schema](../concepts/design-schema.md); the field/display enums are
38
+ in [Design schema reference](../reference/design-schema.md). The schema
39
+ below is the minimum for the generic renderer:
40
+
41
+ ```json
42
+ {
43
+ "scope": "account",
44
+ "fields": {
45
+ "title": { "data_type": "text", "label": "Title", "required": true, "system": true },
46
+ "status": { "data_type": "select", "label": "Status", "required": true, "system": true,
47
+ "options": ["open", "closed"] }
48
+ },
49
+ "views": {
50
+ "default_list": {
51
+ "type": "list", "display": "table", "label": "Tickets",
52
+ "fields": {
53
+ "title": { "sortable": true, "display_type": "text" },
54
+ "status": { "sortable": true, "display_type": "badge" },
55
+ "created_at": { "sortable": true, "display_type": "timestamp" }
56
+ },
57
+ "default_sort": { "field": "created_at", "direction": "desc" }
58
+ },
59
+ "default_detail": {
60
+ "type": "detail", "label": "Ticket",
61
+ "sections": [
62
+ { "title": "Core",
63
+ "fields": { "title": { "display_type": "input" },
64
+ "status": { "display_type": "select" } } }
65
+ ]
66
+ }
67
+ },
68
+ "record_permissions": {
69
+ "system_admin": ["create", "read", "update", "delete"],
70
+ "support": ["create", "read", "update"]
71
+ },
72
+ "functionality": null
73
+ }
74
+ ```
75
+
76
+ - `fields`: every entry needs `data_type`, `label`, `required`. `select`,
77
+ `multiselect`, and `radio` need `options` — `generateValidationSchema`
78
+ derives allowed values from them (`.framework/functions/_shared/schema-utils.ts`).
79
+ - `record_permissions` keys are `roles.slug` values matched **verbatim** —
80
+ `system_admin`, not `system-admin`. A misspelled or nonexistent slug
81
+ silently grants nothing (`.framework/functions/_shared/permissions.ts`).
82
+ - **Reserved column names.** `items` has real columns `title`,
83
+ `description`, and `status` — a field with one of those names and
84
+ `system: true` maps onto the column; the same name with `system: false`
85
+ lands in `record.data` and list/detail views show the column's value
86
+ (e.g. lifecycle `active`) instead of yours. Pick `system: true` for
87
+ these names or rename the field.
88
+ - `record_permissions` is the **base, most restrictive** grant; add
89
+ `fields.*.permissions` only to loosen one field or to restrict a
90
+ `system: true` field — see [Permission model](../concepts/permissions.md).
91
+ - Generic apps (`renderer: "generic"`) need `views.default_list` and
92
+ `views.default_detail` referencing real fields with known
93
+ `display_type` values; custom apps can omit views but still need
94
+ `fields` + `record_permissions` so `admin-data` can stamp and validate.
95
+
96
+ 2. **Create the type.** Either:
97
+
98
+ - Admin UI → **Configs → Types → New** (`configs/types/new`), fill in
99
+ name, slug, kind, and paste the schema into the Design Schema JSON
100
+ field — the page POSTs to `/api/types?action=create`
101
+ (`.framework/src/pages/admin/TypeDetailPage.tsx`).
102
+ - API: `POST /api/types` with a body containing `name`, `slug`, `kind`,
103
+ and `design_schema` — all three fields are required
104
+ (`.framework/functions/types.ts`).
105
+
106
+ 3. **Let the API generate `validation_schema`.** Do not send it yourself —
107
+ the create handler replaces a missing `validation_schema` with
108
+ `generateValidationSchema(design_schema)` before insert, and update
109
+ regenerates it whenever `design_schema` changes
110
+ (`.framework/functions/types.ts`).
111
+
112
+ 4. **Set the row-level fields.** `ownership` (`system`/`app`/`tenant`),
113
+ `is_active`, `icon`, `color`, and optional `app_id` are columns on the
114
+ `types` row, not inside `design_schema`.
115
+
116
+ ## Verify
117
+
118
+ - `GET /api/types?action=get&slug=<slug>&include_schema=true` returns the row
119
+ with a non-null `validation_schema`.
120
+ - Create a record: `POST /api/admin-data?action=create` with
121
+ `{ "entity": "items", "type_slug": "<slug>", "title": "…", "data": {…} }`
122
+ — the stored record carries the stamped `design_schema`.
123
+ - If the app uses the generic renderer, open its list page — an unknown
124
+ `display_type` or missing `default_list` view shows up immediately as a
125
+ broken page.
126
+
127
+ ## Common mistakes
128
+
129
+ - **Slug collisions.** Slugs must be unique per `kind`
130
+ (`.framework/migrations/015_types_kind_slug_unique.sql`); create throws
131
+ `Type with slug '<slug>' already exists for kind '<kind>'`. App-install
132
+ seeds go further — `conflict: "kind,slug"` makes type slugs effectively
133
+ global, so prefix app types (`support_ticket`, not `ticket`) — see
134
+ [Seeds](../reference/seeds.md).
135
+ - **`system-admin` vs `system_admin`.** Seeded role slugs use underscores;
136
+ `system-admin` grants nothing.
137
+ - **Editing `data_type` on a live field.** Existing records keep their old
138
+ stored values; the schema change does not migrate data.
139
+ - **Using `generate` to scaffold types.** `npx spine-framework generate`
140
+ emits a stale legacy schema shape — author via the admin UI or `/api/types`
141
+ (see [Build your first app — Gaps](../guide/04-first-app.md)).
142
+
143
+ ## Reference
144
+
145
+ - [Design schema (concept)](../concepts/design-schema.md) ·
146
+ [Design schema reference](../reference/design-schema.md)
147
+ - [Permission model](../concepts/permissions.md) ·
148
+ [Runtime records](../concepts/runtime-records.md)
149
+ - [types entity page](../types.md) ·
150
+ [types function reference](../reference/functions/types.md) ·
151
+ [admin-data function reference](../reference/functions/admin-data.md)
@@ -0,0 +1,108 @@
1
+ ---
2
+ title: Add a webhook
3
+ audience: developers, agents
4
+ kind: task
5
+ verified_against: e13127d
6
+ verified_on: 2026-10-30
7
+ source_files:
8
+ - .framework/functions/integration-routes.ts
9
+ - .framework/functions/_shared/webhook-registry.ts
10
+ - .framework/functions/_shared/webhook-registration.ts
11
+ - .framework/functions/integration-outbound.ts
12
+ - .framework/functions/adapters/generic-webhook-inbound.ts
13
+ - .framework/migrations/004_core_rpc_and_broker.sql
14
+ ---
15
+
16
+ # Add a webhook
17
+
18
+ Two directions, two different mechanisms: **inbound** webhooks arrive at the
19
+ `integration-routes` function and are normalized by a registry adapter into
20
+ `items` records; **outbound** webhooks leave through
21
+ `integration-outbound` (usually from a pipeline stage). See
22
+ [Integrations and webhooks](../concepts/integrations-and-webhooks.md).
23
+
24
+ ## Goal — inbound
25
+
26
+ Receive an external POST and turn it into a record.
27
+
28
+ ### Steps
29
+
30
+ 1. **Create the integration** ([Add an integration](./add-an-integration.md))
31
+ the webhook posts against. Set `config.event_path` if the event name
32
+ lives inside the payload (`"type"` or `"data.event"`), and
33
+ `config.handler_name` to force an adapter — otherwise the adapter name
34
+ falls back to `integration.provider`, then `generic-webhook`
35
+ (`.framework/functions/integration-routes.ts`).
36
+
37
+ 2. **Mint an API key for the sender.** `integration-routes` requires a
38
+ machine principal (`x-api-key`), and the key is authorized per
39
+ `account_id` — optionally pinned to `integration_id` and `app_id`
40
+ (`.framework/functions/integration-routes.ts`). See
41
+ [Auth and API keys](../concepts/auth-and-api-keys.md).
42
+
43
+ 3. **Register or reuse an adapter.** `webhook_handlers` rows map a handler
44
+ name → `function_name`; lookup prefers the app/account-scoped row and
45
+ falls back to a global one, then `loadHandler` dynamically imports the
46
+ module and takes `adapt` → `handler` → `default`
47
+ (`.framework/functions/_shared/webhook-registry.ts`). Migration
48
+ 004 seeds a global `generic-webhook` handler bound to
49
+ `adapters/generic-webhook-inbound`, which stores the raw payload as a
50
+ `webhook_event` item (`.framework/migrations/004_core_rpc_and_broker.sql`).
51
+
52
+ 4. **Adapter contract.** The adapter receives `(payload, ctx)` where `ctx`
53
+ carries `integration`, `apiKey`, `accountId`, `appId`, `event`,
54
+ `headers`, and a `logger`; it returns
55
+ `{ type_slug, data, title?, status?, thread?, links?, external_id?, error?, retryable? }`.
56
+ A truthy `error` marks the delivery `failed` (or `pending` when
57
+ `retryable`); a result with `type_slug` is created through `admin-data`
58
+ as an `items` record (`.framework/functions/integration-routes.ts`).
59
+
60
+ 5. **Post to it.** `POST /api/integration-routes?slug=<integration-slug>`
61
+ with `x-api-key: <key>` and the payload body. The event name comes from
62
+ the `x-event-type` header first, then `config.event_path`
63
+ (`.framework/functions/integration-routes.ts`).
64
+
65
+ ## Goal — outbound
66
+
67
+ Call an external URL from a pipeline.
68
+
69
+ ### Steps
70
+
71
+ 1. Add an `http_request` stage (arbitrary HTTP) or a webhook action whose
72
+ `handler_module: 'webhook'` routes to `executeWebhookHandler` — the
73
+ webhook URL comes from the action's config
74
+ (`.framework/functions/_shared/pipeline-runner.ts`).
75
+ 2. For credentialed or retried sends, go through `integration-outbound`
76
+ instead — see [Add an integration](./add-an-integration.md). Deliveries
77
+ are tracked in `webhook_deliveries` with exponential backoff.
78
+
79
+ ## Verify
80
+
81
+ - Inbound: `webhook_deliveries` shows `direction: 'inbound'` rows moving
82
+ `pending → delivered`; the created item appears via `/api/admin-data`.
83
+ - Outbound: `webhook_deliveries` shows `direction: 'outbound'` rows with
84
+ `response_status`.
85
+
86
+ ## Common mistakes
87
+
88
+ - **Human JWT instead of `x-api-key`** — the route requires a machine
89
+ principal and returns `Machine principal required` (401).
90
+ - **API key pinned to a different integration/app** — `403 API key is not
91
+ authorized`.
92
+ - **`x-event-type` vs `event_path` confusion** — the header wins; if the
93
+ sender can't set headers, put the event inside the payload and set
94
+ `config.event_path`.
95
+ - **Unregistered handler name** — `config.handler_name` (or `provider`)
96
+ that has no `webhook_handlers` row and no global fallback returns
97
+ `Handler not found` (404).
98
+ - **Registering handlers under the wrong scope** — an app- or
99
+ account-scoped `webhook_handlers` row shadows the global fallback for
100
+ that scope only.
101
+
102
+ ## Reference
103
+
104
+ - [integration-routes reference](../reference/functions/integration-routes.md) ·
105
+ [integration-outbound reference](../reference/functions/integration-outbound.md) ·
106
+ [deliveries entity page](../deliveries.md)
107
+ - [Integrations and webhooks](../concepts/integrations-and-webhooks.md) ·
108
+ [Add a pipeline](./add-a-pipeline.md)
@@ -0,0 +1,108 @@
1
+ ---
2
+ title: Add an AI agent
3
+ audience: developers, agents
4
+ kind: task
5
+ verified_against: e13127d
6
+ verified_on: 2026-10-30
7
+ source_files:
8
+ - .framework/functions/ai-agents.ts
9
+ - .framework/functions/_shared/agent-runner.ts
10
+ - .framework/functions/_shared/llm.ts
11
+ - .framework/functions/_shared/pipeline-runner.ts
12
+ - .framework/functions/prompt-configs.ts
13
+ - .framework/functions/integration-outbound.ts
14
+ ---
15
+
16
+ # Add an AI agent
17
+
18
+ ## Goal
19
+
20
+ Create an `ai_agents` row plus a `prompt_configs` row, then run it either
21
+ conversationally (`ai-agents?action=run` on a thread) or inside a pipeline
22
+ (`agent_inference` stage). See
23
+ [AI agents, prompt configs, and embeddings](../concepts/ai-and-prompts.md).
24
+
25
+ ## Steps
26
+
27
+ 1. **Create the prompt config.** Admin UI → **Configs → Prompts → New**, or
28
+ `POST /api/prompt-configs`. Key fields: `system_prompt`,
29
+ `context_template` (substitutes `{{user_message}}` and
30
+ `{{conversation_history}}` only), `model` (default `gpt-4o`),
31
+ `temperature` (default `0.7`), `max_tokens` (default `4000`),
32
+ `is_multi_turn`, `max_history_messages` (default `20`)
33
+ (`.framework/functions/prompt-configs.ts`).
34
+
35
+ 2. **Create the agent.** Admin UI → **Configs → AI Agents → New**, or
36
+ `POST /api/ai-agents` — `name` and `agent_type` required; optional
37
+ `model_config`, `system_prompt`, `tools`, `capabilities`,
38
+ `constraints`, `metadata` (`.framework/functions/ai-agents.ts`).
39
+ Link the prompt config via `metadata.default_prompt_config_id`.
40
+
41
+ 3. **Configure the LLM integration.** Inference routes through
42
+ `integration-outbound` against the integration slug `openai` with
43
+ `credential_field: 'api_token'` (`.framework/functions/_shared/llm.ts`).
44
+ Migration 004 seeds `openai` with
45
+ `api_token: { source: 'env_var', value: 'OPENAI_API_KEY' }` — set that env
46
+ var or update the credential. Without it the runner returns a
47
+ `[Mock Response]` string instead of calling the model
48
+ (`.framework/functions/_shared/agent-runner.ts`).
49
+
50
+ 4. **Conversational path — `ai-agents?action=run`.**
51
+ `POST /api/ai-agents?action=run` with `{ thread_id, user_message, mode }`;
52
+ `mode` is `step` | `complete` (default) | `episode`. The runner resolves
53
+ the agent from `thread.data.agent_id` → `design_schema.default_agent_id`,
54
+ and the prompt config from `thread.data.prompt_config_id` →
55
+ `agent.metadata.default_prompt_config_id` — both must be `is_active`
56
+ (`.framework/functions/_shared/agent-runner.ts`). A
57
+ prompt-injection preflight reads `config.security.prompt_injection`
58
+ (`off` | `flag` | `block`) on the agent
59
+ (`.framework/functions/ai-agents.ts`).
60
+
61
+ 5. **Pipeline path — `agent_inference` stage.** Add a stage with
62
+ `stage_type: 'agent_inference'` and `config.context` (required;
63
+ `{{trigger.*}}`/`{{record.*}}` templates resolve). With a
64
+ `config.webhook_url` the stage POSTs `{ context, model, temperature,
65
+ max_tokens }` to that URL via the `generic-http` integration; without
66
+ one it returns a mock response —
67
+ `[Mock Response] Received N chars of context`
68
+ (`.framework/functions/_shared/pipeline-runner.ts`).
69
+
70
+ ## Verify
71
+
72
+ - `POST /api/ai-agents?action=run` on a thread whose `data.agent_id` points
73
+ at the agent — returns a completion (or the mock string when the OpenAI
74
+ credential is missing).
75
+ - `agent_step` rate-limit debits and injection audits appear in
76
+ `public.logs`.
77
+ - The `agent_inference` stage output appears in the execution's stage
78
+ results (**Observability → Executions**).
79
+
80
+ ## Common mistakes
81
+
82
+ - **`threads.target_type` is `NOT NULL`.** A chat thread must be bound to a
83
+ target record — there is no unbound "conversation" row. Point it at
84
+ something stable such as `target_type: 'account'` +
85
+ `target_id: <account id>` and keep `data.agent_id` /
86
+ `data.prompt_config_id` on the thread.
87
+ - **No `default_prompt_config_id` on the agent and no
88
+ `prompt_config_id` on the thread** — the run has no prompt config to
89
+ assemble from; always set the agent fallback.
90
+ - **Missing `OPENAI_API_KEY` / unconfigured `openai` integration** — you
91
+ get a mock response, not an error; check the reply text for
92
+ `[Mock Response]`.
93
+ - **`agent_inference` with no `webhook_url`** — returns a mock. This stage
94
+ is an HTTP bridge, not the in-process `runAgent` path.
95
+ - **Agent or prompt config `is_active: false`** — resolution fails before
96
+ inference.
97
+ - **`context_template` with unsupported placeholders** — only
98
+ `{{user_message}}` and `{{conversation_history}}` substitute; anything
99
+ else stays literal (`.framework/functions/_shared/agent-runner.ts`).
100
+
101
+ ## Reference
102
+
103
+ - [ai-agents entity page](../ai-agents.md) ·
104
+ [ai-agents function reference](../reference/functions/ai-agents.md) ·
105
+ [prompt-configs function reference](../reference/functions/prompt-configs.md) ·
106
+ [prompts entity page](../prompts.md)
107
+ - [AI agents, prompt configs, and embeddings](../concepts/ai-and-prompts.md) ·
108
+ [Add a pipeline](./add-a-pipeline.md) · [Add an integration](./add-an-integration.md)