nexus-ai-pro 1.22.0 → 1.24.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 (143) hide show
  1. package/API_STABILITY.md +29 -2
  2. package/CHANGELOG.md +96 -1
  3. package/README.md +33 -11
  4. package/dist/cli/deploy.d.ts +15 -0
  5. package/dist/cli/deploy.js +116 -0
  6. package/dist/cli.js +12 -1
  7. package/dist/context-hub/diff.d.ts +32 -0
  8. package/dist/context-hub/diff.js +64 -0
  9. package/dist/context-hub/hub.d.ts +268 -0
  10. package/dist/context-hub/hub.js +313 -0
  11. package/dist/context-hub/index.d.ts +46 -0
  12. package/dist/context-hub/index.js +55 -0
  13. package/dist/evaluate/cache.d.ts +70 -0
  14. package/dist/evaluate/cache.js +73 -0
  15. package/dist/evaluate/index.d.ts +1 -0
  16. package/dist/evaluate/index.js +1 -0
  17. package/dist/evaluate/run.d.ts +13 -0
  18. package/dist/evaluate/run.js +40 -11
  19. package/dist/index.d.ts +2 -2
  20. package/dist/index.js +1 -1
  21. package/dist/insights/clusters.d.ts +98 -0
  22. package/dist/insights/clusters.js +153 -0
  23. package/dist/insights/index.d.ts +8 -0
  24. package/dist/insights/index.js +3 -0
  25. package/dist/insights/proposals.d.ts +229 -0
  26. package/dist/insights/proposals.js +241 -0
  27. package/dist/insights/regressions.d.ts +117 -0
  28. package/dist/insights/regressions.js +137 -0
  29. package/dist/operations/adapters.d.ts +5 -1
  30. package/dist/operations/adapters.js +11 -0
  31. package/dist/operations/errors.d.ts +18 -0
  32. package/dist/operations/errors.js +10 -0
  33. package/dist/operations/handle.d.ts +7 -0
  34. package/dist/operations/handle.js +6 -0
  35. package/dist/operations/index.d.ts +3 -3
  36. package/dist/operations/index.js +2 -2
  37. package/dist/operations/runner.d.ts +43 -1
  38. package/dist/operations/runner.js +102 -8
  39. package/dist/operations/stats.d.ts +19 -0
  40. package/dist/operations/stats.js +32 -0
  41. package/dist/operations/store.d.ts +6 -1
  42. package/dist/operations/store.js +17 -0
  43. package/dist/postgres/operations.d.ts +5 -1
  44. package/dist/postgres/operations.js +40 -0
  45. package/dist/prompts/evaluate.d.ts +3 -1
  46. package/dist/prompts/evaluate.js +4 -0
  47. package/dist/prompts/gates.d.ts +10 -1
  48. package/dist/prompts/gates.js +38 -37
  49. package/dist/security/index.d.ts +1 -0
  50. package/dist/security/index.js +1 -0
  51. package/dist/server/deployments.d.ts +257 -0
  52. package/dist/server/deployments.js +449 -0
  53. package/dist/server/errors.d.ts +20 -0
  54. package/dist/server/errors.js +18 -0
  55. package/dist/server/http.js +2 -1
  56. package/dist/server/index.d.ts +3 -3
  57. package/dist/server/index.js +2 -2
  58. package/dist/server/metrics.d.ts +24 -0
  59. package/dist/server/metrics.js +85 -0
  60. package/dist/server/runs.d.ts +107 -4
  61. package/dist/server/runs.js +410 -63
  62. package/dist/server/server.d.ts +37 -5
  63. package/dist/server/server.js +68 -5
  64. package/dist/server/state.d.ts +2 -0
  65. package/dist/server/state.js +1 -0
  66. package/dist/server/tenancy.d.ts +148 -0
  67. package/dist/server/tenancy.js +194 -0
  68. package/dist/sqlite/operations.d.ts +5 -1
  69. package/dist/sqlite/operations.js +38 -1
  70. package/dist/store/redis.d.ts +8 -4
  71. package/dist/types/evaluate.d.ts +10 -0
  72. package/dist/types/operations.d.ts +30 -0
  73. package/dist/types/server.d.ts +215 -0
  74. package/dist-cjs/cli/deploy.d.ts +15 -0
  75. package/dist-cjs/cli/deploy.js +119 -0
  76. package/dist-cjs/context-hub/diff.d.ts +32 -0
  77. package/dist-cjs/context-hub/diff.js +68 -0
  78. package/dist-cjs/context-hub/hub.d.ts +268 -0
  79. package/dist-cjs/context-hub/hub.js +323 -0
  80. package/dist-cjs/context-hub/index.d.ts +46 -0
  81. package/dist-cjs/context-hub/index.js +105 -0
  82. package/dist-cjs/evaluate/cache.d.ts +70 -0
  83. package/dist-cjs/evaluate/cache.js +113 -0
  84. package/dist-cjs/evaluate/index.d.ts +1 -0
  85. package/dist-cjs/evaluate/index.js +6 -1
  86. package/dist-cjs/evaluate/run.d.ts +13 -0
  87. package/dist-cjs/evaluate/run.js +40 -11
  88. package/dist-cjs/index.d.ts +2 -2
  89. package/dist-cjs/index.js +5 -4
  90. package/dist-cjs/insights/clusters.d.ts +98 -0
  91. package/dist-cjs/insights/clusters.js +159 -0
  92. package/dist-cjs/insights/index.d.ts +8 -0
  93. package/dist-cjs/insights/index.js +17 -0
  94. package/dist-cjs/insights/proposals.d.ts +229 -0
  95. package/dist-cjs/insights/proposals.js +282 -0
  96. package/dist-cjs/insights/regressions.d.ts +117 -0
  97. package/dist-cjs/insights/regressions.js +141 -0
  98. package/dist-cjs/operations/adapters.d.ts +5 -1
  99. package/dist-cjs/operations/adapters.js +11 -0
  100. package/dist-cjs/operations/errors.d.ts +18 -0
  101. package/dist-cjs/operations/errors.js +12 -1
  102. package/dist-cjs/operations/handle.d.ts +7 -0
  103. package/dist-cjs/operations/handle.js +6 -0
  104. package/dist-cjs/operations/index.d.ts +3 -3
  105. package/dist-cjs/operations/index.js +3 -1
  106. package/dist-cjs/operations/runner.d.ts +43 -1
  107. package/dist-cjs/operations/runner.js +101 -7
  108. package/dist-cjs/operations/stats.d.ts +19 -0
  109. package/dist-cjs/operations/stats.js +38 -0
  110. package/dist-cjs/operations/store.d.ts +6 -1
  111. package/dist-cjs/operations/store.js +19 -1
  112. package/dist-cjs/postgres/operations.d.ts +5 -1
  113. package/dist-cjs/postgres/operations.js +40 -0
  114. package/dist-cjs/prompts/evaluate.d.ts +3 -1
  115. package/dist-cjs/prompts/evaluate.js +4 -0
  116. package/dist-cjs/prompts/gates.d.ts +10 -1
  117. package/dist-cjs/prompts/gates.js +39 -37
  118. package/dist-cjs/security/index.d.ts +1 -0
  119. package/dist-cjs/security/index.js +4 -1
  120. package/dist-cjs/server/deployments.d.ts +257 -0
  121. package/dist-cjs/server/deployments.js +455 -0
  122. package/dist-cjs/server/errors.d.ts +20 -0
  123. package/dist-cjs/server/errors.js +20 -1
  124. package/dist-cjs/server/http.js +2 -1
  125. package/dist-cjs/server/index.d.ts +3 -3
  126. package/dist-cjs/server/index.js +2 -1
  127. package/dist-cjs/server/metrics.d.ts +24 -0
  128. package/dist-cjs/server/metrics.js +90 -0
  129. package/dist-cjs/server/runs.d.ts +107 -4
  130. package/dist-cjs/server/runs.js +416 -69
  131. package/dist-cjs/server/server.d.ts +37 -5
  132. package/dist-cjs/server/server.js +68 -5
  133. package/dist-cjs/server/state.d.ts +2 -0
  134. package/dist-cjs/server/state.js +2 -1
  135. package/dist-cjs/server/tenancy.d.ts +148 -0
  136. package/dist-cjs/server/tenancy.js +201 -0
  137. package/dist-cjs/sqlite/operations.d.ts +5 -1
  138. package/dist-cjs/sqlite/operations.js +37 -0
  139. package/dist-cjs/store/redis.d.ts +8 -4
  140. package/dist-cjs/types/evaluate.d.ts +10 -0
  141. package/dist-cjs/types/operations.d.ts +30 -0
  142. package/dist-cjs/types/server.d.ts +215 -0
  143. package/package.json +78 -5
package/API_STABILITY.md CHANGED
@@ -437,8 +437,9 @@ from the root.
437
437
  `ThreadRecord`, and `RunEvent` follow the 1.x rules. A minor release may add routes and fields.
438
438
  - Event ids are per run and strictly increasing, which is what `Last-Event-ID` depends on. A client
439
439
  that reconnects with an id never receives it again.
440
- - The server is not a security boundary on its own: authentication, authorization beyond tenancy and
441
- scopes, and rate limiting are the application's, through the hooks.
440
+ - The server is not a security boundary on its own: authentication, and authorization beyond tenancy
441
+ and scopes, are the application's, through the hooks. Per-tenant limits are opt-in, through
442
+ `tenants`.
442
443
  - Recovery repeats the step a run died in for graphs and workflows served by `graphAssistant()`, and
443
444
  the whole run for any other assistant, so the repeated part should be idempotent or the run left at a
444
445
  single attempt.
@@ -456,6 +457,32 @@ with nexus-ai-pro and declares the core versions it works with as a peer depende
456
457
  - The access rules are a guarantee: loopback binding by default, a token on every request, the token
457
458
  in a header for every change, and loopback hosts only unless others are allowed.
458
459
 
460
+ ## Deployment stage (1.24.0)
461
+
462
+ Deployments (`nexus-ai-pro/server/deployments`), tenant limits (`nexus-ai-pro/server/tenancy`), and the
463
+ server's worker queue are experimental in production readiness. Their contracts follow the 1.x rules.
464
+
465
+ - `bucket()` is stable across 1.x, so a thread keeps its place in a traffic split through an upgrade.
466
+ - The stored `DeploymentRecord` and `ReplicaReport` shapes may gain fields but not lose them, so
467
+ replicas on different minor releases share one store.
468
+ - The metric names on `/metrics` and the fields of `/scaling` follow the 1.x rules; a minor release may
469
+ add metrics and fields. Autoscaler configurations that read them keep working.
470
+ - `drain()` hands off only runs it could not finish; a hand-off never counts against the retry budget.
471
+ - The files under `deploy/` are templates in the repository, not part of the package, and may change in
472
+ any release.
473
+
474
+ ## Team platform stage (1.23.0)
475
+
476
+ The context hub (`nexus-ai-pro/context-hub`), insights (`nexus-ai-pro/insights`), and the studio's
477
+ accounts are experimental in production readiness. Their contracts follow the 1.x rules.
478
+
479
+ - A bundle's content version covers its name, description, prompts, instructions, tools, skills, and
480
+ config, and is stable across 1.x, so the same content is the same version in every release.
481
+ - The export format is `nexus-context-bundle`, format version 1; a later release reads it.
482
+ - A cached evaluation output is keyed by the fingerprint, the example's id and inputs, and the
483
+ repetition. The key's derivation is stable across 1.x, so a cache survives an upgrade.
484
+ - The studio's roles keep their order and meaning; new actions are assigned to an existing role.
485
+
459
486
  ## Retrieval breadth stage (1.22.0)
460
487
 
461
488
  The loaders (`nexus-ai-pro/loaders/*`), the Redis, Pinecone, Weaviate, Chroma, and SQLite vector
package/CHANGELOG.md CHANGED
@@ -4,6 +4,99 @@ Notable changes to this project are recorded here. The format follows [Keep a Ch
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [1.24.0] - 2026-09-30
8
+
9
+ Deployment at scale, self-managed. Running assistants as a service without a hosted platform:
10
+ revisions and canaries, a worker pool that scales on its queue, and limits per tenant.
11
+
12
+ ### Added
13
+
14
+ - **Revisions and canaries** on `nexus-ai-pro/server/deployments`. `Deployments.assistant()` serves
15
+ several revisions of an assistant from one image; the traffic split is recorded in the server's
16
+ state store and changed with `canary()`, `split()`, `promote()`, and `rollback()`, each kept in a
17
+ history with who and why, and guarded by an expected version. New threads and stateless runs are
18
+ split by a stable hash, a thread keeps its revision while that revision takes traffic, and every
19
+ run records its revision, share, reason, and the deployment version.
20
+ - **A canary guard**: `watchCanaries()` compares each canary with the live revision over the same
21
+ window, rolls it back when its error rate, latency, or cost regressed, and moves one that holds up
22
+ through its `steps` to promotion. A latency rise must be at least `minLatencyChangeMs`, 50 ms by
23
+ default, so noise on fast runs never rolls a canary back. `compareRuns()` on `nexus-ai-pro/insights`
24
+ is the comparison, lifted out of `detectRegressions()`.
25
+ - **A worker queue** in the agent server: with `queue`, a replica runs at most `concurrency` runs and
26
+ leaves the rest queued for the next free worker; `claim: false` makes an API replica. Replicas take
27
+ over lapsed runs every `recoverEveryMs`. `drain()` answers `/health` with `503`, stops claiming, and
28
+ hands runs still going after its timeout to another worker without using a retry.
29
+ - **Scaling signals**: `GET /scaling` as JSON and `GET /metrics` in Prometheus text — queued and running
30
+ runs across the deployment, their load, the oldest wait, lapsed leases, each replica's capacity, and
31
+ run counts and latency per revision. `metrics: { public: true }` serves them to in-cluster scrapers.
32
+ - **Kubernetes and Helm**: `deploy/app` is one image run as an API, a worker, or both, configured from
33
+ the environment; `deploy/kubernetes` has the manifests with a KEDA `ScaledObject` and an HPA
34
+ alternative; `deploy/helm/agent-server` is the chart. The clean-install test runs the template against
35
+ the packed package.
36
+ - **Tenant limits** on `nexus-ai-pro/server/tenancy`: `tenantLimits()` holds each tenant to its active
37
+ runs, its runs per window, and its spending per period, answered with `429` and `Retry-After`.
38
+ `MemoryTenantUsage`, and `RedisTenantUsage` with atomic scripts so replicas share one set of limits.
39
+ Assistants record spending with `context.recordCost()`; `GET /usage` reports it.
40
+ - **Replica health**: with `deployments`, each replica heartbeats its runs, capacity, drain state, and
41
+ revisions, readable from `GET /replicas`.
42
+ - **A deployments view** in the studio: traffic splits and history, each revision's runs, error rate,
43
+ and latency since the last change, replicas, the queue, and tenant usage, with canary, promote, and
44
+ rollback for admins.
45
+ - **`nexus deploy`**: `status`, `canary`, `promote`, and `rollback` against a running server, for the
46
+ pipeline that rolls out an image.
47
+ - Operation queues: `OperationRunner.enqueue()`, `claimQueued()`, and `release()`; `listQueued()` and
48
+ `stats()` on every operation store, and `operationStats()`.
49
+ - Run records gain `revision`, `startedAt`, `finishedAt`, `durationMs`, `cost`, `worker`, and `attempt`.
50
+
51
+ ### Changed
52
+
53
+ - `GET /health` is answered without authentication, so a load balancer's or Kubernetes' probe reaches
54
+ it.
55
+ - A worker never takes over an operation whose lease another worker still holds.
56
+ - `RedisStoreLikeClient` declares `set()` with its expiry and `del()` with one key, the shapes an
57
+ `ioredis` client satisfies as it is.
58
+ - The last nine guides are rewritten for readability.
59
+ - `nexus-ai-pro-studio` requires nexus-ai-pro 1.24 or newer.
60
+
61
+ ### Fixed
62
+
63
+ - The deployment templates run. The Dockerfile started a file the build never produced, and the
64
+ example server ignored `REDIS_URL`, so its replicas shared nothing.
65
+
66
+ ## [1.23.0] - 2026-09-30
67
+
68
+ The team engineering platform. The studio becomes something a team shares, and it starts finding
69
+ problems on its own.
70
+
71
+ ### Added
72
+
73
+ - **A shared studio.** Accounts through an authenticator — personal links, identity headers from a
74
+ signing-in proxy, or bearer tokens verified by your function, such as OIDC — with four roles
75
+ (viewer, reviewer, editor, admin), an audit log of every change and every refused attempt, comments
76
+ on runs, review items, proposals, threads, and bundles, per-person page tokens for changes, and
77
+ `Secure` cookies behind HTTPS. Local, token-only use stays the default. The CLI gains `--users` and
78
+ `--journal`, and a config module may export `options`.
79
+ - **The context hub** on `nexus-ai-pro/context-hub`: prompts, instructions, tool sets, skills, and
80
+ settings versioned together as bundles, committed by content, labelled, promoted through evaluation
81
+ gates, diffed, rolled back, and exported with their prompts to move between projects. Bundles live in
82
+ any prompt store. The studio shows them.
83
+ - **Insights** on `nexus-ai-pro/insights`: failing and slow runs clustered by error, trajectory, or
84
+ meaning; regressions between two time windows; and, opt-in, a proposed fix — a candidate prompt or
85
+ bundle version — evaluated against the dataset before it waits in the studio's inbox for a person
86
+ to promote, with an optional pull request through an injected client.
87
+ - **Evaluation caching**: `evaluate()` reuses a target's outputs when the example and the target's
88
+ fingerprint are unchanged, so a comparison re-runs only what changed; evaluators always run again.
89
+ `MemoryEvaluationCache`, `FileEvaluationCache`, and `fingerprintOf()` on `nexus-ai-pro/evaluate`;
90
+ `evaluatePrompt()` fingerprints the prompt version itself.
91
+
92
+ ### Changed
93
+
94
+ - The guides are rewritten for readability: each says what its feature is for before its options,
95
+ options and fields are in tables, and long sentences are split. Every export is still explained.
96
+ - `nexus-ai-pro-studio` requires nexus-ai-pro 1.23 or newer.
97
+ - The studio's development type paths map each entry point to the file it publishes, so studio tests
98
+ exercise what the package ships.
99
+
7
100
  ## [1.22.0] - 2026-09-29
8
101
 
9
102
  Retrieval and integration breadth. Getting documents in, finding the right ones, and reaching the
@@ -1118,7 +1211,9 @@ shared policy vocabulary is in place for a future release that revisits this.
1118
1211
  - URL fetching rejects unsafe private-network targets and limits response reads.
1119
1212
  - CLI and audit findings no longer disclose detected secret values.
1120
1213
 
1121
- [Unreleased]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.22.0...HEAD
1214
+ [Unreleased]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.24.0...HEAD
1215
+ [1.24.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.23.0...v1.24.0
1216
+ [1.23.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.22.0...v1.23.0
1122
1217
  [1.22.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.21.0...v1.22.0
1123
1218
  [1.21.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.20.0...v1.21.0
1124
1219
  [1.20.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v1.19.0...v1.20.0
package/README.md CHANGED
@@ -1,8 +1,9 @@
1
1
  # nexus-ai-pro
2
2
 
3
- The typed AI framework for TypeScript. One API for provider routing and failover, agent graphs that
4
- run branches in parallel and survive a restart, durable background operations, guardrails, cost
5
- control, images, voice, and evals.
3
+ The typed AI framework for TypeScript. One API for provider routing and failover, agent graphs and
4
+ workflows that run branches in parallel and survive a restart, retrieval over any vector store, MCP,
5
+ durable background operations, guardrails, cost control, images, voice, and evals — with a studio a
6
+ team can share.
6
7
 
7
8
  Import the whole runtime, or one piece: every capability has its own entry point with a size budget
8
9
  CI enforces, and [the packaging guide](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/packaging.md) publishes what each one costs. A graph-only application loads 49 KB
@@ -90,6 +91,16 @@ You can still pass a plain `NexusAIConfig` to `new NexusAI(...)` when you want f
90
91
  - embed text through the same routing, caching, batching, budget, retry, and metrics as a completion
91
92
  - run long operations that survive a restart, with leases, retries, dead-lettering, and signed webhooks
92
93
  - build stateful graphs with cycles, fan-out, subgraphs, human approval, and resumable checkpoints
94
+ - make ordinary async functions durable step by step, and recover a crashed run at the step it died on
95
+ - load files, web pages, sitemaps, and Git repositories into retrieval, over memory, Postgres, SQLite,
96
+ Redis, Qdrant, Pinecone, Weaviate, or Chroma
97
+ - retrieve with hybrid keyword and vector search, reranking, and diversity, and measure it with evals
98
+ - reach tools from many MCP servers under one configuration, with allowlists and credentials from the
99
+ environment
100
+ - version prompts, instructions, tools, and skills together, and promote them through evaluation gates
101
+ - find failing and slow runs, detect regressions, and review evaluated fix proposals in a shared studio
102
+ - serve assistants as revisions, with canaries that roll back on a regression, workers that scale on
103
+ the queue under Kubernetes, and per-tenant quotas, rate limits, and budgets
93
104
  - trip routing away from a failing provider, and share one rate-limit budget across workers
94
105
  - reach the providers' half-price asynchronous batch tier behind one operation handle
95
106
  - persist generated media to disk or S3 with tenant isolation, retention, and checksums
@@ -112,6 +123,8 @@ from the doc comments. The guides live in the repository, so these links go to G
112
123
  | [MCP](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/mcp.md) | Borrowing tools from MCP servers, a registry of many, and serving your own |
113
124
  | [Traces](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/tracing.md) | Run trees, queries, feedback, comparison, and alerts |
114
125
  | [Evaluation](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/evaluation.md) | Datasets, evaluators, experiments, comparisons with a verdict, review queues, LLM judges |
126
+ | [Context hub](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/context-hub.md) | Prompts, instructions, tools, and skills versioned together, promoted through gates, and moved between projects |
127
+ | [Insights](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/insights.md) | Failing and slow runs clustered, regressions between time windows, and evaluated fix proposals |
115
128
  | [Prompts](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/prompts.md) | Typed templates, content versions, gated promotion, rollback, A/B splits, serving through outages |
116
129
  | [Postgres](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/postgres.md) | One adapter family for operations, memory, traces, evaluation, circuits, and prompts |
117
130
  | [SQLite](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/sqlite.md) | Durable operations, checkpoints, and memory on one machine, over any SQLite driver |
@@ -128,8 +141,9 @@ from the doc comments. The guides live in the repository, so these links go to G
128
141
  | [Realtime voice](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/realtime.md) | Browser and server realtime sessions with barge-in, tools, and exports |
129
142
  | [Telephony](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/telephony.md) | Calls, webhooks, phone numbers, and phone agents on realtime sessions |
130
143
  | [Testing](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/testing.md) | Recording and replaying provider traffic, and conformance suites |
131
- | [Studio](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/studio.md) | A local UI for traces, threads, approvals, experiments, prompts, costs, and health, as a separate package |
132
- | [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 |
144
+ | [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 |
145
+ | [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 |
146
+ | [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 |
133
147
  | [Workflows](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/workflows.md) | Ready-made chains and domain workflows |
134
148
  | [Command line](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/cli.md) | The `nexus` command |
135
149
  | [Packaging and install weight](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/docs/packaging.md) | Every entry point and what it costs |
@@ -179,10 +193,11 @@ interface, or enum, has no doc comment, and `npm run docs:guides` fails when a g
179
193
  every export of the entry points it covers, or its generated reference is stale. Both hold at 100%;
180
194
  `--list` names what is missing, and `npm run docs:update` regenerates the references.
181
195
 
182
- Real provider conformance is opt-in:
196
+ Real provider conformance, and the vector store contract against real servers, are opt-in:
183
197
 
184
198
  ```bash
185
199
  npm run test:conformance:real
200
+ npm run test:vectors:live
186
201
  ```
187
202
 
188
203
  ## Roadmap
@@ -201,11 +216,18 @@ Shipped so far:
201
216
  - record and replay of provider traffic;
202
217
  - prompt versioning: typed templates, content versions, gated promotion, and serving through outages;
203
218
  - a self-hosted agent server, and a local studio as a separate package;
204
- - per-entry-point size budgets.
205
-
206
- Next: durable functional workflows and step-level recovery, then retrieval and integration breadth,
207
- a shared team studio, and self-managed deployment at scale, before 2.0.0 consolidates. The image
208
- family leaves experimental once recorded live conformance passes on all three backends.
219
+ - per-entry-point size budgets;
220
+ - vector stores behind one contract, and deprecation warnings at run time;
221
+ - durable functional workflows, step-level recovery in the server, SQLite persistence, and diagrams
222
+ as images;
223
+ - document loaders, five more vector stores, hybrid retrieval with reranking, and an MCP registry;
224
+ - a shared studio with accounts, roles, an audit log, and comments; a context hub; insights with
225
+ evaluated fix proposals; and evaluation caching;
226
+ - self-managed deployment at scale: revisions and canaries with automatic rollback, a worker queue
227
+ that autoscales on Kubernetes, and per-tenant limits.
228
+
229
+ Next: 2.0.0 consolidates. The image family leaves experimental once recorded live
230
+ conformance passes on all three backends.
209
231
 
210
232
  ## Before Production
211
233
 
@@ -0,0 +1,15 @@
1
+ import { type ParsedArgs } from './args.js';
2
+ /**
3
+ * `nexus deploy` reads and changes an agent server's deployments over HTTP, for a pipeline that rolls
4
+ * out a new image and then moves traffic onto it:
5
+ *
6
+ * nexus deploy status [assistant]
7
+ * nexus deploy canary <assistant> <revision> <percent>
8
+ * nexus deploy promote <assistant> <revision>
9
+ * nexus deploy rollback <assistant> [--to <revision>]
10
+ *
11
+ * `--url` (or `NEXUS_SERVER_URL`) names the server, and `--token` (or `NEXUS_SERVER_TOKEN`) is sent
12
+ * as a bearer token. Changes take `--reason`, recorded in the deployment's history, and `--version`,
13
+ * which refuses the change if someone else moved the deployment first.
14
+ */
15
+ export declare function runDeployCommand(subcommand: string | undefined, { positionals, flags }: ParsedArgs): Promise<void>;
@@ -0,0 +1,116 @@
1
+ import { CliUsageError, numberFlag, printTable, stringFlag, writeJson } from './args.js';
2
+ export async function runDeployCommand(subcommand, { positionals, flags }) {
3
+ const server = serverOf(flags);
4
+ const reason = stringFlag(flags, 'reason');
5
+ const expectedVersion = numberFlag(flags, 'version');
6
+ const change = async (assistant, body) => {
7
+ if (!assistant)
8
+ throw new CliUsageError(`nexus deploy ${subcommand} needs an assistant.`);
9
+ const deployment = await server.request('POST', `/deployments/${encodeURIComponent(assistant)}`, {
10
+ ...body,
11
+ ...(reason ? { reason } : {}),
12
+ ...(expectedVersion === undefined ? {} : { expectedVersion }),
13
+ });
14
+ if (flags.json === true)
15
+ writeJson(deployment);
16
+ else
17
+ console.log(`${assistant}: version ${deployment.version}, live ${deployment.live}, ${splitText(deployment.traffic)}`);
18
+ };
19
+ switch (subcommand) {
20
+ case 'status': {
21
+ const [assistant] = positionals;
22
+ if (assistant) {
23
+ const deployment = await server.request('GET', `/deployments/${encodeURIComponent(assistant)}`);
24
+ if (flags.json === true)
25
+ return writeJson(deployment);
26
+ console.log(`${deployment.assistant}: version ${deployment.version}, live ${deployment.live}`);
27
+ console.log(`split: ${splitText(deployment.traffic)}`);
28
+ if (deployment.stats?.length) {
29
+ console.log('');
30
+ printTable(deployment.stats.map((item) => ({
31
+ revision: item.revision || '(none)',
32
+ runs: String(item.runs),
33
+ failed: String(item.failed),
34
+ 'error rate': `${(item.errorRate * 100).toFixed(1)}%`,
35
+ p95: item.p95Ms === undefined ? '' : `${Math.round(item.p95Ms)}ms`,
36
+ })), ['revision', 'runs', 'failed', 'error rate', 'p95']);
37
+ }
38
+ if (deployment.history.length) {
39
+ console.log('');
40
+ printTable(deployment.history.slice(0, 10).map((entry) => ({
41
+ version: String(entry.version),
42
+ at: entry.at,
43
+ action: entry.action,
44
+ split: splitText(entry.traffic),
45
+ by: entry.by ?? '',
46
+ reason: entry.reason ?? '',
47
+ })), ['version', 'at', 'action', 'split', 'by', 'reason']);
48
+ }
49
+ return;
50
+ }
51
+ const { deployments } = await server.request('GET', '/deployments');
52
+ if (flags.json === true)
53
+ return writeJson(deployments);
54
+ printTable(deployments.map((deployment) => ({
55
+ assistant: deployment.assistant,
56
+ live: deployment.live,
57
+ split: splitText(deployment.traffic),
58
+ version: String(deployment.version),
59
+ updated: deployment.version ? deployment.updatedAt : 'never',
60
+ })), ['assistant', 'live', 'split', 'version', 'updated']);
61
+ return;
62
+ }
63
+ case 'canary': {
64
+ const [assistant, revision, percent] = positionals;
65
+ const share = Number(percent);
66
+ if (!revision || !Number.isFinite(share) || share < 0 || share > 100) {
67
+ throw new CliUsageError('Usage: nexus deploy canary <assistant> <revision> <percent from 0 to 100>');
68
+ }
69
+ return change(assistant, { action: 'canary', revision, weight: share / 100 });
70
+ }
71
+ case 'promote': {
72
+ const [assistant, revision] = positionals;
73
+ if (!revision)
74
+ throw new CliUsageError('Usage: nexus deploy promote <assistant> <revision>');
75
+ return change(assistant, { action: 'promote', revision });
76
+ }
77
+ case 'rollback': {
78
+ const [assistant] = positionals;
79
+ const to = stringFlag(flags, 'to');
80
+ return change(assistant, { action: 'rollback', ...(to ? { to } : {}) });
81
+ }
82
+ default:
83
+ throw new CliUsageError(`Unknown deploy command "${subcommand ?? ''}". Use status, canary, promote, or rollback.`);
84
+ }
85
+ }
86
+ function serverOf(flags) {
87
+ const url = stringFlag(flags, 'url') ?? process.env.NEXUS_SERVER_URL;
88
+ if (!url)
89
+ throw new CliUsageError('nexus deploy needs the server: pass --url or set NEXUS_SERVER_URL.');
90
+ const token = stringFlag(flags, 'token') ?? process.env.NEXUS_SERVER_TOKEN;
91
+ const base = url.replace(/\/+$/, '');
92
+ return {
93
+ async request(method, route, body) {
94
+ const response = await fetch(`${base}${route}`, {
95
+ method,
96
+ headers: {
97
+ ...(token ? { authorization: `Bearer ${token}` } : {}),
98
+ ...(body === undefined ? {} : { 'content-type': 'application/json' }),
99
+ },
100
+ ...(body === undefined ? {} : { body: JSON.stringify(body) }),
101
+ });
102
+ const text = await response.text();
103
+ const parsed = text ? JSON.parse(text) : {};
104
+ if (!response.ok) {
105
+ const error = parsed.error;
106
+ throw new Error(`${response.status} ${error?.code ?? response.statusText}: ${error?.message ?? text}`);
107
+ }
108
+ return parsed;
109
+ },
110
+ };
111
+ }
112
+ function splitText(traffic) {
113
+ return Object.entries(traffic)
114
+ .map(([revision, weight]) => `${revision} ${Math.round(weight * 1000) / 10}%`)
115
+ .join(', ');
116
+ }
package/dist/cli.js CHANGED
@@ -49,6 +49,12 @@ async function main() {
49
49
  await runDbCommand(subcommand, { positionals: args, flags: parsed.flags });
50
50
  return;
51
51
  }
52
+ case 'deploy': {
53
+ const [subcommand, ...args] = parsed.positionals;
54
+ const { runDeployCommand } = await import('./cli/deploy.js');
55
+ await runDeployCommand(subcommand, { positionals: args, flags: parsed.flags });
56
+ return;
57
+ }
52
58
  case 'optimize':
53
59
  await runOptimize(parsed);
54
60
  return;
@@ -390,12 +396,17 @@ Usage:
390
396
  nexus traces show <traceId> --store <runs.jsonl|store.mjs> [--json]
391
397
  nexus traces export --store <runs.jsonl|store.mjs> [--out runs.jsonl] [same filters as list]
392
398
  nexus db sql [--adapters operations,store,traces,evaluation,circuits,prompts] [--vector-dimensions 1536]
399
+ nexus deploy status [assistant] --url <server> [--token t] [--json]
400
+ nexus deploy canary <assistant> <revision> <percent> --url <server> [--reason r] [--version n]
401
+ nexus deploy promote <assistant> <revision> --url <server> [--reason r] [--version n]
402
+ nexus deploy rollback <assistant> [--to revision] --url <server> [--reason r] [--version n]
393
403
  nexus optimize [request.json|prompt.txt] [--model gpt-5-mini] [--max-input-tokens 4000] [--densify] [--json]
394
404
 
395
405
  Eval files can export { cases, client } or { cases, config }. JSON cases may use "expected", "contains", and "match": "includes" | "exact" | "regex".
396
406
  Evaluation modules for "eval run" export { target, dataset, evaluators, summary?, options? }.
397
407
  "eval gate" exits 1 when a metric got worse beyond noise, a new failure appeared, or the datasets differ.
398
- Trace store modules export any TraceStore as { store }, such as a PostgresTraceStore over the application's own pool.`);
408
+ Trace store modules export any TraceStore as { store }, such as a PostgresTraceStore over the application's own pool.
409
+ "deploy" reads NEXUS_SERVER_URL and NEXUS_SERVER_TOKEN when --url and --token are not given.`);
399
410
  }
400
411
  main().catch((error) => {
401
412
  const message = error instanceof Error ? error.message : String(error);
@@ -0,0 +1,32 @@
1
+ import { type PromptLineChange } from '../prompts/diff.js';
2
+ import type { ContextBundle } from './hub.js';
3
+ /** One entry that differs between two bundle versions. */
4
+ export interface ContextChange {
5
+ /** The part of the bundle. */
6
+ section: 'description' | 'prompts' | 'instructions' | 'tools' | 'skills' | 'config';
7
+ /** The entry within it: a prompt role, an instruction or skill name, a tool name, or a config key. */
8
+ key: string;
9
+ /** Whether the entry was added, removed, or changed. */
10
+ change: 'added' | 'removed' | 'changed';
11
+ /** Line by line, for text: an instruction, a skill's instructions, or the description. */
12
+ lines?: PromptLineChange[];
13
+ /** The entry before, for anything that is not text. */
14
+ before?: unknown;
15
+ /** The entry after, for anything that is not text. */
16
+ after?: unknown;
17
+ }
18
+ /** What changed between two versions of a bundle. */
19
+ export interface ContextDiff {
20
+ /** The bundle. */
21
+ name: string;
22
+ /** The earlier version. */
23
+ from: string;
24
+ /** The later version. */
25
+ to: string;
26
+ /** Every entry that differs, section by section. */
27
+ changes: ContextChange[];
28
+ }
29
+ /** Compares two versions of a bundle, entry by entry, with line diffs for text. */
30
+ export declare function diffContexts(from: ContextBundle, to: ContextBundle): ContextDiff;
31
+ /** A diff as text, one line per change and `+`/`-` lines for text, for a terminal or a pull request. */
32
+ export declare function formatContextDiff(diff: ContextDiff): string;
@@ -0,0 +1,64 @@
1
+ import { diffLines } from '../prompts/diff.js';
2
+ import { canonicalJson } from '../prompts/version.js';
3
+ export function diffContexts(from, to) {
4
+ const changes = [];
5
+ if ((from.description ?? '') !== (to.description ?? '')) {
6
+ changes.push({
7
+ section: 'description',
8
+ key: 'description',
9
+ change: from.description === undefined ? 'added' : to.description === undefined ? 'removed' : 'changed',
10
+ lines: diffLines(from.description ?? '', to.description ?? ''),
11
+ });
12
+ }
13
+ compare(changes, 'prompts', from.prompts, to.prompts);
14
+ compare(changes, 'instructions', from.instructions, to.instructions, (value) => value);
15
+ compare(changes, 'tools', Object.fromEntries((from.tools ?? []).map((tool) => [tool.name, tool])), Object.fromEntries((to.tools ?? []).map((tool) => [tool.name, tool])));
16
+ compare(changes, 'skills', from.skills, to.skills, (value) => {
17
+ const skill = value;
18
+ return `${skill.description}\n\n${skill.instructions}${Object.entries(skill.resources ?? {})
19
+ .map(([name, text]) => `\n\n[${name}]\n${text}`)
20
+ .join('')}`;
21
+ });
22
+ compare(changes, 'config', from.config, to.config);
23
+ return { name: to.name, from: from.version, to: to.version, changes };
24
+ }
25
+ export function formatContextDiff(diff) {
26
+ const lines = [`${diff.name}: ${diff.from} -> ${diff.to}`];
27
+ if (diff.changes.length === 0)
28
+ lines.push(' no changes');
29
+ for (const change of diff.changes) {
30
+ lines.push(` ${change.change} ${change.section}.${change.key}`);
31
+ for (const line of change.lines ?? []) {
32
+ if (line.op !== '=')
33
+ lines.push(` ${line.op} ${line.text}`);
34
+ }
35
+ if (!change.lines && change.change === 'changed') {
36
+ lines.push(` - ${canonicalJson(change.before)}`, ` + ${canonicalJson(change.after)}`);
37
+ }
38
+ }
39
+ return lines.join('\n');
40
+ }
41
+ function compare(changes, section, before, after, asText) {
42
+ const keys = [...new Set([...Object.keys(before ?? {}), ...Object.keys(after ?? {})])].sort();
43
+ for (const key of keys) {
44
+ const was = before?.[key];
45
+ const is = after?.[key];
46
+ if (was !== undefined && is !== undefined && canonicalJson(was) === canonicalJson(is))
47
+ continue;
48
+ const change = was === undefined ? 'added' : is === undefined ? 'removed' : 'changed';
49
+ changes.push(asText
50
+ ? {
51
+ section,
52
+ key,
53
+ change,
54
+ lines: diffLines(was === undefined ? '' : asText(was), is === undefined ? '' : asText(is)),
55
+ }
56
+ : {
57
+ section,
58
+ key,
59
+ change,
60
+ ...(was === undefined ? {} : { before: was }),
61
+ ...(is === undefined ? {} : { after: is }),
62
+ });
63
+ }
64
+ }