nodal-agents 0.4.0 → 0.4.2

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 (174) hide show
  1. package/README.md +38 -14
  2. package/cli.js +16 -1
  3. package/migrations/0030_agent_fallback_llm_keys.sql +6 -0
  4. package/migrations/0031_llm_key_capabilities.sql +7 -0
  5. package/migrations/0032_drop_llm_key_model_caps.sql +9 -0
  6. package/migrations/0033_agent_fallback_chain.sql +21 -0
  7. package/migrations/meta/_journal.json +28 -0
  8. package/package.json +1 -1
  9. package/runner.js +512 -79
  10. package/web/.next/BUILD_ID +1 -1
  11. package/web/.next/build-manifest.json +2 -2
  12. package/web/.next/prerender-manifest.json +3 -3
  13. package/web/.next/server/app/(dashboard)/agents/[id]/edit/page.js +3 -3
  14. package/web/.next/server/app/(dashboard)/agents/[id]/edit/page.js.nft.json +1 -1
  15. package/web/.next/server/app/(dashboard)/agents/[id]/edit/page_client-reference-manifest.js +1 -1
  16. package/web/.next/server/app/(dashboard)/agents/[id]/telegram/page.js +1 -1
  17. package/web/.next/server/app/(dashboard)/agents/[id]/telegram/page.js.nft.json +1 -1
  18. package/web/.next/server/app/(dashboard)/agents/[id]/telegram/page_client-reference-manifest.js +1 -1
  19. package/web/.next/server/app/(dashboard)/agents/page.js +2 -2
  20. package/web/.next/server/app/(dashboard)/agents/page.js.nft.json +1 -1
  21. package/web/.next/server/app/(dashboard)/agents/page_client-reference-manifest.js +1 -1
  22. package/web/.next/server/app/(dashboard)/approvals/page.js +2 -2
  23. package/web/.next/server/app/(dashboard)/approvals/page.js.nft.json +1 -1
  24. package/web/.next/server/app/(dashboard)/approvals/page_client-reference-manifest.js +1 -1
  25. package/web/.next/server/app/(dashboard)/automations/page.js +1 -1
  26. package/web/.next/server/app/(dashboard)/automations/page.js.nft.json +1 -1
  27. package/web/.next/server/app/(dashboard)/automations/page_client-reference-manifest.js +1 -1
  28. package/web/.next/server/app/(dashboard)/billing/page.js +1 -1
  29. package/web/.next/server/app/(dashboard)/billing/page.js.nft.json +1 -1
  30. package/web/.next/server/app/(dashboard)/billing/page_client-reference-manifest.js +1 -1
  31. package/web/.next/server/app/(dashboard)/chat/page.js +2 -2
  32. package/web/.next/server/app/(dashboard)/chat/page.js.nft.json +1 -1
  33. package/web/.next/server/app/(dashboard)/chat/page_client-reference-manifest.js +1 -1
  34. package/web/.next/server/app/(dashboard)/connectors/page.js +1 -1
  35. package/web/.next/server/app/(dashboard)/connectors/page.js.nft.json +1 -1
  36. package/web/.next/server/app/(dashboard)/connectors/page_client-reference-manifest.js +1 -1
  37. package/web/.next/server/app/(dashboard)/credentials/page.js +1 -1
  38. package/web/.next/server/app/(dashboard)/credentials/page.js.nft.json +1 -1
  39. package/web/.next/server/app/(dashboard)/credentials/page_client-reference-manifest.js +1 -1
  40. package/web/.next/server/app/(dashboard)/jobs/[id]/page.js +2 -2
  41. package/web/.next/server/app/(dashboard)/jobs/[id]/page.js.nft.json +1 -1
  42. package/web/.next/server/app/(dashboard)/jobs/[id]/page_client-reference-manifest.js +1 -1
  43. package/web/.next/server/app/(dashboard)/jobs/page.js +2 -2
  44. package/web/.next/server/app/(dashboard)/jobs/page.js.nft.json +1 -1
  45. package/web/.next/server/app/(dashboard)/jobs/page_client-reference-manifest.js +1 -1
  46. package/web/.next/server/app/(dashboard)/llm-providers/page.js +2 -2
  47. package/web/.next/server/app/(dashboard)/llm-providers/page.js.nft.json +1 -1
  48. package/web/.next/server/app/(dashboard)/llm-providers/page_client-reference-manifest.js +1 -1
  49. package/web/.next/server/app/(dashboard)/logs/page.js +1 -1
  50. package/web/.next/server/app/(dashboard)/logs/page.js.nft.json +1 -1
  51. package/web/.next/server/app/(dashboard)/logs/page_client-reference-manifest.js +1 -1
  52. package/web/.next/server/app/(dashboard)/mcp/page.js +2 -2
  53. package/web/.next/server/app/(dashboard)/mcp/page.js.nft.json +1 -1
  54. package/web/.next/server/app/(dashboard)/mcp/page_client-reference-manifest.js +1 -1
  55. package/web/.next/server/app/(dashboard)/memories/page.js +2 -2
  56. package/web/.next/server/app/(dashboard)/memories/page.js.nft.json +1 -1
  57. package/web/.next/server/app/(dashboard)/memories/page_client-reference-manifest.js +1 -1
  58. package/web/.next/server/app/(dashboard)/page.js +3 -3
  59. package/web/.next/server/app/(dashboard)/page.js.nft.json +1 -1
  60. package/web/.next/server/app/(dashboard)/page_client-reference-manifest.js +1 -1
  61. package/web/.next/server/app/(dashboard)/settings/page.js +2 -2
  62. package/web/.next/server/app/(dashboard)/settings/page.js.nft.json +1 -1
  63. package/web/.next/server/app/(dashboard)/settings/page_client-reference-manifest.js +1 -1
  64. package/web/.next/server/app/(dashboard)/skills/[id]/edit/page.js +1 -1
  65. package/web/.next/server/app/(dashboard)/skills/[id]/edit/page.js.nft.json +1 -1
  66. package/web/.next/server/app/(dashboard)/skills/[id]/edit/page_client-reference-manifest.js +1 -1
  67. package/web/.next/server/app/(dashboard)/skills/new/page.js +2 -2
  68. package/web/.next/server/app/(dashboard)/skills/new/page.js.nft.json +1 -1
  69. package/web/.next/server/app/(dashboard)/skills/new/page_client-reference-manifest.js +1 -1
  70. package/web/.next/server/app/(dashboard)/skills/page.js +2 -2
  71. package/web/.next/server/app/(dashboard)/skills/page.js.nft.json +1 -1
  72. package/web/.next/server/app/(dashboard)/skills/page_client-reference-manifest.js +1 -1
  73. package/web/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  74. package/web/.next/server/app/_global-error.html +1 -1
  75. package/web/.next/server/app/_global-error.rsc +2 -2
  76. package/web/.next/server/app/_global-error.segments/_full.segment.rsc +2 -2
  77. package/web/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  78. package/web/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  79. package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  80. package/web/.next/server/app/_global-error.segments/_index.segment.rsc +2 -2
  81. package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  82. package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  83. package/web/.next/server/app/_not-found.html +1 -1
  84. package/web/.next/server/app/_not-found.rsc +3 -3
  85. package/web/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  86. package/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  87. package/web/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
  88. package/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  89. package/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  90. package/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  91. package/web/.next/server/app/api/oauth/[provider]/callback/route.js +1 -1
  92. package/web/.next/server/app/api/oauth/[provider]/start/route.js +1 -1
  93. package/web/.next/server/app/login/page_client-reference-manifest.js +1 -1
  94. package/web/.next/server/app/onboarding/page_client-reference-manifest.js +1 -1
  95. package/web/.next/server/app/onboarding.html +1 -1
  96. package/web/.next/server/app/onboarding.rsc +3 -3
  97. package/web/.next/server/app/onboarding.segments/_full.segment.rsc +3 -3
  98. package/web/.next/server/app/onboarding.segments/_head.segment.rsc +1 -1
  99. package/web/.next/server/app/onboarding.segments/_index.segment.rsc +3 -3
  100. package/web/.next/server/app/onboarding.segments/_tree.segment.rsc +2 -2
  101. package/web/.next/server/app/onboarding.segments/onboarding/__PAGE__.segment.rsc +1 -1
  102. package/web/.next/server/app/onboarding.segments/onboarding.segment.rsc +1 -1
  103. package/web/.next/server/chunks/1945.js +1 -0
  104. package/web/.next/server/chunks/3233.js +1 -0
  105. package/web/.next/server/chunks/4574.js +1 -1
  106. package/web/.next/server/chunks/4808.js +1 -0
  107. package/web/.next/server/chunks/4839.js +1 -0
  108. package/web/.next/server/chunks/{3057.js → 7466.js} +1 -1
  109. package/web/.next/server/chunks/7741.js +3 -3
  110. package/web/.next/server/chunks/8206.js +1 -0
  111. package/web/.next/server/chunks/8398.js +1 -0
  112. package/web/.next/server/chunks/{7557.js → 9606.js} +2 -2
  113. package/web/.next/server/middleware-build-manifest.js +1 -1
  114. package/web/.next/server/pages/404.html +1 -1
  115. package/web/.next/server/pages/500.html +1 -1
  116. package/web/.next/server/server-reference-manifest.js +1 -1
  117. package/web/.next/server/server-reference-manifest.json +1 -1
  118. package/web/.next/static/chunks/{9060-df7c0c4c6fa27737.js → 2575-e660568bd1a9bcb6.js} +2 -2
  119. package/web/.next/static/chunks/3233-e6efb7fb1fa24591.js +1 -0
  120. package/web/.next/static/chunks/5436-c1006a40e59853ed.js +1 -0
  121. package/web/.next/static/chunks/7025-7afa82fda10bddc4.js +62 -0
  122. package/web/.next/static/chunks/{5801-e411029984b17b8b.js → 8396-f3502b9af3172006.js} +1 -1
  123. package/web/.next/static/chunks/{8503-ced632da5c3fce79.js → 9098-2bfef80a73c706b3.js} +1 -1
  124. package/web/.next/static/chunks/9123-20653d928e33410a.js +1 -0
  125. package/web/.next/static/chunks/{6679-7c76034b83edeb06.js → 9582-fbf7c8d9b2a39101.js} +1 -1
  126. package/web/.next/static/chunks/app/(dashboard)/agents/[id]/edit/page-a8e293c54c818084.js +2 -0
  127. package/web/.next/static/chunks/app/(dashboard)/agents/[id]/telegram/{page-e6b35d5f361044a9.js → page-7a94ae67b2c3c9c3.js} +1 -1
  128. package/web/.next/static/chunks/app/(dashboard)/agents/page-b258b8975ac6450b.js +1 -0
  129. package/web/.next/static/chunks/app/(dashboard)/approvals/page-79dea6e91956eeba.js +1 -0
  130. package/web/.next/static/chunks/app/(dashboard)/automations/page-3b863b7af8e2c1a3.js +1 -0
  131. package/web/.next/static/chunks/app/(dashboard)/chat/page-4d965bb7ee3732db.js +1 -0
  132. package/web/.next/static/chunks/app/(dashboard)/connectors/page-4a437ba82f4086da.js +1 -0
  133. package/web/.next/static/chunks/app/(dashboard)/jobs/[id]/page-be20dcbf25c8f3ce.js +1 -0
  134. package/web/.next/static/chunks/app/(dashboard)/jobs/page-94a311f688a255d8.js +1 -0
  135. package/web/.next/static/chunks/app/(dashboard)/layout-e1b0d4fad2926646.js +1 -0
  136. package/web/.next/static/chunks/app/(dashboard)/llm-providers/page-e5e2c4e2b783d37f.js +1 -0
  137. package/web/.next/static/chunks/app/(dashboard)/mcp/page-c071c54f76273ac4.js +1 -0
  138. package/web/.next/static/chunks/app/(dashboard)/memories/page-8ca0b34ad35eb1fa.js +1 -0
  139. package/web/.next/static/chunks/app/(dashboard)/{page-fc49d7ed8e472118.js → page-fb50e1576a3ab2e4.js} +1 -1
  140. package/web/.next/static/chunks/app/(dashboard)/settings/page-7b256e9c462e97f8.js +1 -0
  141. package/web/.next/static/chunks/app/(dashboard)/skills/[id]/edit/page-12930816795e8b20.js +1 -0
  142. package/web/.next/static/chunks/app/(dashboard)/skills/new/page-e3a19abaf7468db9.js +1 -0
  143. package/web/.next/static/chunks/app/(dashboard)/skills/page-43f1475a0bc9c45f.js +1 -0
  144. package/web/.next/static/css/78ead23854ab041e.css +3 -0
  145. package/web/.next/server/chunks/1511.js +0 -1
  146. package/web/.next/server/chunks/2103.js +0 -1
  147. package/web/.next/server/chunks/211.js +0 -1
  148. package/web/.next/server/chunks/8178.js +0 -1
  149. package/web/.next/server/chunks/9201.js +0 -1
  150. package/web/.next/server/chunks/9824.js +0 -1
  151. package/web/.next/static/chunks/1165-ec573be2aa63710b.js +0 -1
  152. package/web/.next/static/chunks/2569-6b5e0af9c1f584a4.js +0 -1
  153. package/web/.next/static/chunks/6522-3f865de55adb618d.js +0 -1
  154. package/web/.next/static/chunks/921-f437093debcddbb3.js +0 -1
  155. package/web/.next/static/chunks/9421-d522a48618c4fe37.js +0 -62
  156. package/web/.next/static/chunks/app/(dashboard)/agents/[id]/edit/page-d3724fbf38b71806.js +0 -2
  157. package/web/.next/static/chunks/app/(dashboard)/agents/page-b58294bf588f4581.js +0 -1
  158. package/web/.next/static/chunks/app/(dashboard)/approvals/page-b9e504918d043b6d.js +0 -1
  159. package/web/.next/static/chunks/app/(dashboard)/automations/page-4807e81e2af3030e.js +0 -1
  160. package/web/.next/static/chunks/app/(dashboard)/chat/page-2c8f9571a443f250.js +0 -1
  161. package/web/.next/static/chunks/app/(dashboard)/connectors/page-72ccb0e3a5ed6f2d.js +0 -1
  162. package/web/.next/static/chunks/app/(dashboard)/jobs/[id]/page-40172a14d0b1368f.js +0 -1
  163. package/web/.next/static/chunks/app/(dashboard)/jobs/page-d4a3a16745e02fd1.js +0 -1
  164. package/web/.next/static/chunks/app/(dashboard)/layout-4d5634ba460464d7.js +0 -1
  165. package/web/.next/static/chunks/app/(dashboard)/llm-providers/page-90fb785e2ab32759.js +0 -1
  166. package/web/.next/static/chunks/app/(dashboard)/mcp/page-426478332dfe8313.js +0 -1
  167. package/web/.next/static/chunks/app/(dashboard)/memories/page-aa46f5f7efbfa262.js +0 -1
  168. package/web/.next/static/chunks/app/(dashboard)/settings/page-1cc10beb46234c7d.js +0 -1
  169. package/web/.next/static/chunks/app/(dashboard)/skills/[id]/edit/page-0b61f21847f4c7a0.js +0 -1
  170. package/web/.next/static/chunks/app/(dashboard)/skills/new/page-9de96e643c361732.js +0 -1
  171. package/web/.next/static/chunks/app/(dashboard)/skills/page-4566512d74e54bfe.js +0 -1
  172. package/web/.next/static/css/0a81480f93d3ab37.css +0 -3
  173. /package/web/.next/static/{9FXcaPSw8KYgjKzjKLpT2 → ZuUX-HBTQOhLf0tFI6JQI}/_buildManifest.js +0 -0
  174. /package/web/.next/static/{9FXcaPSw8KYgjKzjKLpT2 → ZuUX-HBTQOhLf0tFI6JQI}/_ssgManifest.js +0 -0
package/README.md CHANGED
@@ -7,9 +7,20 @@
7
7
  [![Node](https://img.shields.io/badge/node-22%2B-339933?logo=node.js&logoColor=white)](https://nodejs.org)
8
8
  [![TypeScript](https://img.shields.io/badge/typescript-strict-3178c6?logo=typescript&logoColor=white)](https://www.typescriptlang.org)
9
9
 
10
- Build and orchestrate AI agents on your own hardware. Multi-LLM,
11
- multi-channel, multi-connector **no SaaS lock-in, no per-token markup,
12
- no cloud roundtrip.** Runs on any machine with Node 22+ — Mac, PC, Linux.
10
+ Build and orchestrate a **team of AI agents** on your own hardware
11
+ each with its own personality, tools, memory, and model. Talk to them in
12
+ the dashboard or on Telegram; they research, write files, call your
13
+ connectors, and delegate to each other to get the job done.
14
+
15
+ **No SaaS lock-in. No per-token markup. No cloud roundtrip.** Two commands
16
+ to install, runs on any machine with Node 22+ (Mac, PC, Linux), and works
17
+ with **any LLM** — frontier or local, paid or free.
18
+
19
+ > **Run the models others choke on.** DeepSeek V4 works out of the box —
20
+ > including the cheap, fast OpenRouter routes — because the runtime quietly
21
+ > handles its quirks (non-standard tool-call payloads) instead of failing.
22
+ > Mix the right model per agent: a frontier model where judgment matters, a
23
+ > near-free one where it doesn't.
13
24
 
14
25
  ---
15
26
 
@@ -18,9 +29,11 @@ no cloud roundtrip.** Runs on any machine with Node 22+ — Mac, PC, Linux.
18
29
  | | |
19
30
  | --- | --- |
20
31
  | 🏠  **Local-first** | Single binary, embedded Postgres, zero cloud dependency. Your conversations, memory, and credentials stay on your machine. |
21
- | 🔌  **LLM-agnostic** | Anthropic, OpenAI, Google, Groq, Mistral, OpenRouter, or any OpenAI-compatible local model (LM Studio, Ollama). Per-agent key, swap providers without code changes. |
32
+ | 🔌  **Any model, per agent** | Anthropic, OpenAI, Google, Groq, Mistral, OpenRouter, or any local model (LM Studio, Ollama). Setup is just an API key per provider — **each agent picks its own model**, so you can run Claude for the orchestrator and **DeepSeek V4** for the workers. The quirks of OSS frontier models (DeepSeek's non-spec tool args, Kimi/Qwen/GLM XML tool formats) are handled automatically. |
33
+ | 🛟  **Provider failover** | Give an agent a backup key chain — if its provider 5xx's, times out, or hits quota mid-job, the runner fails over to the next one and keeps going (and fails loud only when the whole chain is down). |
22
34
  | 🧠  **Memory that compounds** | Persistent facts (entity-scoped, auto-injected into every job) and chat-thread continuity (your agent remembers what it said 30 seconds ago — and what it said yesterday). |
23
- | 🤝  **Orchestrators that finish** | Router and planner orchestrators delegate to specialist sub-agents. Hard guards against runaway loops — turn caps, chain caps, per-slug delegation budgets, smart retry on side-effect-free failures. |
35
+ | 🤝  **Orchestrators that finish** | Router and planner orchestrators delegate to specialist sub-agents, then resume on completion to wrap up the answer. |
36
+ | 🛡️  **Agents that don't lie, loop, or die** | Generic runtime guards: a per-job token budget and a no-progress detector kill runaway loops; a no-false-success guard refuses to report "done" when an action actually failed (fail loud, never fake it); turn/chain/delegation caps bound everything. |
24
37
  | 🔧  **Multi-instance connectors** | Gmail perso *and* Gmail boulot on the same install. OAuth *and* API-key supported. Active list + Marketplace UI in the dashboard. |
25
38
  | 🗂️  **Workspaces** | Multiple isolated workspaces on one install (personal vs work) — each with its own agents, skills, connectors, jobs and memory. Switch from the sidebar. |
26
39
  | 🤖  **Self-extending (ROOT agent)** | Designate an orchestrator as ROOT and let it create skills/agents and assign them on your behalf — gated by per-grant toggles and an autonomy level (propose-confirm → fully-autonomous). |
@@ -48,8 +61,8 @@ nodal-agents up
48
61
 
49
62
  Open <http://localhost:3000>. The CLI spawns an embedded Postgres on a
50
63
  free port, applies migrations, seeds the system skills, and starts the
51
- runner (`:3001`) and dashboard (`:3000`). Configure your LLM provider
52
- from **Settings → LLM Keys** in the dashboard.
64
+ runner (`:3001`) and dashboard (`:3000`). Connect an LLM provider from
65
+ **LLM Providers** in the dashboard — paste an API key and you're running.
53
66
 
54
67
  > Requires Node 22+. No external Postgres, no Redis, no cloud config.
55
68
  > Data lives at `~/.nodalai/` — wipe with `rm -rf ~/.nodalai`.
@@ -77,7 +90,7 @@ When a newer version is available, `nodal-agents up` also prints a one-line
77
90
  notice:
78
91
 
79
92
  ```
80
- ℹ v0.4.0 available — run `nodal-agents update`
93
+ ℹ v0.4.2 available — run `nodal-agents update`
81
94
  ```
82
95
 
83
96
  ### Build from source
@@ -131,7 +144,8 @@ Delegations create child jobs that resume the parent on completion.
131
144
 
132
145
  | Route | Purpose |
133
146
  | --- | --- |
134
- | `/agents` | Create, edit, assign skills + connectors + MCP servers to agents. |
147
+ | `/agents` | Create, edit, assign skills + connectors + MCP servers to agents — and pick each agent's provider + model (with an optional failover chain). |
148
+ | `/llm-providers` | Connect each LLM provider with a single API key — enable/disable inline. Models are chosen per-agent, not here. |
135
149
  | `/jobs` | Live job stream — task, agent, status, full transcript, tool I/O. |
136
150
  | `/connectors` | Active connector instances + Marketplace (multi-instance, OAuth or API-key). |
137
151
  | `/mcp` | Active MCP servers + Marketplace — HTTP & stdio, a growing catalogue, plus add/edit your own custom servers. |
@@ -140,7 +154,7 @@ Delegations create child jobs that resume the parent on completion.
140
154
  | `/logs` | Tool-call audit — input/output JSON per call, filterable by tool name. |
141
155
  | `/approvals` | Human-in-the-loop gates for risky tools (and the ROOT agent's meta-tools under propose-confirm). |
142
156
  | `/automations` | Cron-scheduled agent triggers. |
143
- | `/settings` | LLM keys, auth mode, network (loopback / LAN), bot tokens, workspace management, ROOT-agent designation + grants/autonomy. |
157
+ | `/settings` | Auth mode, network (loopback / LAN), bot tokens, workspace management, ROOT-agent grants + autonomy. |
144
158
 
145
159
  ---
146
160
 
@@ -204,18 +218,28 @@ pnpm deps:check # runs locally and in CI before every release
204
218
 
205
219
  ## Status
206
220
 
207
- **Current release:** `0.3.10` on npm `latest`. Used daily by the
221
+ **Current release:** `0.4.2` on npm `latest`. Used daily by the
208
222
  maintainer, stable enough for personal production. Pre-1.0 — breaking
209
223
  changes are still possible between minors.
210
224
 
211
225
  ### Shipped and working
212
226
 
213
- - Multi-LLM provider routing with per-agent keys
227
+ - Multi-LLM, **per-agent model selection** — provider setup is just an API key
228
+ (one per provider); each agent chooses its own model on top. Frontier and
229
+ local providers, including **DeepSeek V4** (non-spec tool-call args normalized
230
+ automatically), plus native tool-call parsing for Kimi K2 / Qwen3-Coder / GLM
231
+ - **Provider failover** — an opt-in ordered key chain per agent; on a 5xx /
232
+ timeout / quota mid-job the runner fails over to the next key, and fails loud
233
+ (`all_providers_failed`) only when the whole chain is exhausted
234
+ - **Reliability guards (generic, model-agnostic)** — per-job token budget +
235
+ no-progress detector (kill runaway loops), and a no-false-success guard that
236
+ refuses to complete a job as "success" when a tool action failed and was never
237
+ resolved (fail loud, never fake it)
214
238
  - Persistent memory (sanitation, dedup, importance ranking, auto-injection,
215
239
  feedback loop)
216
240
  - Session-thread continuity on chat channels (Telegram today)
217
- - Orchestrator (router + planner) with delegation chains and anti-runaway
218
- caps
241
+ - Orchestrator (router + planner) with delegation chains that resume the
242
+ parent on completion
219
243
  - Multi-instance connectors with OAuth (Gmail, Drive, Sheets, Docs, Notion,
220
244
  Airtable) and API-key (Notion, Airtable, Apify, Firecrawl, Tavily)
221
245
  - MCP catalog — Streamable HTTP *and* stdio (local subprocess) servers, API-key auth; a growing catalogue (Stripe, n8n, Supabase, Airtable, Notion…) with a "test pending" badge on entries not yet verified live, plus add *and edit* your own custom HTTP/stdio servers from the dashboard
package/cli.js CHANGED
@@ -12185,6 +12185,13 @@ var init_connector_catalog = __esm({
12185
12185
  }
12186
12186
  });
12187
12187
 
12188
+ // ../../packages/shared/src/model-catalog.ts
12189
+ var init_model_catalog = __esm({
12190
+ "../../packages/shared/src/model-catalog.ts"() {
12191
+ "use strict";
12192
+ }
12193
+ });
12194
+
12188
12195
  // ../../packages/shared/src/index.ts
12189
12196
  var init_src2 = __esm({
12190
12197
  "../../packages/shared/src/index.ts"() {
@@ -12210,6 +12217,7 @@ var init_src2 = __esm({
12210
12217
  init_providers();
12211
12218
  init_root_agent();
12212
12219
  init_connector_catalog();
12220
+ init_model_catalog();
12213
12221
  }
12214
12222
  });
12215
12223
 
@@ -12364,7 +12372,6 @@ var init_llm_keys = __esm({
12364
12372
  apiKeyLast4: text("api_key_last4").notNull().default(""),
12365
12373
  baseUrl: text("base_url"),
12366
12374
  nickname: text("nickname"),
12367
- defaultModel: text("default_model"),
12368
12375
  isActive: boolean("is_active").notNull().default(true),
12369
12376
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
12370
12377
  updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
@@ -12393,6 +12400,14 @@ var init_agents = __esm({
12393
12400
  personality: text("personality").notNull(),
12394
12401
  model: text("model").default("claude-sonnet-4-6-20260217"),
12395
12402
  llmKeyId: uuid("llm_key_id").references(() => entityLlmKeys.id, { onDelete: "set null" }),
12403
+ // Ordered LLM-key fallback chain (Guard 2). When the primary key
12404
+ // (llmKeyId) exhausts retries / times out / hits quota mid-job, the runner
12405
+ // fails over to these in order; all-down fails loud (`all_providers_failed`).
12406
+ // Empty = no failover (default). Each link is a (keyId, model) pair so a
12407
+ // fallback runs on a CHOSEN model (empty model ⇒ that provider's catalog
12408
+ // default). FK integrity is enforced in the app layer; a deleted key is
12409
+ // skipped at resolution time.
12410
+ fallbackChain: jsonb("fallback_chain").$type().default(sql`'[]'::jsonb`),
12396
12411
  active: boolean("active").default(true),
12397
12412
  isDefault: boolean("is_default").default(false),
12398
12413
  role: text("role").default("agent"),
@@ -0,0 +1,6 @@
1
+ -- Guard 2 (provider failover): ordered LLM-key fallback chain per agent.
2
+ -- When the agent's primary key (llm_key_id) exhausts retries / times out /
3
+ -- hits quota mid-job, the runner fails over to these keys in order; all-down
4
+ -- fails loud (`all_providers_failed`). Empty default = no failover.
5
+ ALTER TABLE "agents"
6
+ ADD COLUMN IF NOT EXISTS "fallback_llm_key_ids" uuid[] DEFAULT '{}'::uuid[];
@@ -0,0 +1,7 @@
1
+ -- T2 (per-model LLM capabilities): cache a key's tool-calling capability so the
2
+ -- runtime can pick the right tool_choice WITHOUT a failed first call. NULL =
3
+ -- unknown (runtime assumes supported; the tool_choice floor catches a wrong
4
+ -- guess). Set from the model catalog or the live "Test" capability probe.
5
+ ALTER TABLE "entity_llm_keys"
6
+ ADD COLUMN IF NOT EXISTS "supports_tools" boolean,
7
+ ADD COLUMN IF NOT EXISTS "supports_forced_tool_choice" boolean;
@@ -0,0 +1,9 @@
1
+ -- Simplify LLM providers: a key is now JUST credentials (provider + API key +
2
+ -- base URL + active). The model is chosen per-AGENT, and tool capability is
3
+ -- read from the code model catalog at runtime — so these per-key columns are
4
+ -- dead weight. Drop them. (0030 fallback_llm_key_ids stays — failover is still a
5
+ -- per-key concern.)
6
+ ALTER TABLE "entity_llm_keys"
7
+ DROP COLUMN IF EXISTS "default_model",
8
+ DROP COLUMN IF EXISTS "supports_tools",
9
+ DROP COLUMN IF EXISTS "supports_forced_tool_choice";
@@ -0,0 +1,21 @@
1
+ -- Per-fallback model selection (Guard 2). The failover chain now stores
2
+ -- (keyId, model) pairs instead of bare key ids, so each fallback runs on a
3
+ -- CHOSEN model rather than its provider's catalog default. Backfill existing
4
+ -- id arrays with an empty model ('' ⇒ runtime uses the catalog default), then
5
+ -- drop the old column.
6
+ ALTER TABLE "agents"
7
+ ADD COLUMN IF NOT EXISTS "fallback_chain" jsonb DEFAULT '[]'::jsonb;
8
+
9
+ UPDATE "agents"
10
+ SET "fallback_chain" = COALESCE(
11
+ (
12
+ SELECT jsonb_agg(jsonb_build_object('keyId', kid::text, 'model', ''))
13
+ FROM unnest("fallback_llm_key_ids") AS kid
14
+ ),
15
+ '[]'::jsonb
16
+ )
17
+ WHERE "fallback_llm_key_ids" IS NOT NULL
18
+ AND array_length("fallback_llm_key_ids", 1) > 0;
19
+
20
+ ALTER TABLE "agents"
21
+ DROP COLUMN IF EXISTS "fallback_llm_key_ids";
@@ -211,6 +211,34 @@
211
211
  "when": 1780900000000,
212
212
  "tag": "0029_backfill_root_agent",
213
213
  "breakpoints": true
214
+ },
215
+ {
216
+ "idx": 30,
217
+ "version": "7",
218
+ "when": 1781000000000,
219
+ "tag": "0030_agent_fallback_llm_keys",
220
+ "breakpoints": true
221
+ },
222
+ {
223
+ "idx": 31,
224
+ "version": "7",
225
+ "when": 1781100000000,
226
+ "tag": "0031_llm_key_capabilities",
227
+ "breakpoints": true
228
+ },
229
+ {
230
+ "idx": 32,
231
+ "version": "7",
232
+ "when": 1781200000000,
233
+ "tag": "0032_drop_llm_key_model_caps",
234
+ "breakpoints": true
235
+ },
236
+ {
237
+ "idx": 33,
238
+ "version": "7",
239
+ "when": 1781300000000,
240
+ "tag": "0033_agent_fallback_chain",
241
+ "breakpoints": true
214
242
  }
215
243
  ]
216
244
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodal-agents",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "Local-first AI agent platform with a web dashboard — install in one command.",
5
5
  "license": "MIT",
6
6
  "repository": {