nexus-ai-pro 2.0.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (282) hide show
  1. package/API_STABILITY.md +65 -0
  2. package/CHANGELOG.md +267 -1
  3. package/MIGRATING.md +1 -1
  4. package/README.md +17 -8
  5. package/dist/agent/create-agent.d.ts +9 -1
  6. package/dist/agent/create-agent.js +40 -1
  7. package/dist/cache/memory-cache.d.ts +8 -0
  8. package/dist/cache/memory-cache.js +9 -0
  9. package/dist/cache/semantic-cache.d.ts +6 -3
  10. package/dist/cache/semantic-cache.js +5 -2
  11. package/dist/cli/args.js +3 -0
  12. package/dist/cli/db.d.ts +16 -4
  13. package/dist/cli/db.js +169 -22
  14. package/dist/cli/doctor.d.ts +17 -0
  15. package/dist/cli/doctor.js +53 -0
  16. package/dist/cli/graph.d.ts +9 -0
  17. package/dist/cli/graph.js +47 -0
  18. package/dist/cli.js +21 -2
  19. package/dist/core/lifecycle.js +11 -6
  20. package/dist/core/nexus.d.ts +12 -2
  21. package/dist/core/nexus.js +106 -23
  22. package/dist/doctor/index.d.ts +90 -0
  23. package/dist/doctor/index.js +237 -0
  24. package/dist/evals/judge.d.ts +11 -0
  25. package/dist/evals/judge.js +33 -0
  26. package/dist/evaluate/index.d.ts +1 -0
  27. package/dist/evaluate/index.js +1 -0
  28. package/dist/evaluate/provenance.d.ts +22 -0
  29. package/dist/evaluate/provenance.js +10 -0
  30. package/dist/evaluate/run.js +20 -4
  31. package/dist/graph/durability.d.ts +27 -0
  32. package/dist/graph/durability.js +66 -0
  33. package/dist/graph/errors.d.ts +30 -4
  34. package/dist/graph/errors.js +18 -2
  35. package/dist/graph/event-stream.d.ts +75 -0
  36. package/dist/graph/event-stream.js +289 -0
  37. package/dist/graph/functional.d.ts +80 -7
  38. package/dist/graph/functional.js +138 -35
  39. package/dist/graph/graph.d.ts +56 -8
  40. package/dist/graph/graph.js +331 -49
  41. package/dist/graph/index.d.ts +4 -2
  42. package/dist/graph/index.js +3 -1
  43. package/dist/graph/lint.d.ts +57 -0
  44. package/dist/graph/lint.js +141 -0
  45. package/dist/graph/run-control.d.ts +28 -0
  46. package/dist/graph/run-control.js +26 -0
  47. package/dist/images/evals.js +4 -2
  48. package/dist/operations/adapters.d.ts +52 -4
  49. package/dist/operations/adapters.js +194 -7
  50. package/dist/operations/runner.js +14 -15
  51. package/dist/operations/stats.js +8 -1
  52. package/dist/operations/store.d.ts +5 -1
  53. package/dist/operations/store.js +7 -0
  54. package/dist/ops/circuit-breaker.d.ts +52 -0
  55. package/dist/ops/circuit-breaker.js +4 -0
  56. package/dist/ops/circuit-store.d.ts +13 -2
  57. package/dist/ops/circuit-store.js +68 -0
  58. package/dist/ops/circuit-sync.d.ts +17 -0
  59. package/dist/ops/circuit-sync.js +86 -0
  60. package/dist/ops/health.d.ts +24 -0
  61. package/dist/ops/health.js +38 -10
  62. package/dist/ops/index.d.ts +1 -1
  63. package/dist/ops/index.js +1 -1
  64. package/dist/ops/rate-limit-adapters.d.ts +23 -0
  65. package/dist/ops/rate-limit-adapters.js +34 -0
  66. package/dist/ops/rate-limiter.d.ts +14 -2
  67. package/dist/ops/rate-limiter.js +31 -0
  68. package/dist/postgres/circuits.d.ts +21 -4
  69. package/dist/postgres/circuits.js +65 -4
  70. package/dist/postgres/client.d.ts +0 -2
  71. package/dist/postgres/client.js +0 -4
  72. package/dist/postgres/evaluate.d.ts +16 -4
  73. package/dist/postgres/evaluate.js +17 -5
  74. package/dist/postgres/index.d.ts +32 -11
  75. package/dist/postgres/index.js +65 -31
  76. package/dist/postgres/migrations.d.ts +60 -0
  77. package/dist/postgres/migrations.js +121 -0
  78. package/dist/postgres/operations.d.ts +14 -3
  79. package/dist/postgres/operations.js +45 -15
  80. package/dist/postgres/prompts.d.ts +11 -2
  81. package/dist/postgres/prompts.js +14 -3
  82. package/dist/postgres/rollups.d.ts +35 -0
  83. package/dist/postgres/rollups.js +129 -0
  84. package/dist/postgres/store.d.ts +11 -2
  85. package/dist/postgres/store.js +14 -3
  86. package/dist/postgres/traces.d.ts +13 -3
  87. package/dist/postgres/traces.js +25 -3
  88. package/dist/postgres/vectors.d.ts +11 -2
  89. package/dist/postgres/vectors.js +14 -3
  90. package/dist/router/auto-router.d.ts +34 -7
  91. package/dist/router/auto-router.js +201 -68
  92. package/dist/server/assistant.d.ts +44 -0
  93. package/dist/server/assistant.js +39 -0
  94. package/dist/server/errors.d.ts +12 -0
  95. package/dist/server/errors.js +7 -0
  96. package/dist/server/index.d.ts +1 -1
  97. package/dist/server/index.js +1 -1
  98. package/dist/server/runs.d.ts +28 -1
  99. package/dist/server/runs.js +110 -14
  100. package/dist/sqlite/index.d.ts +16 -3
  101. package/dist/sqlite/index.js +16 -3
  102. package/dist/sqlite/migrations.d.ts +26 -0
  103. package/dist/sqlite/migrations.js +63 -0
  104. package/dist/sqlite/operations.d.ts +14 -3
  105. package/dist/sqlite/operations.js +42 -15
  106. package/dist/sqlite/store.d.ts +9 -2
  107. package/dist/sqlite/store.js +13 -3
  108. package/dist/sqlite/vectors.d.ts +11 -2
  109. package/dist/sqlite/vectors.js +13 -2
  110. package/dist/store/tenant.d.ts +12 -0
  111. package/dist/store/tenant.js +29 -0
  112. package/dist/tenancy/index.d.ts +113 -0
  113. package/dist/tenancy/index.js +233 -0
  114. package/dist/testing/record.d.ts +42 -0
  115. package/dist/testing/record.js +57 -5
  116. package/dist/tracing/index.d.ts +3 -3
  117. package/dist/tracing/index.js +2 -2
  118. package/dist/tracing/instrument.js +30 -0
  119. package/dist/tracing/otlp.d.ts +91 -0
  120. package/dist/tracing/otlp.js +124 -0
  121. package/dist/tracing/rollups.d.ts +100 -0
  122. package/dist/tracing/rollups.js +139 -0
  123. package/dist/tracing/stores.d.ts +17 -0
  124. package/dist/tracing/stores.js +23 -0
  125. package/dist/tracing/tracer.d.ts +50 -4
  126. package/dist/tracing/tracer.js +85 -6
  127. package/dist/types/config.d.ts +33 -2
  128. package/dist/types/evaluate.d.ts +37 -0
  129. package/dist/types/graph.d.ts +354 -9
  130. package/dist/types/graph.js +3 -1
  131. package/dist/types/messages.d.ts +5 -0
  132. package/dist/types/operations.d.ts +22 -2
  133. package/dist/types/response.d.ts +20 -0
  134. package/dist/types/server.d.ts +18 -0
  135. package/dist/types/tracing.d.ts +11 -0
  136. package/dist/utils/schema-migrations.d.ts +97 -0
  137. package/dist/utils/schema-migrations.js +91 -0
  138. package/dist/utils/signals.d.ts +17 -0
  139. package/dist/utils/signals.js +26 -0
  140. package/dist/utils/tenant.d.ts +14 -0
  141. package/dist/utils/tenant.js +16 -0
  142. package/dist/version.d.ts +5 -0
  143. package/dist/version.js +1 -0
  144. package/dist-cjs/agent/create-agent.d.ts +9 -1
  145. package/dist-cjs/agent/create-agent.js +40 -1
  146. package/dist-cjs/cache/memory-cache.d.ts +8 -0
  147. package/dist-cjs/cache/memory-cache.js +9 -0
  148. package/dist-cjs/cache/semantic-cache.d.ts +6 -3
  149. package/dist-cjs/cache/semantic-cache.js +5 -2
  150. package/dist-cjs/cli/args.js +3 -0
  151. package/dist-cjs/cli/db.d.ts +16 -4
  152. package/dist-cjs/cli/db.js +200 -20
  153. package/dist-cjs/cli/doctor.d.ts +17 -0
  154. package/dist-cjs/cli/doctor.js +56 -0
  155. package/dist-cjs/cli/graph.d.ts +9 -0
  156. package/dist-cjs/cli/graph.js +50 -0
  157. package/dist-cjs/core/lifecycle.js +11 -6
  158. package/dist-cjs/core/nexus.d.ts +12 -2
  159. package/dist-cjs/core/nexus.js +106 -23
  160. package/dist-cjs/doctor/index.d.ts +90 -0
  161. package/dist-cjs/doctor/index.js +276 -0
  162. package/dist-cjs/evals/judge.d.ts +11 -0
  163. package/dist-cjs/evals/judge.js +33 -0
  164. package/dist-cjs/evaluate/index.d.ts +1 -0
  165. package/dist-cjs/evaluate/index.js +3 -1
  166. package/dist-cjs/evaluate/provenance.d.ts +22 -0
  167. package/dist-cjs/evaluate/provenance.js +14 -0
  168. package/dist-cjs/evaluate/run.js +20 -4
  169. package/dist-cjs/graph/durability.d.ts +27 -0
  170. package/dist-cjs/graph/durability.js +70 -0
  171. package/dist-cjs/graph/errors.d.ts +30 -4
  172. package/dist-cjs/graph/errors.js +20 -3
  173. package/dist-cjs/graph/event-stream.d.ts +75 -0
  174. package/dist-cjs/graph/event-stream.js +295 -0
  175. package/dist-cjs/graph/functional.d.ts +80 -7
  176. package/dist-cjs/graph/functional.js +137 -34
  177. package/dist-cjs/graph/graph.d.ts +56 -8
  178. package/dist-cjs/graph/graph.js +330 -48
  179. package/dist-cjs/graph/index.d.ts +4 -2
  180. package/dist-cjs/graph/index.js +7 -1
  181. package/dist-cjs/graph/lint.d.ts +57 -0
  182. package/dist-cjs/graph/lint.js +144 -0
  183. package/dist-cjs/graph/run-control.d.ts +28 -0
  184. package/dist-cjs/graph/run-control.js +30 -0
  185. package/dist-cjs/images/evals.js +4 -2
  186. package/dist-cjs/operations/adapters.d.ts +52 -4
  187. package/dist-cjs/operations/adapters.js +193 -6
  188. package/dist-cjs/operations/runner.js +14 -15
  189. package/dist-cjs/operations/stats.js +8 -1
  190. package/dist-cjs/operations/store.d.ts +5 -1
  191. package/dist-cjs/operations/store.js +7 -0
  192. package/dist-cjs/ops/circuit-breaker.d.ts +52 -0
  193. package/dist-cjs/ops/circuit-breaker.js +4 -0
  194. package/dist-cjs/ops/circuit-store.d.ts +13 -2
  195. package/dist-cjs/ops/circuit-store.js +68 -0
  196. package/dist-cjs/ops/circuit-sync.d.ts +17 -0
  197. package/dist-cjs/ops/circuit-sync.js +87 -0
  198. package/dist-cjs/ops/health.d.ts +24 -0
  199. package/dist-cjs/ops/health.js +38 -10
  200. package/dist-cjs/ops/index.d.ts +1 -1
  201. package/dist-cjs/ops/rate-limit-adapters.d.ts +23 -0
  202. package/dist-cjs/ops/rate-limit-adapters.js +35 -1
  203. package/dist-cjs/ops/rate-limiter.d.ts +14 -2
  204. package/dist-cjs/ops/rate-limiter.js +32 -0
  205. package/dist-cjs/postgres/circuits.d.ts +21 -4
  206. package/dist-cjs/postgres/circuits.js +98 -3
  207. package/dist-cjs/postgres/client.d.ts +0 -2
  208. package/dist-cjs/postgres/client.js +0 -5
  209. package/dist-cjs/postgres/evaluate.d.ts +16 -4
  210. package/dist-cjs/postgres/evaluate.js +50 -4
  211. package/dist-cjs/postgres/index.d.ts +32 -11
  212. package/dist-cjs/postgres/index.js +69 -20
  213. package/dist-cjs/postgres/migrations.d.ts +60 -0
  214. package/dist-cjs/postgres/migrations.js +129 -0
  215. package/dist-cjs/postgres/operations.d.ts +14 -3
  216. package/dist-cjs/postgres/operations.js +78 -14
  217. package/dist-cjs/postgres/prompts.d.ts +11 -2
  218. package/dist-cjs/postgres/prompts.js +47 -2
  219. package/dist-cjs/postgres/rollups.d.ts +35 -0
  220. package/dist-cjs/postgres/rollups.js +168 -0
  221. package/dist-cjs/postgres/store.d.ts +11 -2
  222. package/dist-cjs/postgres/store.js +47 -2
  223. package/dist-cjs/postgres/traces.d.ts +13 -3
  224. package/dist-cjs/postgres/traces.js +58 -2
  225. package/dist-cjs/postgres/vectors.d.ts +11 -2
  226. package/dist-cjs/postgres/vectors.js +47 -2
  227. package/dist-cjs/router/auto-router.d.ts +34 -7
  228. package/dist-cjs/router/auto-router.js +201 -68
  229. package/dist-cjs/server/assistant.d.ts +44 -0
  230. package/dist-cjs/server/assistant.js +39 -0
  231. package/dist-cjs/server/errors.d.ts +12 -0
  232. package/dist-cjs/server/errors.js +9 -1
  233. package/dist-cjs/server/index.d.ts +1 -1
  234. package/dist-cjs/server/index.js +2 -1
  235. package/dist-cjs/server/runs.d.ts +28 -1
  236. package/dist-cjs/server/runs.js +109 -13
  237. package/dist-cjs/sqlite/index.d.ts +16 -3
  238. package/dist-cjs/sqlite/index.js +31 -10
  239. package/dist-cjs/sqlite/migrations.d.ts +26 -0
  240. package/dist-cjs/sqlite/migrations.js +71 -0
  241. package/dist-cjs/sqlite/operations.d.ts +14 -3
  242. package/dist-cjs/sqlite/operations.js +76 -15
  243. package/dist-cjs/sqlite/store.d.ts +9 -2
  244. package/dist-cjs/sqlite/store.js +47 -3
  245. package/dist-cjs/sqlite/vectors.d.ts +11 -2
  246. package/dist-cjs/sqlite/vectors.js +47 -2
  247. package/dist-cjs/store/tenant.d.ts +12 -0
  248. package/dist-cjs/store/tenant.js +33 -0
  249. package/dist-cjs/tenancy/index.d.ts +113 -0
  250. package/dist-cjs/tenancy/index.js +250 -0
  251. package/dist-cjs/testing/record.d.ts +42 -0
  252. package/dist-cjs/testing/record.js +57 -5
  253. package/dist-cjs/tracing/index.d.ts +3 -3
  254. package/dist-cjs/tracing/index.js +4 -1
  255. package/dist-cjs/tracing/instrument.js +30 -0
  256. package/dist-cjs/tracing/otlp.d.ts +91 -0
  257. package/dist-cjs/tracing/otlp.js +129 -0
  258. package/dist-cjs/tracing/rollups.d.ts +100 -0
  259. package/dist-cjs/tracing/rollups.js +147 -0
  260. package/dist-cjs/tracing/stores.d.ts +17 -0
  261. package/dist-cjs/tracing/stores.js +24 -0
  262. package/dist-cjs/tracing/tracer.d.ts +50 -4
  263. package/dist-cjs/tracing/tracer.js +87 -6
  264. package/dist-cjs/types/config.d.ts +33 -2
  265. package/dist-cjs/types/evaluate.d.ts +37 -0
  266. package/dist-cjs/types/graph.d.ts +354 -9
  267. package/dist-cjs/types/graph.js +3 -1
  268. package/dist-cjs/types/messages.d.ts +5 -0
  269. package/dist-cjs/types/operations.d.ts +22 -2
  270. package/dist-cjs/types/response.d.ts +20 -0
  271. package/dist-cjs/types/server.d.ts +18 -0
  272. package/dist-cjs/types/tracing.d.ts +11 -0
  273. package/dist-cjs/utils/schema-migrations.d.ts +97 -0
  274. package/dist-cjs/utils/schema-migrations.js +102 -0
  275. package/dist-cjs/utils/signals.d.ts +17 -0
  276. package/dist-cjs/utils/signals.js +29 -0
  277. package/dist-cjs/utils/tenant.d.ts +14 -0
  278. package/dist-cjs/utils/tenant.js +22 -0
  279. package/dist-cjs/version.d.ts +5 -0
  280. package/dist-cjs/version.js +4 -0
  281. package/llms.txt +4 -3
  282. package/package.json +110 -2
package/API_STABILITY.md CHANGED
@@ -41,6 +41,71 @@ Both are covered by this policy:
41
41
  Deep imports into `dist`, `dist-cjs`, or `src` are not supported. Import only from `nexus-ai-pro` or
42
42
  one of its explicit subpaths.
43
43
 
44
+ ## 2.2 stage (2.2.0)
45
+
46
+ Everything 2.2 adds is stable from the start and additive: no default changes meaning.
47
+
48
+ - versioned migrations: `SchemaMigration`, `AppliedMigration`, `MigrationStatus`, `MigrationResult`,
49
+ `SchemaMigrationError`, `migrationChecksum()`, `applyPostgresMigrations()`,
50
+ `postgresMigrationStatus()`, `postgresMigrations()`, `PostgresMigrateOptions`, every adapter's
51
+ `…Migrations()`, and the SQLite twins (`nexus-ai-pro/postgres`, `nexus-ai-pro/postgres/migrations`,
52
+ `nexus-ai-pro/sqlite`, `nexus-ai-pro/sqlite/migrations`). A migration, once released, never
53
+ changes; later releases add versions. `migrate()` now resolves to a `MigrationResult` instead of
54
+ nothing;
55
+ - `nexus db status` and `nexus db migrate`, and `nexus db sql --record`;
56
+ - `RedisOperationStoreOptions.index` and `reindex()`;
57
+ - `CircuitBreakerConfig.shareObservations`, `CircuitStateStore.observe()`, `CircuitObservation`,
58
+ `CircuitWindow`, and `CircuitSnapshot.shared`;
59
+ - `PostgresRollupStore` (`nexus-ai-pro/postgres/rollups`);
60
+ - the tenant model (`nexus-ai-pro/tenancy`): every `tenant…()` view, `tenantScope()`,
61
+ `TenantAssetStore`, `TenantStores`, `assertTenantId()`, `TenantIdError`, and `TENANT_NAMESPACE`.
62
+ The prefix a view stores names under (`tenant/<id>/`) is part of the contract and does not change
63
+ within 2.x; `GraphRunOptions.tenantId`, `NodeContext.tenantId`, the `tenantId` options of `state()`,
64
+ `history()`, `fork()`, and `updateState()`, `OperationRecord.tenantId`,
65
+ `OperationSubmitOptions.tenantId`, `OperationStoreFilter.tenantId`, `RateLimitedRequest.tenantId`,
66
+ and `rateLimit.key: 'tenantId'`;
67
+ - `CacheConfig.namespace`, `CacheNamespace`, `CacheOutcome` and `ResponseMeta.cache`,
68
+ `CompletionRequest.responseCache`, `MemoryCache.lookup()`, and the `scope` of `SemanticCache`'s
69
+ `get()` and `set()`;
70
+ - `diagnose()` and its types (`nexus-ai-pro/doctor`), and `nexus doctor`. Checks may be added in a
71
+ minor release; a check's id and what makes it fail do not change within 2.x;
72
+ - workflow steps' `timeout`, `onError`, `StepTimeout`, `StepFailure`, `StepRecovery`,
73
+ `StepContext.heartbeat()`, `WorkflowOptions.stepDefaults`, `WorkflowRunOptions.control` and
74
+ `tenantId`, `WorkflowContext.tenantId`, and `WorkflowStepTimeoutError.kind`;
75
+ - `RunHandOffError`, `AssistantRunContext.saveProgress()` and `progress`.
76
+
77
+ Three races are closed rather than added: a busy thread is claimed atomically, and the in-memory and
78
+ Redis operation stores refuse a second record under a held idempotency key, as the SQL stores
79
+ already did. The semantic cache no longer matches across tenants, and a failing response cache no
80
+ longer fails a request.
81
+
82
+ ## 2.1 stage (2.1.0)
83
+
84
+ Everything 2.1 adds is stable from the start and additive: no default changes meaning.
85
+
86
+ - graph durability modes, `flush()`, `maxPendingWrites`, `onError` with `NodeFailure` and
87
+ `RecoveredFailure`, `nodeDefaults`, `NodeTimeout`, `context.heartbeat()`, `SendOptions`,
88
+ `RunControl` and `RunControlLike`, and `GraphDrainedError` (`nexus-ai-pro/graph`);
89
+ - `lintGraph()` and its finding types (`nexus-ai-pro/graph/lint`). New rules may be added in a minor
90
+ release; a rule's code and severity do not change within 2.x;
91
+ - `OperationContext.previousHeartbeat` and `OperationRecord.heartbeatDetails`;
92
+ - `AssistantRunContext.control`;
93
+ - the graph event stream: `events()`, `resumeEvents()`, `continueEvents()`, `GraphEventStream`, its
94
+ event and option types, `GraphStreamOverflowError`, `context.message()` and `context.tool()`, and
95
+ `subgraphEvents`. New projections may be added in a minor release;
96
+ - `TracerOptions.incremental` and `exporters`, `TraceExporter`, `traceparent` in and out, `flush()`,
97
+ `closeAbandonedRuns()`, `w3cTraceId()` and `w3cSpanId()` (`nexus-ai-pro/tracing`);
98
+ - `OtlpTraceExporter` and `runToOtlpSpan()` (`nexus-ai-pro/tracing/otlp`). Attributes may be added as
99
+ the GenAI conventions settle; the ones listed in the tracing guide stay;
100
+ - rollups (`nexus-ai-pro/tracing/rollups`), timed replay (`timing`, `pace`), evaluator provenance
101
+ (`withProvenance()`, `Experiment.evaluators` and `framework`, `LLMJudge.asEvaluator()`),
102
+ `createAgent({ streamTokens })`, and `graphAssistant(graph, { events })`;
103
+ - `rateLimit.algorithm` and `burst`, `RateLimitStore.gcra()`, `RateLimitDecision`, and `gcraDecide()`;
104
+ `health.observationTtlMs`, `ProviderHealthStatus`, a snapshot's `status` and `stale`, and
105
+ `checkProviders({ staleOnly })`.
106
+
107
+ The studio's `rollups` source follows the studio's experimental label.
108
+
44
109
  ## 2.0 stage (2.0.0)
45
110
 
46
111
  2.0 removes what 1.x deprecated and promotes the surfaces that have settled.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,270 @@ Notable changes to this project are recorded here. The format follows [Keep a Ch
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [2.2.0] - 2026-10-05
8
+
9
+ Production scale and safe upgrades. Every database schema is versioned, so an upgrade applies only
10
+ what changed, and a worker of the previous release keeps running beside the new one. A claim costs
11
+ the same however long the queue is, routing costs the same however many models are configured, a
12
+ circuit breaker counts every worker's failures, and work that several replicas race for is taken by
13
+ exactly one. One tenant model reaches every store, and a deployment can check itself. Nothing
14
+ changes until it is opted into, except races and leaks that are now closed.
15
+
16
+ ### Added
17
+
18
+ - **Versioned migrations.** Every Postgres and SQLite adapter's schema is a list of numbered
19
+ `SchemaMigration`s, recorded in `nexus_schema_migrations` with a checksum. `migrate()` applies what
20
+ is pending, under a lock that holds through a connection pool, and refuses a migration whose
21
+ statements changed after it ran (`SchemaMigrationError`). Version 1 is the schema 2.0 created, so an
22
+ existing database is recognized as it is. `applyPostgresMigrations()`, `postgresMigrationStatus()`,
23
+ `postgresMigrations()`, and their SQLite twins work on any list, from `nexus-ai-pro/postgres`,
24
+ `nexus-ai-pro/sqlite`, and the new `nexus-ai-pro/postgres/migrations` and
25
+ `nexus-ai-pro/sqlite/migrations`. `PostgresMigrateOptions.transaction` runs each migration in one
26
+ transaction on one connection, and `postgresMigration({ record: true })` records what the script
27
+ creates.
28
+ - **`nexus db status` and `nexus db migrate`**, through a module that exports your own client:
29
+ applied, pending, changed, and newer migrations, `--check` for a deploy step, and `--dry-run`.
30
+ - **Indexed dispatch.** Version 2 of the Postgres and SQLite operation schemas indexes queued work by
31
+ age, so a claim reads the oldest few rows instead of sorting the queue, and version 3 by tenant.
32
+ `RedisOperationStore` takes `index: true`: queued work in a sorted set by age and leased work in one
33
+ by lease expiry, kept in the same Lua call as the record, with `reindex()` to index what is already
34
+ stored. A claim reads the same handful of records with 100,000 operations queued as with 1,000.
35
+ - **A circuit breaker that counts across workers.** `shareObservations: true` adds each worker's
36
+ calls to a rolling window in the shared store, so nine failures over three workers count as nine
37
+ and the circuit opens on all of them. Checks stay synchronous; a worker learns the shared count on
38
+ its next sync. `CircuitStateStore.observe()` is implemented by the memory, Redis, and Postgres
39
+ stores, and `CircuitSnapshot.shared` reports the window.
40
+ - **`PostgresRollupStore`**, on the new `nexus-ai-pro/postgres/rollups`: hourly rollups beside the
41
+ traces, summed in one statement so two workers adding to one hour both count.
42
+ - **A routing index.** The auto-router plans once per configuration — candidates, allow and deny
43
+ lists, capability requirements as bitmasks, and strategy scores — and a request pays only for open
44
+ circuits and health. Over 1,000 models a warm route takes about 15 µs at p50 and 23 µs at p99.
45
+ - **One tenant model**, on the new `nexus-ai-pro/tenancy`: a tenant's view of every store —
46
+ `tenantStore()`, `tenantTraceStore()`, `tenantOperationStore()`, `tenantPromptStore()` (and with it
47
+ the context hub), `tenantDatasetStore()`, `tenantExperimentStore()`, `tenantVectorStore()`,
48
+ `tenantCache()`, `tenantAssetStore()`, `tenantRollupStore()` — each implementing the store's own
49
+ contract over any backend, and `tenantScope()` for all of them at once. Graph runs take a
50
+ `tenantId`: recorded on every checkpoint, read as `context.tenantId`, scoping `context.store`, and
51
+ refusing another tenant's resume, continue, fork, or edit. `graphAssistant()` passes the
52
+ principal's tenant. Operations carry `tenantId`, and `OperationStoreFilter.tenantId` narrows queue
53
+ reads. Model calls are traced with their `tenantId`, so rollups count model spend per tenant, and
54
+ `rateLimit.key: 'tenantId'` gives each tenant its own share of a client's rate limit.
55
+ - **Cache namespaces and outcomes.** `cache.namespace` keeps responses apart by tenant, environment,
56
+ model revision, and context version. Every response reports `meta.cache` — `hit`, `miss`, `stale`,
57
+ `bypass`, or `error`, with its layer and reason — and the request's trace records it.
58
+ `CompletionRequest.responseCache` skips the cache (`bypass`) or refreshes it (`refresh`).
59
+ - **`nexus doctor`**, and `diagnose()` on the new `nexus-ai-pro/doctor`: the Node.js version,
60
+ optional packages, provider credentials (by name only), database connectivity and pending or
61
+ changed migrations, Redis, lapsed leases and a stalled queue, the registry's age, stale provider
62
+ health, and process-local stores behind several replicas. It exits 1 on a failure, or with
63
+ `--strict` on a warning.
64
+ - **Runtime budgets.** `npm run bench:runtime`, part of the release gate, times fourteen paths — graph
65
+ creation, a 1,000-node compile, `Send`, checkpoints, routing at three registry sizes, a
66
+ 20-middleware agent, traces, the event stream, server event fan-out, claims, and cache lookups —
67
+ as ratios to a calibration loop, so a budget holds across machines, and checks that 100,000
68
+ checkpoints and 10,000 runs leave the heap where it was.
69
+ - **Workflow steps have what graph nodes have.** `timeout: { runMs, idleMs }` with
70
+ `context.heartbeat()`, an `onError` that decides once retries run out — recorded as `recovered`,
71
+ so a resume repeats neither the step nor its recovery — `stepDefaults`, a `control` that drains
72
+ between steps and ends in `GraphDrainedError`, and `tenantId`. A workflow the agent server serves
73
+ is handed off on a drain like a graph.
74
+ - **A function assistant hands its run off on a drain**: it checks `context.control?.draining`,
75
+ saves how far it got with `context.saveProgress()`, and throws `RunHandOffError`; the next worker
76
+ reads `context.progress` and carries on.
77
+
78
+ ### Changed
79
+
80
+ - `migrate()` on every Postgres and SQLite adapter resolves to a `MigrationResult` instead of
81
+ nothing, records what it applied, and loads the migration runner only when called.
82
+ - `nexus db sql` and `postgresMigration()` include the rollups table and the new indexes by
83
+ default; every statement stays idempotent.
84
+ - The root import grew by 11 KB, to 336 KB, with the routing plan, cache outcomes, and linked
85
+ signals; `nexus-ai-pro/graph` by 5 KB, to 78 KB, with tenant runs; and
86
+ `nexus-ai-pro/graph/functional` by 6 KB, to 32 KB, with step timeouts, recovery, drain, and
87
+ tenants.
88
+
89
+ ### Fixed
90
+
91
+ - **Two replicas could both start a run on one busy thread.** The busy check read and wrote
92
+ separately. A thread is now claimed through the operation store under a unique key with
93
+ compare-and-set, so exactly one run proceeds and the rest meet the busy policy. An `enqueue` start
94
+ still busy when `queueTimeoutMs` runs out is refused instead of running beside the other.
95
+ - **Two submissions under one idempotency key could both run** on the in-memory and Redis operation
96
+ stores, which overwrote the key's owner. Both now refuse a second record under a key another record
97
+ holds, as Postgres and SQLite do, and Redis checks and writes in one Lua call.
98
+ - A circuit breaker's calls recorded before its shared-state module finished loading were not
99
+ shared.
100
+ - **Security: the semantic response cache answered one tenant with another's response** when their
101
+ questions were alike, because it compared every entry regardless of tenant. A semantic match is
102
+ now kept to the request's tenant and cache namespace.
103
+ - A response cache that threw failed the request, and one that failed to store an answer failed it
104
+ after the provider had answered. Both are now reported in `meta.cache`, and the request goes on.
105
+ - The auto-router rebuilt the model registry for every candidate on every request: over 1,000 models
106
+ a request took most of a second.
107
+ - **Memory grew with every run that shared a signal.** `AbortSignal.any()`, which composed each task
108
+ attempt's signal, keeps a reference per call for as long as any source lives in Node 22, so a
109
+ server's or a worker's long-lived signal held about 5 KB per graph run, and every client operation
110
+ with a deadline held one too. Signals are now linked and released when their work ends; 10,000
111
+ runs leave the heap within 0.3 MB of where it started, down from 52 MB.
112
+
113
+ ### Proof
114
+
115
+ - A database created by the published 2.0.0 package upgrades with 2.2's migrations, on Postgres and
116
+ SQLite, and a published 2.1.0 worker and a 2.2 worker drain one queue together, nothing run twice.
117
+ - Ten workers race one thread, one idempotency key, one queue, one tenant's slots and budget, and one
118
+ circuit, on every shared store, and every invariant holds.
119
+ - A Redis claim reads the same number of records at 1,000 and 100,000 queued; Postgres and SQLite
120
+ plan a claim through the age index with no sort.
121
+ - The Redis stores' Lua runs for real in the tests, in a Lua VM over an in-memory Redis.
122
+ - The routing index decides exactly as the published 2.1.0 router on 300 random configurations, and
123
+ a warm route over 1,000 models stays under 100 µs at p50 and 1 ms at p99.
124
+ - A tenant-isolation suite runs against every storage adapter — memory, Redis, Postgres, SQLite, and
125
+ files — and fails on any cross-tenant get, list, search, delete, fork, resume, trace query, vector
126
+ search, or cache hit.
127
+ - `nexus doctor` reports each misconfiguration a fixture deployment is seeded with — an old Node.js,
128
+ no credentials, an old registry, pending migrations, an unreachable Redis, a lapsed lease and a
129
+ stalled queue, stale health, and memory stores behind three replicas — and passes it once fixed.
130
+ - A workflow and a function assistant are each drained on one worker and finished on the next, with
131
+ every step and every item run exactly once.
132
+
133
+ ### Not in this release, and why
134
+
135
+ - **The migration lock is a row with a lease, not a session advisory lock.** A session lock does not
136
+ hold through a connection pool or a serverless driver, where each statement may reach another
137
+ connection; a lease row does, everywhere. Without a `transaction` function, statements run one at a
138
+ time, which is safe because every bundled statement is idempotent.
139
+ - **The Redis dispatch index is opt-in**, since a 2.1 worker writes records without index entries;
140
+ `stats()` on Redis still reads every record.
141
+ - **Consecutive failures across workers are counted as failures since any worker's last success.**
142
+ Calls are reported in batches, so two workers' failures and successes in the same second may be
143
+ ordered differently than they happened.
144
+ - **Tenant views scope by prefix and stamp, not by a column**, so they work over every backend
145
+ unchanged; data written before a view was used is not the tenant's until it is copied under its
146
+ prefix.
147
+ - **Runtime budgets compare ratios to a calibration loop**, which catches a cliff, not a 10% drift;
148
+ the tolerance is three times the recorded ratio.
149
+
150
+ ## [2.1.0] - 2026-10-04
151
+
152
+ Fault-tolerant graphs and crash-safe observability. A graph now chooses when its checkpoints are
153
+ written, recovers from a node whose retries ran out, tells a hung task from a slow one, and stops
154
+ cleanly when its worker has to go. Traces survive the process that wrote them and reach any
155
+ OpenTelemetry backend, every consumer of a run reads one event stream, and dashboards read hourly
156
+ totals instead of every trace. Nothing changes until it is opted into: the defaults keep their 2.0
157
+ meaning.
158
+
159
+ Two pieces planned for 2.3 come early: a rate limiter with no window edge to burst across, and
160
+ provider health that expires.
161
+
162
+ ### Added
163
+
164
+ - **Durability modes.** `compile({ durability })` takes `sync` (the default), `async`, or `exit`.
165
+ `async` writes checkpoints in the background, in order, while the next superstep runs, and waits
166
+ for them before the run returns, pauses, fails, or drains. `maxPendingWrites` (8) bounds the writes
167
+ in flight. `exit` writes only where the run stops. `flush()` waits for every run's writes.
168
+ - **Recovery after retries.** A node's `onError` receives a `NodeFailure` once its retries run out
169
+ and returns an update or a `Command`, such as a route to a refund node. The decision is
170
+ checkpointed, as `recovered` on the next checkpoint, before anything it routes to runs, so a crash
171
+ after it resumes into the same path. `compile({ nodeDefaults })` sets retries, timeouts, and
172
+ `onError` for every node.
173
+ - **Run and idle timeouts.** `timeout: { runMs, idleMs }` caps an attempt and the time between signs
174
+ of progress. `context.heartbeat()`, `emit()`, and `report()` refresh the idle timer.
175
+ `GraphNodeTimeoutError.kind` says which limit fired. A `Send` can override the timeout of the task
176
+ it creates, and the override is checkpointed with the task.
177
+ - **Cooperative drain.** `RunControl`, passed as `control` to a run, stops it after its current
178
+ superstep with its checkpoint written, ending in `GraphDrainedError`; `continue()` resumes it
179
+ anywhere. The agent server drains its graph runs this way when a replica drains with a queue, so
180
+ they are handed off at once instead of after the drain timeout.
181
+ - **`lintGraph()`** on the new `nexus-ai-pro/graph/lint`, and `nexus graph lint`: static cycles,
182
+ retried nodes that are not `idempotent` or have no timeout, deferred durability with side effects,
183
+ an in-process checkpointer under a deployment, unbounded concurrency, and unchecked routes. Nodes
184
+ can declare `idempotent: true`, and `describe()` reports durability, the checkpointer, and each
185
+ node's retries, timeouts, and recovery.
186
+ - **Progress that survives a retry.** An operation's `context.heartbeat(details)` stores `details` as
187
+ `heartbeatDetails`, and the next attempt reads them as `context.previousHeartbeat`.
188
+ - A `task_failed` graph event, and a semantics table in the graph guide that says what each
189
+ execution primitive repeats after a crash.
190
+ - **Crash-safe traces.** `new Tracer({ incremental: true })` writes each run when it starts and when it
191
+ ends, so a crashed run stays in the store with every run beneath it that finished, and a live run
192
+ can be watched. Sampling is decided at the root's start, and an unsampled trace is still kept whole
193
+ when tail sampling wants it. `closeAbandonedRuns()` closes what a dead process left running, as a
194
+ `RunAbandoned` error. `tracer.flush()` waits for writes and exports.
195
+ - **Traces across processes.** `handle.traceparent()` and `StartRunOptions.traceparent` carry a trace
196
+ through a W3C `traceparent` header: an operation's `traceContext`, a remote call, any OpenTelemetry
197
+ service. `w3cTraceId()` and `w3cSpanId()` give the ids every process derives alike.
198
+ - **OpenTelemetry export**, on the new `nexus-ai-pro/tracing/otlp`. `TracerOptions.exporters` takes any
199
+ `TraceExporter`, and `OtlpTraceExporter` sends runs to a collector as OTLP/HTTP JSON with no SDK:
200
+ model calls as `chat <model>` client spans with the GenAI semantic conventions, tools, agents, and
201
+ graph nodes with theirs, and Nexus-only facts under `nexus.*`. `runToOtlpSpan()` is the conversion.
202
+ - **One event stream.** `graph.events()`, `resumeEvents()`, and `continueEvents()` run a graph as
203
+ typed events with projections — `values`, `updates`, `messages`, `tools`, `tasks`, `checkpoints`,
204
+ `custom` — read together or one at a time through `messages()`, `tools()`, `values()`, and
205
+ `updates()`, with `result` for the outcome. Nodes stream model output with `context.message()`
206
+ and report tool calls with `context.tool()`. Subgraph events come through with a `namespace` when
207
+ asked, and `GraphRunOptions.subgraphEvents` does the same for `onEvent`.
208
+ - **Bounded readers.** Every reader of a stream has its own buffer (`maxBuffered`, 1,000), and the run
209
+ waits for the slowest at each superstep. Within a step, `overflow` coalesces model chunks and state
210
+ snapshots (the default), drops the oldest or newest, or fails with `GraphStreamOverflowError`.
211
+ `streamStats()` reports the peak and what was merged or dropped.
212
+ - **Agents stream.** `createAgent()` reports its model's output and every tool call on the stream,
213
+ token by token with `streamTokens` and a client with `stream()`. `traceGraph()` records the tool
214
+ calls as `tool` runs inside their node. `graphAssistant(graph, { events })` records chosen
215
+ projections on each server run.
216
+ - **Rollups**, on the new `nexus-ai-pro/tracing/rollups`: `rollupTraceStore()` adds every finished run
217
+ to hourly rows per kind, model, provider, and tenant, in `MemoryRollupStore` or any `RollupStore`,
218
+ with latency buckets for `rollupPercentile()` and `sumRollups()` to total them. The studio's costs
219
+ view reads them when given `rollups`.
220
+ - **Timed replay.** `recordingFetch({ timing: true })` keeps each chunk of a response and when it came,
221
+ and `replayFetch({ pace })` replays it at once, at the original pace, scaled, or released chunk by
222
+ chunk by a function.
223
+ - **GCRA rate limiting.** `rateLimit.algorithm: 'gcra'` spreads calls at `maxRequests` per `windowMs`
224
+ with at most `burst` at once, so a burst across a window edge can no longer pass twice the limit.
225
+ Both stores decide it through `gcra()`; `RedisRateLimitStore` runs it as one Lua script on Redis's
226
+ clock. `gcraDecide()` is the arithmetic, for a store of your own.
227
+ - **Health that expires.** `health.observationTtlMs` makes a provider with no recent calls `unknown`,
228
+ routed to as one never seen instead of kept down by an old failure or up by an old success.
229
+ Snapshots carry a `status` (`healthy`, `degraded`, `unhealthy`, `unknown`) and `stale`, and
230
+ `ai.checkProviders({ staleOnly: true })` refreshes the unknown ones.
231
+ - **Evaluator provenance.** An experiment records each evaluator's position, name, the score keys it
232
+ produced, and what `withProvenance()` declared — version, judge model, prompt version, temperature,
233
+ and rubric — and the framework version that ran it. `LLMJudge.asEvaluator()` brings the judge to
234
+ `evaluate()`, declaring its own provenance.
235
+ - A weekly `live-providers` workflow runs real conformance against every provider with a key in the
236
+ repository's secrets, DeepSeek included, and opens an issue when one breaks.
237
+
238
+ ### Fixed
239
+
240
+ - **A manual operation heartbeat no longer strands the result.** `context.heartbeat()` wrote the
241
+ record behind the runner's back, so the runner's final write failed its sequence check and a
242
+ successful result was not saved. Heartbeats now go through the runner, one at a time.
243
+ - `continue()` on a finished thread left it marked `running` with an empty result. It now leaves it
244
+ completed and returns its state.
245
+ - A run started under a parent from another tracer, or another process, was never written: its trace
246
+ waited for a root that was not there. It is now written as its trace's root in this process.
247
+
248
+ ### Proof
249
+
250
+ - A crash-injection suite kills a run before, during, and after every checkpoint write and inside
251
+ nodes, before and after their side effects, in `sync` and `async` mode, on the memory, operation
252
+ store, SQLite, Postgres, and Redis checkpointers. A fresh process always reaches the state of an
253
+ uninterrupted run.
254
+ - A saga test: a charge exhausts its retries, `onError` routes to a refund, the process dies during
255
+ the refund, and the resumed run refunds without charging again.
256
+ - A property test generates 40 graphs with branches, loops, `Send`, retries, and interrupts, and
257
+ replays each from every checkpoint of its history to the same state.
258
+ - The graph benchmark runs 50 supersteps against an 8 ms store: `async` finishes in about half the
259
+ time of `sync`, in the same state, and CI fails if it stops beating it by a quarter.
260
+ - A long agent's process dies at its fourteenth step: a second process reads every finished step from
261
+ the same JSONL store, and `closeAbandonedRuns()` closes the rest.
262
+ - A server and a worker each export to a local OTLP collector, and the collector receives one trace,
263
+ the worker's run under the server's, with the GenAI attributes on the model call.
264
+ - A reader a hundred times slower than a node that streams 100,000 chunks never holds more than its
265
+ buffer, under every overflow policy, and coalescing loses no text.
266
+ - The studio's costs view answers over a month of a million runs, held as rollups, in under 200 ms,
267
+ reading the traces once.
268
+ - In the two milliseconds around a window's edge, fixed windows pass 199 calls of a 100-call limit and
269
+ GCRA passes 100. A provider down on Monday and idle since is `unknown`, and routed to, on Wednesday.
270
+
7
271
  ## [2.0.0] - 2026-10-02
8
272
 
9
273
  Consolidation. One lifecycle runs every call of every family, the root import holds the core client
@@ -1373,7 +1637,9 @@ shared policy vocabulary is in place for a future release that revisits this.
1373
1637
  - URL fetching rejects unsafe private-network targets and limits response reads.
1374
1638
  - CLI and audit findings no longer disclose detected secret values.
1375
1639
 
1376
- [Unreleased]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.0.0...HEAD
1640
+ [Unreleased]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.2.0...HEAD
1641
+ [2.2.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.1.0...v2.2.0
1642
+ [2.1.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.0.0...v2.1.0
1377
1643
  [2.0.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.25.0...v2.0.0
1378
1644
  [1.25.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.24.0...v1.25.0
1379
1645
  [1.24.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.23.0...v1.24.0
package/MIGRATING.md CHANGED
@@ -69,7 +69,7 @@ It cannot split a namespace import (`import * as nexus from 'nexus-ai-pro'`) or
69
69
  so it lists them. Import each name you use from its subpath instead. Every subpath, and what it costs,
70
70
  is in the [packaging guide](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/packaging.md).
71
71
 
72
- The root import now costs about 323 KB, down from 572 KB.
72
+ The root import now costs about 325 KB, down from 572 KB.
73
73
 
74
74
  ## Validators and Node types are optional
75
75
 
package/README.md CHANGED
@@ -8,12 +8,12 @@ team can share.
8
8
  Import only what you use. The root import is the client and nothing else, and every other capability
9
9
  has its own entry point with a size budget CI enforces; [the packaging guide](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/packaging.md)
10
10
  publishes what each one costs. The package has no required dependencies: a graph-only application
11
- loads 51 KB, and installs nothing but this package.
11
+ loads 78 KB, and installs nothing but this package.
12
12
 
13
13
  - NPM: https://www.npmjs.com/package/nexus-ai-pro
14
14
  - GitHub: https://github.com/mkhitar-abrahamyan/nexus-ai
15
15
  - Contributing, testing, and release procedure: [CONTRIBUTING.md](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/CONTRIBUTING.md)
16
- - Delivered work and what is planned next: [ROADMAP.md](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/ROADMAP.md)
16
+ - What is planned next: [ROADMAP.md](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/ROADMAP.md)
17
17
  - For AI assistants: [llms.txt](https://raw.githubusercontent.com/mkhitar-abrahamyan/nexus-ai/main/llms.txt) indexes every guide, and [llms-full.txt](https://raw.githubusercontent.com/mkhitar-abrahamyan/nexus-ai/main/llms-full.txt) holds them all in one file
18
18
 
19
19
  ## Install
@@ -158,6 +158,7 @@ from the doc comments. The guides live in the repository, so these links go to G
158
158
  | [Studio](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/studio.md) | A shared UI for traces, threads, approvals, experiments, prompts, deployments, costs, and health, as a separate package |
159
159
  | [Agent server](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/server.md) | Self-hosted HTTP server for assistants: threads, durable runs, resumable streams, cron, a worker queue, and metrics |
160
160
  | [Deployments](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/deployments.md) | Revisions and canaries with automatic rollback, autoscaling on Kubernetes and Helm, and per-tenant limits |
161
+ | [Tenancy](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/tenancy.md) | One tenant model: a tenant's view of every store, tenant graph runs, and the isolation suite |
161
162
  | [Workflows](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/workflows.md) | Ready-made chains and domain workflows |
162
163
  | [Command line](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/cli.md) | The `nexus` command |
163
164
  | [Packaging and install weight](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/packaging.md) | Every entry point and what it costs |
@@ -200,7 +201,8 @@ npm run check:release
200
201
 
201
202
  `check` runs formatting and lint gates, source/test/example type checks, the build, unit and mock
202
203
  conformance tests, coverage thresholds, package import checks, and an external type-consumer test.
203
- `check:release` additionally verifies the dry-run tarball and a clean packed-package install.
204
+ `check:release` additionally holds the scheduling and runtime budgets — speed against a calibration
205
+ loop, and memory by retention — and verifies the dry-run tarball and a clean packed-package install.
204
206
 
205
207
  `npm run docs:check` fails when any public export, or any public member of an exported class,
206
208
  interface, or enum, has no doc comment, and `npm run docs:guides` fails when a guide does not name
@@ -216,8 +218,8 @@ npm run test:vectors:live
216
218
 
217
219
  ## Roadmap
218
220
 
219
- See [ROADMAP.md](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/ROADMAP.md) for what has
220
- shipped and what is planned. It is a design proposal, not a compatibility promise; the guarantees
221
+ See [ROADMAP.md](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/ROADMAP.md) for what is
222
+ planned, and [CHANGELOG.md](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/CHANGELOG.md) for what has shipped. It is a design proposal, not a compatibility promise; the guarantees
221
223
  live in [API_STABILITY.md](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/API_STABILITY.md).
222
224
 
223
225
  Shipped so far:
@@ -243,12 +245,19 @@ Shipped so far:
243
245
  a codemod that moves your imports;
244
246
  - 2.0 consolidation: one lifecycle for every call of every family, a shared spend budget, a root
245
247
  import that holds only the client, no required dependencies, and a model registry checked against
246
- every provider.
248
+ every provider;
249
+ - 2.1 fault tolerance and crash-safe observability: checkpoints written on your terms, recovery after
250
+ retries, idle timeouts and a clean drain, traces that survive a crash and reach any OpenTelemetry
251
+ backend, one event stream, and dashboard rollups;
252
+ - 2.2 production scale and safe upgrades: versioned database migrations, queue claims and routing
253
+ that cost the same at any size, a circuit breaker that counts across workers, one tenant model
254
+ across every store, and `nexus doctor` to check a deployment before it serves traffic.
247
255
 
248
256
  Upgrading from 1.x: `npx nexus migrate src --write` moves your imports, and
249
257
  [MIGRATING.md](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/MIGRATING.md) covers the rest.
250
- Next, the image family leaves experimental once recorded live conformance passes on all three
251
- backends.
258
+ Next, 2.3 gives agents a real security boundary — capabilities, permissions, and a sandbox contract —
259
+ with a complete middleware catalog, and retrieval that works at production scale. The [roadmap](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/ROADMAP.md) lists the
260
+ releases after it.
252
261
 
253
262
  ## Before Production
254
263
 
@@ -2,7 +2,7 @@ import type { OperationLifecycleLike } from '../types/lifecycle.js';
2
2
  import { type CompiledGraph } from '../graph/graph.js';
3
3
  import type { GraphCheckpointer, NodeContext } from '../types/graph.js';
4
4
  import type { CompletionRequest, Message, ToolDefinition } from '../types/messages.js';
5
- import type { NexusResponse } from '../types/response.js';
5
+ import type { NexusResponse, StreamChunk } from '../types/response.js';
6
6
  import type { Store } from '../types/store.js';
7
7
  /**
8
8
  * An agent as a graph.
@@ -17,6 +17,8 @@ import type { Store } from '../types/store.js';
17
17
  export interface AgentModelClient {
18
18
  /** Runs one completion. */
19
19
  complete(request: CompletionRequest): Promise<NexusResponse>;
20
+ /** Streams one completion. Used when the agent is created with `streamTokens`. */
21
+ stream?(request: CompletionRequest): AsyncIterable<StreamChunk>;
20
22
  }
21
23
  /** A tool call the agent is about to make. */
22
24
  export interface AgentToolCall {
@@ -108,6 +110,12 @@ export interface CreateAgentOptions {
108
110
  * `ai.lifecycle`. Its model calls are operations of their own.
109
111
  */
110
112
  lifecycle?: OperationLifecycleLike;
113
+ /**
114
+ * Streams the model's output token by token onto the run's `messages` events, through the client's
115
+ * `stream()`. Without it, or with a client that cannot stream, each answer arrives as one message
116
+ * event once the model returns. Middleware sees the assembled response either way.
117
+ */
118
+ streamTokens?: boolean;
111
119
  }
112
120
  declare const agentChannels: () => {
113
121
  messages: import("../types/graph.js").Channel<Message[]>;
@@ -37,7 +37,12 @@ export function createAgent(options) {
37
37
  for (const item of middleware) {
38
38
  request = (await item.beforeModel?.({ request, state, store: options.store })) ?? request;
39
39
  }
40
- let response = await options.client.complete(request);
40
+ let response = options.streamTokens && options.client.stream
41
+ ? await streamed(options.client.stream(request), (chunk) => context.message(chunk))
42
+ : await options.client.complete(request);
43
+ if (!(options.streamTokens && options.client.stream) && response.content) {
44
+ context.message({ content: response.content });
45
+ }
41
46
  for (const item of middleware) {
42
47
  response = (await item.afterModel?.({ response, state, store: options.store })) ?? response;
43
48
  }
@@ -88,9 +93,13 @@ export function createAgent(options) {
88
93
  args = verdict.args;
89
94
  }
90
95
  const run = async () => executor.execute(description.name, args);
96
+ context.tool({ phase: 'start', id: call.id, name: description.name, args });
91
97
  const result = await middleware.reduceRight((next, item) => item.wrapToolCall
92
98
  ? () => Promise.resolve(item.wrapToolCall?.({ ...description, args }, next))
93
99
  : next, run)();
100
+ context.tool(result.ok
101
+ ? { phase: 'result', id: call.id, name: description.name, result: result.result }
102
+ : { phase: 'error', id: call.id, name: description.name, error: result.error ?? 'The tool failed' });
94
103
  return { messages: [toolMessage(call, result)] };
95
104
  }, { ends: ['model'] })
96
105
  .setEntry('model')
@@ -156,3 +165,33 @@ function parseArguments(raw) {
156
165
  return { ok: false, error: `Tool arguments are not valid JSON (${reason}); the tool was not run` };
157
166
  }
158
167
  }
168
+ async function streamed(chunks, onChunk) {
169
+ let content = '';
170
+ const toolCalls = [];
171
+ let meta = {};
172
+ for await (const chunk of chunks) {
173
+ if (chunk.type === 'text' && chunk.content) {
174
+ content += chunk.content;
175
+ onChunk({ kind: 'text', content: chunk.content });
176
+ }
177
+ else if (chunk.type === 'reasoning' && chunk.content) {
178
+ onChunk({ kind: 'reasoning', content: chunk.content });
179
+ }
180
+ else if (chunk.type === 'tool_call' && chunk.toolCall) {
181
+ toolCalls.push(chunk.toolCall);
182
+ }
183
+ else if (chunk.type === 'done') {
184
+ meta = chunk.meta ?? meta;
185
+ }
186
+ else if (chunk.type === 'error') {
187
+ throw new Error(chunk.error ?? 'The model stream failed');
188
+ }
189
+ }
190
+ return {
191
+ content,
192
+ role: 'assistant',
193
+ ...(toolCalls.length ? { toolCalls } : {}),
194
+ finishReason: toolCalls.length ? 'tool_calls' : 'stop',
195
+ meta: meta,
196
+ };
197
+ }
@@ -15,6 +15,14 @@ export declare class MemoryCache<T> {
15
15
  constructor(maxEntries?: number);
16
16
  /** Returns a value, or `undefined` for a miss or an expired entry. A hit counts as recent use. */
17
17
  get(key: string): T | undefined;
18
+ /**
19
+ * Like `get()`, also saying whether an entry was there but had expired, which a caller can report
20
+ * as stale rather than missing. The expired entry is removed either way.
21
+ */
22
+ lookup(key: string): {
23
+ value?: T;
24
+ stale: boolean;
25
+ };
18
26
  /** Stores a value. Defaults to 5 minutes. */
19
27
  set(key: string, value: T, ttlSeconds?: number): void;
20
28
  /** Removes every entry. */
@@ -16,6 +16,15 @@ export class MemoryCache {
16
16
  this.entries.set(key, entry);
17
17
  return entry.value;
18
18
  }
19
+ lookup(key) {
20
+ const entry = this.entries.get(key);
21
+ if (entry && entry.expiresAt < Date.now()) {
22
+ this.entries.delete(key);
23
+ return { stale: true };
24
+ }
25
+ const value = this.get(key);
26
+ return value === undefined ? { stale: false } : { value, stale: false };
27
+ }
19
28
  set(key, value, ttlSeconds = 300) {
20
29
  if (this.entries.size >= this.maxEntries) {
21
30
  const oldest = this.entries.keys().next().value;
@@ -25,10 +25,13 @@ export declare class SemanticCache {
25
25
  constructor(options?: SemanticCacheOptions);
26
26
  /**
27
27
  * The cached response for the most similar earlier request above the threshold, or `undefined`.
28
+ * Only entries stored under the same `scope` are compared: the client passes the request's tenant
29
+ * and cache namespace, so one tenant is never answered from another's entry however alike the
30
+ * questions are.
28
31
  */
29
- get(request: CompletionRequest): Promise<NexusResponse | undefined>;
30
- /** Caches a response under its request's embedding. */
31
- set(key: string, request: CompletionRequest, response: NexusResponse): Promise<void>;
32
+ get(request: CompletionRequest, scope?: string): Promise<NexusResponse | undefined>;
33
+ /** Caches a response under its request's embedding, in a `scope` lookups must match. */
34
+ set(key: string, request: CompletionRequest, response: NexusResponse, scope?: string): Promise<void>;
32
35
  /** Removes every entry. */
33
36
  clear(): void;
34
37
  }
@@ -7,7 +7,7 @@ export class SemanticCache {
7
7
  this.options = options;
8
8
  this.embed = options.embed || createHashEmbeddings;
9
9
  }
10
- async get(request) {
10
+ async get(request, scope = '') {
11
11
  if (!this.options.enabled)
12
12
  return undefined;
13
13
  const text = requestToSearchText(request);
@@ -17,6 +17,8 @@ export class SemanticCache {
17
17
  let best;
18
18
  this.entries = this.entries.filter((entry) => entry.expiresAt > now);
19
19
  for (const entry of this.entries) {
20
+ if (entry.scope !== scope)
21
+ continue;
20
22
  const score = cosineSimilarity(embedding, entry.embedding);
21
23
  if (score >= threshold && (!best || score > best.score)) {
22
24
  best = { entry, score };
@@ -37,7 +39,7 @@ export class SemanticCache {
37
39
  },
38
40
  };
39
41
  }
40
- async set(key, request, response) {
42
+ async set(key, request, response, scope = '') {
41
43
  if (!this.options.enabled)
42
44
  return;
43
45
  const text = requestToSearchText(request);
@@ -47,6 +49,7 @@ export class SemanticCache {
47
49
  this.entries.shift();
48
50
  this.entries.push({
49
51
  key,
52
+ scope,
50
53
  text,
51
54
  embedding,
52
55
  response,
package/dist/cli/args.js CHANGED
@@ -5,12 +5,15 @@ export const BOOLEAN_FLAGS = new Set([
5
5
  'allow-dataset-mismatch',
6
6
  'check',
7
7
  'densify',
8
+ 'dry-run',
8
9
  'fail',
9
10
  'fail-on-regression',
10
11
  'help',
11
12
  'json',
12
13
  'print',
14
+ 'record',
13
15
  'reveal-values',
16
+ 'strict',
14
17
  'write',
15
18
  ]);
16
19
  export function parseArgs(args) {