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.
- package/API_STABILITY.md +65 -0
- package/CHANGELOG.md +267 -1
- package/MIGRATING.md +1 -1
- package/README.md +17 -8
- package/dist/agent/create-agent.d.ts +9 -1
- package/dist/agent/create-agent.js +40 -1
- package/dist/cache/memory-cache.d.ts +8 -0
- package/dist/cache/memory-cache.js +9 -0
- package/dist/cache/semantic-cache.d.ts +6 -3
- package/dist/cache/semantic-cache.js +5 -2
- package/dist/cli/args.js +3 -0
- package/dist/cli/db.d.ts +16 -4
- package/dist/cli/db.js +169 -22
- package/dist/cli/doctor.d.ts +17 -0
- package/dist/cli/doctor.js +53 -0
- package/dist/cli/graph.d.ts +9 -0
- package/dist/cli/graph.js +47 -0
- package/dist/cli.js +21 -2
- package/dist/core/lifecycle.js +11 -6
- package/dist/core/nexus.d.ts +12 -2
- package/dist/core/nexus.js +106 -23
- package/dist/doctor/index.d.ts +90 -0
- package/dist/doctor/index.js +237 -0
- package/dist/evals/judge.d.ts +11 -0
- package/dist/evals/judge.js +33 -0
- package/dist/evaluate/index.d.ts +1 -0
- package/dist/evaluate/index.js +1 -0
- package/dist/evaluate/provenance.d.ts +22 -0
- package/dist/evaluate/provenance.js +10 -0
- package/dist/evaluate/run.js +20 -4
- package/dist/graph/durability.d.ts +27 -0
- package/dist/graph/durability.js +66 -0
- package/dist/graph/errors.d.ts +30 -4
- package/dist/graph/errors.js +18 -2
- package/dist/graph/event-stream.d.ts +75 -0
- package/dist/graph/event-stream.js +289 -0
- package/dist/graph/functional.d.ts +80 -7
- package/dist/graph/functional.js +138 -35
- package/dist/graph/graph.d.ts +56 -8
- package/dist/graph/graph.js +331 -49
- package/dist/graph/index.d.ts +4 -2
- package/dist/graph/index.js +3 -1
- package/dist/graph/lint.d.ts +57 -0
- package/dist/graph/lint.js +141 -0
- package/dist/graph/run-control.d.ts +28 -0
- package/dist/graph/run-control.js +26 -0
- package/dist/images/evals.js +4 -2
- package/dist/operations/adapters.d.ts +52 -4
- package/dist/operations/adapters.js +194 -7
- package/dist/operations/runner.js +14 -15
- package/dist/operations/stats.js +8 -1
- package/dist/operations/store.d.ts +5 -1
- package/dist/operations/store.js +7 -0
- package/dist/ops/circuit-breaker.d.ts +52 -0
- package/dist/ops/circuit-breaker.js +4 -0
- package/dist/ops/circuit-store.d.ts +13 -2
- package/dist/ops/circuit-store.js +68 -0
- package/dist/ops/circuit-sync.d.ts +17 -0
- package/dist/ops/circuit-sync.js +86 -0
- package/dist/ops/health.d.ts +24 -0
- package/dist/ops/health.js +38 -10
- package/dist/ops/index.d.ts +1 -1
- package/dist/ops/index.js +1 -1
- package/dist/ops/rate-limit-adapters.d.ts +23 -0
- package/dist/ops/rate-limit-adapters.js +34 -0
- package/dist/ops/rate-limiter.d.ts +14 -2
- package/dist/ops/rate-limiter.js +31 -0
- package/dist/postgres/circuits.d.ts +21 -4
- package/dist/postgres/circuits.js +65 -4
- package/dist/postgres/client.d.ts +0 -2
- package/dist/postgres/client.js +0 -4
- package/dist/postgres/evaluate.d.ts +16 -4
- package/dist/postgres/evaluate.js +17 -5
- package/dist/postgres/index.d.ts +32 -11
- package/dist/postgres/index.js +65 -31
- package/dist/postgres/migrations.d.ts +60 -0
- package/dist/postgres/migrations.js +121 -0
- package/dist/postgres/operations.d.ts +14 -3
- package/dist/postgres/operations.js +45 -15
- package/dist/postgres/prompts.d.ts +11 -2
- package/dist/postgres/prompts.js +14 -3
- package/dist/postgres/rollups.d.ts +35 -0
- package/dist/postgres/rollups.js +129 -0
- package/dist/postgres/store.d.ts +11 -2
- package/dist/postgres/store.js +14 -3
- package/dist/postgres/traces.d.ts +13 -3
- package/dist/postgres/traces.js +25 -3
- package/dist/postgres/vectors.d.ts +11 -2
- package/dist/postgres/vectors.js +14 -3
- package/dist/router/auto-router.d.ts +34 -7
- package/dist/router/auto-router.js +201 -68
- package/dist/server/assistant.d.ts +44 -0
- package/dist/server/assistant.js +39 -0
- package/dist/server/errors.d.ts +12 -0
- package/dist/server/errors.js +7 -0
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/runs.d.ts +28 -1
- package/dist/server/runs.js +110 -14
- package/dist/sqlite/index.d.ts +16 -3
- package/dist/sqlite/index.js +16 -3
- package/dist/sqlite/migrations.d.ts +26 -0
- package/dist/sqlite/migrations.js +63 -0
- package/dist/sqlite/operations.d.ts +14 -3
- package/dist/sqlite/operations.js +42 -15
- package/dist/sqlite/store.d.ts +9 -2
- package/dist/sqlite/store.js +13 -3
- package/dist/sqlite/vectors.d.ts +11 -2
- package/dist/sqlite/vectors.js +13 -2
- package/dist/store/tenant.d.ts +12 -0
- package/dist/store/tenant.js +29 -0
- package/dist/tenancy/index.d.ts +113 -0
- package/dist/tenancy/index.js +233 -0
- package/dist/testing/record.d.ts +42 -0
- package/dist/testing/record.js +57 -5
- package/dist/tracing/index.d.ts +3 -3
- package/dist/tracing/index.js +2 -2
- package/dist/tracing/instrument.js +30 -0
- package/dist/tracing/otlp.d.ts +91 -0
- package/dist/tracing/otlp.js +124 -0
- package/dist/tracing/rollups.d.ts +100 -0
- package/dist/tracing/rollups.js +139 -0
- package/dist/tracing/stores.d.ts +17 -0
- package/dist/tracing/stores.js +23 -0
- package/dist/tracing/tracer.d.ts +50 -4
- package/dist/tracing/tracer.js +85 -6
- package/dist/types/config.d.ts +33 -2
- package/dist/types/evaluate.d.ts +37 -0
- package/dist/types/graph.d.ts +354 -9
- package/dist/types/graph.js +3 -1
- package/dist/types/messages.d.ts +5 -0
- package/dist/types/operations.d.ts +22 -2
- package/dist/types/response.d.ts +20 -0
- package/dist/types/server.d.ts +18 -0
- package/dist/types/tracing.d.ts +11 -0
- package/dist/utils/schema-migrations.d.ts +97 -0
- package/dist/utils/schema-migrations.js +91 -0
- package/dist/utils/signals.d.ts +17 -0
- package/dist/utils/signals.js +26 -0
- package/dist/utils/tenant.d.ts +14 -0
- package/dist/utils/tenant.js +16 -0
- package/dist/version.d.ts +5 -0
- package/dist/version.js +1 -0
- package/dist-cjs/agent/create-agent.d.ts +9 -1
- package/dist-cjs/agent/create-agent.js +40 -1
- package/dist-cjs/cache/memory-cache.d.ts +8 -0
- package/dist-cjs/cache/memory-cache.js +9 -0
- package/dist-cjs/cache/semantic-cache.d.ts +6 -3
- package/dist-cjs/cache/semantic-cache.js +5 -2
- package/dist-cjs/cli/args.js +3 -0
- package/dist-cjs/cli/db.d.ts +16 -4
- package/dist-cjs/cli/db.js +200 -20
- package/dist-cjs/cli/doctor.d.ts +17 -0
- package/dist-cjs/cli/doctor.js +56 -0
- package/dist-cjs/cli/graph.d.ts +9 -0
- package/dist-cjs/cli/graph.js +50 -0
- package/dist-cjs/core/lifecycle.js +11 -6
- package/dist-cjs/core/nexus.d.ts +12 -2
- package/dist-cjs/core/nexus.js +106 -23
- package/dist-cjs/doctor/index.d.ts +90 -0
- package/dist-cjs/doctor/index.js +276 -0
- package/dist-cjs/evals/judge.d.ts +11 -0
- package/dist-cjs/evals/judge.js +33 -0
- package/dist-cjs/evaluate/index.d.ts +1 -0
- package/dist-cjs/evaluate/index.js +3 -1
- package/dist-cjs/evaluate/provenance.d.ts +22 -0
- package/dist-cjs/evaluate/provenance.js +14 -0
- package/dist-cjs/evaluate/run.js +20 -4
- package/dist-cjs/graph/durability.d.ts +27 -0
- package/dist-cjs/graph/durability.js +70 -0
- package/dist-cjs/graph/errors.d.ts +30 -4
- package/dist-cjs/graph/errors.js +20 -3
- package/dist-cjs/graph/event-stream.d.ts +75 -0
- package/dist-cjs/graph/event-stream.js +295 -0
- package/dist-cjs/graph/functional.d.ts +80 -7
- package/dist-cjs/graph/functional.js +137 -34
- package/dist-cjs/graph/graph.d.ts +56 -8
- package/dist-cjs/graph/graph.js +330 -48
- package/dist-cjs/graph/index.d.ts +4 -2
- package/dist-cjs/graph/index.js +7 -1
- package/dist-cjs/graph/lint.d.ts +57 -0
- package/dist-cjs/graph/lint.js +144 -0
- package/dist-cjs/graph/run-control.d.ts +28 -0
- package/dist-cjs/graph/run-control.js +30 -0
- package/dist-cjs/images/evals.js +4 -2
- package/dist-cjs/operations/adapters.d.ts +52 -4
- package/dist-cjs/operations/adapters.js +193 -6
- package/dist-cjs/operations/runner.js +14 -15
- package/dist-cjs/operations/stats.js +8 -1
- package/dist-cjs/operations/store.d.ts +5 -1
- package/dist-cjs/operations/store.js +7 -0
- package/dist-cjs/ops/circuit-breaker.d.ts +52 -0
- package/dist-cjs/ops/circuit-breaker.js +4 -0
- package/dist-cjs/ops/circuit-store.d.ts +13 -2
- package/dist-cjs/ops/circuit-store.js +68 -0
- package/dist-cjs/ops/circuit-sync.d.ts +17 -0
- package/dist-cjs/ops/circuit-sync.js +87 -0
- package/dist-cjs/ops/health.d.ts +24 -0
- package/dist-cjs/ops/health.js +38 -10
- package/dist-cjs/ops/index.d.ts +1 -1
- package/dist-cjs/ops/rate-limit-adapters.d.ts +23 -0
- package/dist-cjs/ops/rate-limit-adapters.js +35 -1
- package/dist-cjs/ops/rate-limiter.d.ts +14 -2
- package/dist-cjs/ops/rate-limiter.js +32 -0
- package/dist-cjs/postgres/circuits.d.ts +21 -4
- package/dist-cjs/postgres/circuits.js +98 -3
- package/dist-cjs/postgres/client.d.ts +0 -2
- package/dist-cjs/postgres/client.js +0 -5
- package/dist-cjs/postgres/evaluate.d.ts +16 -4
- package/dist-cjs/postgres/evaluate.js +50 -4
- package/dist-cjs/postgres/index.d.ts +32 -11
- package/dist-cjs/postgres/index.js +69 -20
- package/dist-cjs/postgres/migrations.d.ts +60 -0
- package/dist-cjs/postgres/migrations.js +129 -0
- package/dist-cjs/postgres/operations.d.ts +14 -3
- package/dist-cjs/postgres/operations.js +78 -14
- package/dist-cjs/postgres/prompts.d.ts +11 -2
- package/dist-cjs/postgres/prompts.js +47 -2
- package/dist-cjs/postgres/rollups.d.ts +35 -0
- package/dist-cjs/postgres/rollups.js +168 -0
- package/dist-cjs/postgres/store.d.ts +11 -2
- package/dist-cjs/postgres/store.js +47 -2
- package/dist-cjs/postgres/traces.d.ts +13 -3
- package/dist-cjs/postgres/traces.js +58 -2
- package/dist-cjs/postgres/vectors.d.ts +11 -2
- package/dist-cjs/postgres/vectors.js +47 -2
- package/dist-cjs/router/auto-router.d.ts +34 -7
- package/dist-cjs/router/auto-router.js +201 -68
- package/dist-cjs/server/assistant.d.ts +44 -0
- package/dist-cjs/server/assistant.js +39 -0
- package/dist-cjs/server/errors.d.ts +12 -0
- package/dist-cjs/server/errors.js +9 -1
- package/dist-cjs/server/index.d.ts +1 -1
- package/dist-cjs/server/index.js +2 -1
- package/dist-cjs/server/runs.d.ts +28 -1
- package/dist-cjs/server/runs.js +109 -13
- package/dist-cjs/sqlite/index.d.ts +16 -3
- package/dist-cjs/sqlite/index.js +31 -10
- package/dist-cjs/sqlite/migrations.d.ts +26 -0
- package/dist-cjs/sqlite/migrations.js +71 -0
- package/dist-cjs/sqlite/operations.d.ts +14 -3
- package/dist-cjs/sqlite/operations.js +76 -15
- package/dist-cjs/sqlite/store.d.ts +9 -2
- package/dist-cjs/sqlite/store.js +47 -3
- package/dist-cjs/sqlite/vectors.d.ts +11 -2
- package/dist-cjs/sqlite/vectors.js +47 -2
- package/dist-cjs/store/tenant.d.ts +12 -0
- package/dist-cjs/store/tenant.js +33 -0
- package/dist-cjs/tenancy/index.d.ts +113 -0
- package/dist-cjs/tenancy/index.js +250 -0
- package/dist-cjs/testing/record.d.ts +42 -0
- package/dist-cjs/testing/record.js +57 -5
- package/dist-cjs/tracing/index.d.ts +3 -3
- package/dist-cjs/tracing/index.js +4 -1
- package/dist-cjs/tracing/instrument.js +30 -0
- package/dist-cjs/tracing/otlp.d.ts +91 -0
- package/dist-cjs/tracing/otlp.js +129 -0
- package/dist-cjs/tracing/rollups.d.ts +100 -0
- package/dist-cjs/tracing/rollups.js +147 -0
- package/dist-cjs/tracing/stores.d.ts +17 -0
- package/dist-cjs/tracing/stores.js +24 -0
- package/dist-cjs/tracing/tracer.d.ts +50 -4
- package/dist-cjs/tracing/tracer.js +87 -6
- package/dist-cjs/types/config.d.ts +33 -2
- package/dist-cjs/types/evaluate.d.ts +37 -0
- package/dist-cjs/types/graph.d.ts +354 -9
- package/dist-cjs/types/graph.js +3 -1
- package/dist-cjs/types/messages.d.ts +5 -0
- package/dist-cjs/types/operations.d.ts +22 -2
- package/dist-cjs/types/response.d.ts +20 -0
- package/dist-cjs/types/server.d.ts +18 -0
- package/dist-cjs/types/tracing.d.ts +11 -0
- package/dist-cjs/utils/schema-migrations.d.ts +97 -0
- package/dist-cjs/utils/schema-migrations.js +102 -0
- package/dist-cjs/utils/signals.d.ts +17 -0
- package/dist-cjs/utils/signals.js +29 -0
- package/dist-cjs/utils/tenant.d.ts +14 -0
- package/dist-cjs/utils/tenant.js +22 -0
- package/dist-cjs/version.d.ts +5 -0
- package/dist-cjs/version.js +4 -0
- package/llms.txt +4 -3
- 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.
|
|
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
|
|
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
|
|
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
|
-
-
|
|
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
|
|
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
|
|
220
|
-
|
|
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,
|
|
251
|
-
|
|
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 =
|
|
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) {
|