# API Stability Policy

This policy describes compatibility guarantees for `nexus-ai-pro`. The 3.x guarantees apply beginning
with version 3.0.0, as the 2.x guarantees applied from 2.0.0 and the 1.x guarantees from 1.0.0. The sections for the 1.x stages below
still describe each surface; where they say "the 1.x rules", read the rules of the current major line.

## Release stages

The package follows semantic versioning. Within a major line, incompatible changes to stable public
APIs require the next major release. Minor releases may add backward-compatible features, and patch releases
are intended to remain backward compatible.

The following surfaces are public and covered by this policy:

- exports documented in the root package or an explicit `package.json` subpath;
- exported TypeScript types and runtime symbols;
- documented configuration keys, response shapes, and CLI commands.

Source files, `dist` file paths, examples, test helpers that are not exported, and undocumented implementation details are not public API. Import only from `nexus-ai-pro` or one of its explicit subpaths.

## Compatibility rules

- A public symbol will not be removed or incompatibly changed in a patch release.
- Deprecations will be documented before removal whenever practical.
- New optional fields, providers, exports, error subclasses, and enum-like union members may be added in a minor release.
- TypeScript improvements that expose previously invalid usage may arrive in a patch release.
- Security fixes may tighten validation or blocking behavior in a patch release when preserving the old behavior would leave users exposed.
- Provider behavior can change when an upstream API changes; Nexus will preserve its normalized request and response contracts where possible.

## Module formats

Since 1.2.0 the package ships parallel ESM and CommonJS builds and supports Node.js 22 or newer.
Both are covered by this policy:

- every export subpath carries `import` and `require` conditions, each resolving to its own build and
  its own declarations, so a CommonJS consumer never lands on ESM declarations;
- `typesVersions` maps each subpath to its CommonJS declarations, so subpath types resolve under
  `moduleResolution: "node10"` without a consumer migrating its `tsconfig.json`;
- dropping either build, or removing a subpath from either condition, requires a major release.

Deep imports into `dist`, `dist-cjs`, or `src` are not supported. Import only from `nexus-ai-pro` or
one of its explicit subpaths.

## How an item graduates

A new surface starts this way:

1. Provider-neutral types and a deterministic mock land first.
2. One real adapter proves the contract; conformance fixtures cover it.
3. A second adapter on a different wire protocol proves portability.
4. Packed-package, clean-install, and consumer type-resolution tests pass on every supported Node
   line.
5. Evaluation appropriate to the modality covers the supported capability matrix.

It leaves experimental status here only when every item below has evidence, published with the
graduation:

| Requirement | Evidence |
| --- | --- |
| API stability | Covered by the 2.x compatibility rules, with no planned breaking change |
| Conformance | Passes its contract suite on every supported backend |
| Crash recovery | Crash-injection tests at each durable boundary, where it has one |
| Concurrency | Race tests with several processes on the same records |
| Load | A published benchmark with a budget in CI |
| Tenant isolation | The isolation suite, where it stores tenant data |
| Security | A written threat review, with its findings closed |
| Upgrade | A migration test from the previous minor |
| Soak | A long-running test with flat memory and no leaked handles |

## 3.0 stage (3.0.0)

3.0 is a major release. It turns on the three defaults 2.21 previewed — incremental tracing on a
durable trace store, the Redis operation store's dispatch index, and `hash-v2` for a deployment first
recorded — and removes `OpenApiTools.skipped` and `GraphCheckpointV2`, both deprecated in 2.x. Nothing
stored changes: checkpoints of version 1 and 2, the state store's keys, operation records, and recorded
deployments read as they did, and the state store's keys are read by every later 3.x.
`useDefaults('2.x')` and `NEXUS_DEFAULTS=2.x` keep 2.x's three defaults through 3.x.
[MIGRATING.md](./MIGRATING.md) covers each change. The rollout is proven against the published 2.21.0
package, in `tests/upgrade-3.test.ts`: a 2.21 worker and a 3.0 worker share a Redis prefix, each
claiming what it should and 3.0 finding the rest after one `reindex()`, and 2.21 and 3.0 replicas
place every thread alike, whichever records a deployment first.

Removed: `OpenApiTools.skipped`, always empty since 2.22, and `GraphCheckpointV2`, for `GraphCheckpoint`.
Everything else 3.0 adds is stable from the start and additive, unless this section says otherwise.

### Graduation

The studio's sign-in leaves experimental status: `oidcSignIn()`, `OidcSignInOptions`,
`SIGN_IN_COOKIE`, and the `/auth/login`, `/auth/callback`, and `/auth/logout` routes.

| Requirement | Evidence |
| --- | --- |
| API stability | Covered by the compatibility rules, with no planned breaking change |
| Conformance | Signs a person in through Dex v2.46.0, run in a container on every build, where a wrong password is kept and a reused code refused: `studio/test/signin-dex.test.ts`; every way a callback can be wrong, a token for another client among them, against a provider written from the specification: `studio/test/signin.test.ts` |
| Crash recovery | n/a: it keeps nothing; a session is a signed cookie, and a sign-in in progress is one the person carries |
| Concurrency | A sign-in begun on one replica finishes on another, and every replica sharing the secret reads the session: `studio/test/signin.test.ts` |
| Load | 500 reads on a sign-in session cost at most twice the same reads on a personal token: `studio/test/signin.test.ts` |
| Tenant isolation | A tenant from a claim reads only that tenant's sources, comments, and audit entries: `studio/test/signin.test.ts`, `studio/test/tenants.test.ts` |
| Security | [Review](./SECURITY.md#the-studio-and-its-accounts), reviewed for 2.22, its findings closed |
| Upgrade | A session sealed by 2.22.0 is read: `studio/test/signin.test.ts` |
| Soak | 1,000 sign-ins and their reads with flat memory: `studio/test/signin.test.ts` |

## 2.22 stage (2.22.0)

Everything 2.22 adds is stable from the start and additive, unless this section says otherwise.

New and stable: `StreamChunk.logprobs`; in `nexus-ai-pro/protocols/a2a`, `signA2aCard()`,
`verifyA2aCard()`, `canonicalA2aCard()`, `A2aCardSignatureError`, `A2aCardSigner`, `A2aCardKeyResolver`,
`A2aSignatureAlgorithm`, `A2aJsonWebKey`, `A2aCryptoKey`, `A2aServerOptions.sign`, and
`A2aClientOptions.verifyCard`, which `A2aGrpcClientOptions` takes too; in `nexus-ai-pro/mcp`,
`mcpHttpHandler()`, `McpHttpOptions`, `McpHttpHandler`, `samplingFrom()`, `McpSamplingOptions`,
`McpSamplingClient`, `McpServerPrompt`, `McpPromptArgument`, `McpPromptMessage`, `McpServerResource`,
the server's `registerPrompt()`, `registerResource()`, `resourceUpdated()`, `createMessage()`,
`elicit()`, `listRoots()`, and `onRootsChanged()`, the client's `sampling`, `elicitation`, and `roots`
options, `onPromptsChanged()`, `onResourcesChanged()`, `onResourceUpdated()`, `subscribeResource()`,
`unsubscribeResource()`, and `rootsChanged()`, `HttpClientOptions.listen`, the protocol types
`McpSamplingRequest`, `McpSamplingResult`, `McpSamplingMessage`, `McpSamplingContent`,
`McpModelPreferences`, `McpElicitationRequest`, `McpElicitationResult`, `McpRoot`, and
`McpCallerRequests`, and `ToolContext.mcp`; in `nexus-ai-pro/agent/openapi`, `swaggerToOpenApi()`,
`bundleOpenApiDocument()`, `OpenApiBundleOptions`, `OpenApiCredential`, `OpenApiWebhook`,
`OpenApiToolsOptions.credentials`, `OpenApiOperation.security`, `OpenApiTools.webhooks`, and
`loadOpenApiTools()`'s `references`, `maxDocuments`, and `maxBytes`; in `nexus-ai-pro/server/auth`,
`createJwtSigner()`, `JwtSigner`, `JwtSignerOptions`, `JwtSignOptions`, `generateJwtKey()`,
`jwksHandler()`, `createApiKeyStore()`, `ApiKeyStore`, `ApiKeyStoreOptions`, `ApiKeyRecord`,
`IssuedApiKey`, `ApiKeyIssueOptions`, `createSessions()`, `Sessions`, `SessionOptions`,
`SessionCookieOptions`, `SessionRecord`, and `StartedSession`, and the principal method `session`; in
`nexus-ai-pro/rag/pinecone`, `PineconeVectorStore.create()`, `ensurePineconeIndex()`,
`PineconeIndexOptions`, `PineconeIndex`, and `PineconeCreateOptions`, experimental with the store;
`TypeSafeDecisionProviderOptions.pictures`; and in `nexus-ai-pro/embeddings`, `EmbeddingVideoPart`, the
modality `video`, `EmbeddingMeta.unpricedMedia`, and `EmbeddingModelCapabilities`'
`costPerAudioSecond`, `costPerVideoFrame`, `videoFrames`, `maxAudioSeconds`, and `maxVideoSeconds`; in
the loaders, `FileInput`'s stream form, `CsvRowParser`, `JsonLoaderOptions.recordsAt`,
`WebLoaderOptions.render` and `renderBelow`, `browserRenderer()`, `WebPageRenderer`, `WebPageToRender`,
`RenderingBrowser`, and `BrowserRendererOptions`; in the realtime family, `ImageItem`, the `message.image`
event, and `RealtimeConversationOptions.recordImages`; in telephony, `TelephonyGatherResult`,
`EndGatherRequest`, `parseGatherResult()` and `endGather()` on the manager and the providers, and
`TelnyxTelephonyProviderConfig.transcriptionEngine`; in the studio, `StudioOptions.tenantSources`,
`StudioUser.tenant`, `StudioAuditEntry.tenant`, `StudioComment.tenant`, `StudioAuditQuery`,
`HeaderAuthOptions.tenant`, `BearerAuthOptions.tenant`, `StudioSignIn`, and `StudioOptions.signIn`.
Experimental: in the studio, `oidcSignIn()`, `OidcSignInOptions`, `SIGN_IN_COOKIE`, and the
`/auth/` routes, until a sign-in against a live provider is recorded; `nexus-ai-pro/voice/google`,
`GoogleVoiceProvider` and `GoogleVoiceProviderConfig`, until a live run is recorded.
The state store keys and sessions are kept in, under `nexus/server`, is read by every later 2.x.

Changed on purpose: an answer the output guard changes or blocks keeps no `logprobs`, streamed or
not; a typed decision of a model that reports the chosen token alone is sampled; an Ollama stream
honours `stop`. An MCP server declares prompts and resource subscriptions at `initialize` whatever it
was given, and serves every transport connected to it rather than the last; a tool it runs gets a
`signal` and `mcp` in its context. The HTTP client transport returns from `send()` before a streamed
answer ends, sends `mcp-protocol-version`, and sends a `DELETE` when closed with a session. OpenAPI
tools are made of operations with any request body, where those with a body other than JSON or a form
were skipped, and of Swagger 2.0 documents, which were refused; `loadOpenApiTools()` reads no more of
a document than `maxBytes`. With `credentials` given, an operation whose security cannot be met fails
before it is sent.

A TypeSafe AI request with pictures is multipart unless `pictures: 'base64'`, and its size is counted
in bytes. gemini-embedding-2's audio and video are priced, where they cost nothing before, and audio
past 180 seconds or video past 120 is refused before the call. A Google embedding batch over 20 MB is
sent as a file, where it was refused. Gemini Live acts on `updateSession()`, which it ignored. A
realtime record holds the images sent, as `image` items, which a consumer that switches on every item
type meets for the first time. A Telnyx gather for speech transcribes the caller, where it was
refused.

Deprecated: `OpenApiTools.skipped`, always empty, is removed in 3.0.

## 2.21 stage (2.21.0)

Everything 2.21 adds is stable from the start and additive, unless this section says otherwise.

New and stable, in `nexus-ai-pro/decisions`: `BoxQuestion`, `BoxAnswer`, `DecisionBox`, `boxOverlap()`,
`statePictures()`, `readPicture()`, `DecisionPicture`, `ReadDecisionPicture`, the question type `box`
and its `DecisionDistribution`, `DecisionProviderCapabilities.images`, `answeredRight()`'s box and
`overlap`, `DecisionEvaluatorOptions.where` and `overlap`, `DecisionConformanceOptions.picture`,
`DecisionConformanceReport.skipped`, and `CompletionDecisionsOptions.images`; in
`nexus-ai-pro/decisions/typesafe`, `TypeSafeDecisionProviderOptions.media`; in `nexus-ai-pro/adapter-kit`,
`gatherRecords()`, `GatherRecordsOptions`, `GatherReport`, `GatheredSource`, `GatherOutcome`,
`RecordSource`, and `ADAPTER_KEYWORD`; the command `nexus adapters gather`; a package's
`nexus.compatibility` field, the URL of its record; in `nexus-ai-pro/config`, `useDefaults()`,
`nexusDefaults()`, `NexusDefaults`, `DEFAULT_CHANGES`, and `DefaultChange`, and the `NEXUS_DEFAULTS`
variable; `DoctorOptions.upgrade` and `nexus doctor --upgrade`; and `TraceStore.durable`. The
preview turns on exactly what 3.0 will; should 3.0 change its defaults again before it ships, the
preview follows, and `DEFAULT_CHANGES` says so. A box from TypeSafe AI rests
on what it marks as experimental, its pictures and boxes, whose request format may change between its
releases.

Changed on purpose: TypeSafe AI's 400 and 413 are `DECISION_INVALID`, not `DECISION_FAILED`; neither
was retried before or is now. A provider of your own that reads pictures declares `images: true`;
without it, a decision about a picture is not sent to it. A union over `DecisionQuestionType` or
`DecisionAnswer` that handled three kinds now meets a fourth, `box`. `compatibilityPage()` writes a
repository that is not an `http` or `https` address as text, not a link, and escapes a record's
Markdown. A doctor report has one more check, `upgrade`, skipped unless asked for. With `tenants`, an
A2A handler answers `/{tenant}/.well-known/agent-card.json` with that tenant's card, where it answered
the card itself; without `tenants`, nothing changes. A model on OpenAI's Responses API is sent
`include` and `top_logprobs` when a request asks for log probabilities, which it was not before.

## 2.20 stage (2.20.0)

Everything 2.20 adds is stable from the start and additive, unless this section says otherwise.

New and stable:
- `nexus-ai-pro/agent/openapi`: `openApiTools()`, `loadOpenApiTools()`, `OpenApiError`,
  `OpenApiDocument`, `OpenApiOperation`, `OpenApiTools`, `OpenApiToolsOptions`, and
  `OpenApiHeadersRequest`;
- the capabilities `api.read`, `api.write`, and `api.destructive`;
- in `nexus-ai-pro/evaluate/emulate`: `EmulatedWorld`, `EmulatedWorldCall`, `EmulatedWorldSnapshot`,
  `EmulatedToolsOptions.world` and `reads`, and `emulationKey()`'s `world`. A key without a world is
  the key of every earlier release;
- in `nexus-ai-pro/protocols/a2a`: `A2aServerOptions.tenants`, `A2aTenancyOptions`,
  `A2aClientOptions.tenant`, `A2aTaskRecord.tenant`, `A2aTaskQuery.tenant`, `A2A_ERRORS.permissionDenied`,
  the card's `securitySchemes`, `securityRequirements`, and `signatures`, `A2aSkill.securityRequirements`,
  `A2aSecurityScheme`, `A2aOAuthFlows`, `A2aSecurityRequirement`, and `A2aCardSignature`.

Changed on purpose: the gRPC binding writes and reads a card's security schemes, requirements, and
signatures, which it read past; without `tenants`, a request's tenant is still not read. A custom
`A2aTaskStore` serving several tenants should honour `A2aTaskQuery.tenant`, for full pages; the handler
filters by tenant either way.

## 2.19 stage (2.19.0)

Everything 2.19 adds is stable from the start and additive, unless this section says otherwise.

New and stable:
- in the root: `CompletionRequest.logprobs`, `NexusResponse.logprobs`, `TokenLogprob`, and
  `ModelCapabilities.logprobs`;
- in `nexus-ai-pro/workflows`: `ClassifyRouteOptions.decisions`.

New and experimental, until the live workflow passes against TypeSafe AI's Jev and a model that
reports log probabilities: the typed decisions family, `nexus-ai-pro/decisions`,
`nexus-ai-pro/decisions/typesafe`, and `nexus-ai-pro/decisions/mock`.

Changed on purpose: `OperationFamily` and `RunKind` widen with `decision`.

## 2.18 stage (2.18.0)

Everything 2.18 adds is stable from the start and additive, unless this section says otherwise.

New and stable:
- in `nexus-ai-pro/tracing`: `RunEvent`, `RunLink`, `RunAttributeValue`, `RunEventInput`, `RunLinkInput`,
  `Run.events`, `droppedEvents`, and `links`, `Tracer.event()`, `RunHandle.event()` and `link()`,
  `StartRunOptions.links`, `FinishRunOptions.events` and `links`, `TracerOptions.maxEventsPerRun`, and
  the event names `retry`, `failover`, `progress`, `guardrail`, `cache`, and `capability_warning`;
- in `nexus-ai-pro/tracing/otlp`: `OtlpSpanEvent`, `OtlpSpanLink`, and `OtlpSpan.events`,
  `droppedEventsCount`, and `links`;
- in the root: `ResponseAttempt` and `ResponseMeta.failedAttempts`;
- in `nexus-ai-pro/evaluate/media`: `readMediaDuration()` and `MediaDuration`;
- in `nexus-ai-pro/video`, experimental with the family: `VideoModelCapabilities.maxInputVideoSeconds`;
- in `nexus-ai-pro/server/regions`: `RegionalGatewayOptions.lists`, `RegionListGap`, and a merged
  list's `regions`, `incomplete`, and `nextCursor`;
- in `nexus-ai-pro/server`: `ListPage`, `RunManager.listThreads()` and `listRuns()`, and the lists'
  `cursor` and `nextCursor`;
- in `nexus-ai-pro/mcp`: `McpClientOptions.reconnect`, `McpClient.onToolsChanged()`, and
  `SseClientOptions.reopen`;
- in `nexus-ai-pro/video`, experimental with the family: `VideoGenerateRequest.referenceVideos` and
  `VideoProviderRequest.referenceVideos`, the `reference-videos` input kind,
  `VideoModelCapabilities.maxReferenceVideos` and `referenceVideoSeconds`, and
  `VideoConformanceOptions.referenceVideo`;
- in `nexus-ai-pro/video/google`, experimental with the family:
  `GoogleVideoProviderConfig.interactionsBaseUrl`, and Gemini Omni Flash on Vertex AI.

Changed on purpose:
- a list through the regional gateway is merged from every region the tenant may reach;
  `lists: 'home'` keeps the earlier answer;
- an agent server's lists are ordered by last write, then id, and a `limit` that is not a whole
  number above 0 is refused;
- `McpServer` says `tools.listChanged`, and `register()` sends the notification;
- `VideoInputKind` widens with `reference-videos`.

## 2.17 stage (2.17.0)

Everything 2.17 adds is stable from the start and additive, unless this section says otherwise.

New and stable:
- in `nexus-ai-pro/mcp`: `createSseTransport()`, `SseClientOptions`, `mcpToolCapabilities()`,
  `mcpToolAnnotations()`, `McpToolAnnotations`, `McpToolsOptions`, and `McpToolDescriptor.annotations`;
  the capability kinds `mcp.read`, `mcp.write`, and `mcp.destructive`;
- in `nexus-ai-pro/protocols/acp/editor`: `AcpEditorOptions.mcpToolCapabilities` and
  `AcpEditor.mcpCapabilities`; in `nexus-ai-pro/protocols/acp`: `AcpEditorBridge.mcpCapabilities`;
- in `nexus-ai-pro/video`, experimental with the family: `VideoSource`, `VideoGenerateRequest.video`,
  `VideoProviderRequest.video`, the `VideoInputKind` values `extend` and `edit`,
  `VideoModelCapabilities.extendDurations`, `requires`, and `continuesJobs`,
  `VideoProviderCapabilities.inputVideoMimeTypes`, and `VideoConformanceOptions.video`; Gemini Omni
  Flash in `nexus-ai-pro/video/google`, and video to video in `nexus-ai-pro/video/runway`.

Changed on purpose: `acpEditor()` reads MCP tool annotations by default. A policy that allowed the
editor's MCP tools as undeclared (`undeclared: 'allow'`) now decides annotated tools by their
capabilities; `mcpToolCapabilities: false` keeps the earlier decisions. `GoogleVideoProvider`'s
default models follow Google's shutdown of the Veo 3.1 previews: Gemini Omni Flash on the Gemini API,
and the generally available Veo 3.1 models on Vertex AI.

## 2.16 stage (2.16.0)

Everything 2.16 adds is stable from the start and additive, unless this section says otherwise.

New and stable:
- `nexus-ai-pro/protocols/a2a/grpc`: `a2aGrpcService()`, `a2aGrpcClient()`, `A2aGrpcService`,
  `A2aGrpcServiceOptions`, `A2aGrpcClient`, and `A2aGrpcClientOptions`;
- in `nexus-ai-pro/protocols/a2a`: the HTTP+JSON routes, `A2aServerOptions.bindings` and
  `interfaces`, `A2aHandler.call()`, `card`, and `authenticate()`, `A2aBinding`, `A2aCallContext`,
  `A2aCallOutcome`, `A2aErrorStatus`, `a2aErrorStatus()`, `a2aErrorCode()`,
  `A2aClientOptions.binding`, `A2aPushOptions.text`, and `A2aPushNotificationConfig.authentication`.

The routes, the gRPC service, and their errors follow A2A 1.0's bindings: a change the specification
makes in a later version comes with that version, behind `A2A_PROTOCOL_VERSION`.

## 2.15 stage (2.15.0)

Everything 2.15 adds is stable from the start and additive, unless this section says otherwise.

New and stable:
- in `nexus-ai-pro/adapter-kit`: `Adapter.record()`, `compatibilityRecord()`, `readCompatibilityRecord()`,
  `recordDigest()`, `recordStatus()`, `compareRecords()`, `compatibilityPage()`,
  `CompatibilityRecordError`, `COMPATIBILITY_RECORD_FORMAT`, `CompatibilityRecord`,
  `CompatibilityRecordAdapter`, `CompatibilityRecordOptions`, `CompatibilityStatus`,
  `CompatibilityState`, `CompatibilityComparison`, `CheckChange`, `CompatibilityPageOptions`,
  `ADAPTER_CONTRACTS`, `AdapterContract`, and `AdapterContractVersion`; `AdapterReport.contractVersion`,
  `nexusRange`, `capabilities`, and `budgets`;
- the `nexus-compatibility/1` record format: a reader of one format version reads every record of it;
  a new field may be added, and a record that needs it declares a new format;
- the `nexus adapters verify`, `page`, and `recheck` commands, their flags, and their exit codes;
- `OpenAIRealtimeServerOptions.credentials`, `tenantId`, and `provider`, the optional `apiKey`, and the
  tenant the session endpoint takes per call.

A contract's version rises when a minor release adds a check, by the rule above; its history keeps
every version.

## 2.14 stage (2.14.0)

Everything 2.14 adds is stable from the start and additive, unless this section says otherwise.

New and stable:
- `nexus-ai-pro/policy`: `parsePolicy()`, `applyPolicy()`, `comparePolicies()`, `checkPolicyChange()`,
  `providerGate()`, `policyHash()`, `PolicyError`, `Policy`, `PolicyDocument`, `PolicyAccess`,
  `PolicyRouting`, `PolicyBudgets`, `PolicyResidency`, `PolicyCredentials`, `PolicyIssue`,
  `ApplyPolicyOptions`, `PolicyChange`, `PolicyChangeKind`, and `PolicyCheck`;
- `LifecycleConfig.providers` and `policy`, `ProviderGate`, `PolicyReference`,
  `OperationDescriptor.policy`, `AuditLogEvent.policy`, and `OperationLifecycle.access()`;
- the `nexus policy check` command, its flags, its exit codes, and its `--json` output;
- `nexus-ai-pro/evaluate/emulate`: `emulatedTools()`, `EmulatedToolsOptions`, `EmulatorClient`,
  `EmulatedToolCall`, `EmulatedResult`, `EmulationStore`, `MemoryEmulationStore`, `EmulationError`, and
  `emulationKey()`. What the emulator is told may be reworded in a minor release; a store's keys never
  change within a major;
- `CallResidency.refusal()`, optional, and the optional `model` its `allows()`, `place()`, and
  `check()` take;
- `OperationLifecycle.callContext()`, the optional `OperationLifecycleLike.callContext`, and
  `ContinuedCallContext`;
- `tenantId` on `BatchSubmitRequest`, `EmbeddingBatchSubmitRequest`, `MediaReadRequest`,
  `MediaDescribeRequest`, `MediaAnswerRequest`, `TranscriptionRequest`, `SpeechRequest`, and every
  telephony request that takes a `signal`; `MediaCallContext.tenantId` and `credential`;
  `RealtimeSessionConfig.credential`; the optional context the OpenAI voice adapter and the Twilio and
  Telnyx adapters take on each method that calls their API.

The kinds `comparePolicies()` gives a change may be refined in a minor release: a change found to widen
what a tenant may reach is reported as `widened` from then on, so a check may start to ask for an
acknowledgment it did not ask for before. A check never stops asking for one it asked for.

## 2.13 stage (2.13.0)

Everything 2.13 adds is stable from the start and additive, unless this section says otherwise.

New and stable:
- `nexus-ai-pro/server/regions`: `regionalGateway()`, `RegionalGateway`, `RegionalGatewayOptions`,
  `RegionTarget`, `RegionHealth`, and `RegionHealthOptions`;
- `RunManagerOptions.region` and `residency`, as `AgentServerOptions` carries them; `ThreadRecord.region`,
  `RunRecord.region`, and `ReplicaReport.region`; a regional server's ids start with its region;
- `nexus-ai-pro/residency`: `residency()`, `Residency`, `ResidencyOptions`, `RegionRule`,
  `RegionDefinition`, `ResidencyPolicy`, `ResidencyDecision`, `ResidencyTarget`, `ResidencyPlacement`,
  `CallResidency`, `ResidencyError`, `regionalTraceStore()`, `RegionalTraceStore`,
  `RegionalTraceStoreOptions`, `regionalAssetStore()`, and `RegionalAssetStoreOptions`;
- `LifecycleConfig.residency`, `ProviderCallContext.residency`, and `ImageRequestBase.tenantId`;
- `nexus-ai-pro/credentials`: `tenantCredentials()`, `TenantCredentials`, `TenantCredentialsOptions`,
  `TenantCredentialStats`, `CredentialError`, `parseSecret()`, `credentialRequest()`, `SecretSource`,
  `awsSecrets()`, `gcpSecrets()`, `azureSecrets()`, `vaultSecrets()`, `envSecrets()`,
  `memorySecrets()`, `MemorySecrets`, `AwsSecretsClient`, `GcpSecretsClient`, `AzureSecretsClient`,
  and `VaultSecretsOptions`;
- `LifecycleConfig.credentials`, `CredentialResolver`, `CredentialRequest`, and `ProviderCredential`;
  `ProviderCallContext.tenantId` and `credential`; `OperationTicket.refreshCredential()` and
  `scrub()`;
- `OpenAIProviderConfig.credentialHeader`, `EmbeddingRequest.tenantId`, and
  `EmbeddingProviderError.attempts`.

## 2.12 stage (2.12.0)

Everything 2.12 adds is stable from the start and additive, unless this section says otherwise.

New and stable:
- `nexus-ai-pro/tracing/spool`: `spoolingExporter()`, `SpoolingExporter`, `SpoolingExporterOptions`,
  `TraceBatchSender`, `SpoolStats`, and `SpoolDrainReport`;
- `OtlpTraceExporter.send()`;
- `RunManagerOptions.tracer` and `AgentServerOptions.tracer`, and `RunRecord.traceId`;
- `Deployments.evaluationWorker()` and `DeploymentEvaluationWorkerOptions`; `RunEvaluationOptions`
  gains `traces`, `operations`, `pollMs`, and the worker's options, and `RunEvaluationReport` gains
  `failed`;
- in `nexus-ai-pro/evaluate`: `traceEvidence()`, `mediaParts()`, `EvaluationTrace`, `MediaPart`,
  `TraceToolCall`, `TraceRetrieval`, `TraceDocument`, `TraceModelCall`, `TraceMedia`, and
  `EvaluationContext.trace`;
- `OnlineEvaluationWorkerOptions.withTrace`, `onScored`, and `onError`, and `OnlineScored`;
- `traceRetriever()`, `RetrieverLike`, and `Tracer.within()`;
- `nexus-ai-pro/evaluate/automation`: `automationRules()`, `deploymentScores()`, `traceScores()`,
  `memoryScores()`, `openReview()`, `sendWebhook()`, `markUnhealthy()`, `AutomationRule`,
  `AutomationRules`, `AutomationRulesOptions`, `AutomationEvent`, `AutomationAction`,
  `AutomationStatus`, `AutomationWebhookOptions`, `ScoreSample`, `ScoreSource`, `MemoryScoreSource`,
  `ReviewOpener`, and `RuleStatistic`;
- `Deployments.markUnhealthy()`, `markHealthy()`, and `scores()`, `RunScore`, the `health`
  `DeploymentChange`, `DeploymentRecord.unhealthy`, `RevisionHealthMark`,
  `DeploymentChangeRecord.revision` and `healthy`, and `CanaryDecision.unhealthy`;
- `nexus-ai-pro/evaluate/media`: `mediaChecks()`, `mediaJudge()`, `mediaOf()`, `resolveMedia()`,
  `MediaCheckOptions`, `MediaJudgeOptions`, `MediaResolveOptions`, `MediaSelection`, `MediaRange`,
  `ResolvedMedia`, and `MediaSubjectKind`;
- `nexus-ai-pro/media/catalog`: `mediaCatalog()`, `catalogedAssetStore()`, `MediaCatalog`,
  `MediaCatalogOptions`, `MediaCatalogEntry`, `MediaCatalogQuery`, `MediaCatalogPage`,
  `MediaCatalogStats`, `MediaCatalogRecordOptions`, `CatalogScore`, `CatalogMediaKind`, and
  `CatalogedAssetStoreOptions`;
- `nexus-ai-pro/tracing/otlp-grpc`: `OtlpGrpcTraceExporter`, `OtlpGrpcTraceExporterOptions`, and
  `OtlpGrpcError`;
- in `nexus-ai-pro/tracing/otlp`: `OtlpTraceExporterOptions.protocol`, `encodeOtlpTraceRequest()`,
  `decodeOtlpTraceResponse()`, `OtlpExportRequest`, `OtlpKeyValue`, `OtlpAttributeValue`,
  `OtlpPartialSuccess`, and `OtlpPartialSuccessError`.

Changed: `DeploymentChange` gains a `health` action, so a `switch` over its actions that must be
exhaustive needs a case for it.

Changed: `Deployments.evaluate()` runs through the online-evaluation worker, scoring up to 4 runs at
once rather than one, so evaluators that are not safe to call concurrently need `concurrency: 1`.

## 2.11 stage (2.11.0)

Everything 2.11 adds is stable from the start and additive, unless this section says otherwise.

New and stable:
- `nexus-ai-pro/agent/workspace`: `Workspace`, `WorkspaceGlobOptions`, `WorkspaceGrepOptions`,
  `WorkspaceMatch`, `WorkspacePathError`, `WorkspaceReadOnlyError`, `resolveWorkspacePath()`,
  `memoryWorkspace()`, `fileWorkspace()`, `storeWorkspace()`, `readOnlyWorkspace()`,
  `compositeWorkspace()` and their options, `WorkspaceMount`, `workspaceTools()` and
  `WorkspaceToolsOptions`, and `runWorkspaceConformance()` with its report and check;
- `DeepAgentOptions.workspace`;
- `nexus-ai-pro/telephony/telnyx`, with `TelnyxTelephonyProvider` and its config;
- on `nexus-ai-pro/telephony`: `TelephonyManager.answer()`, `transferCall()`, `joinConference()`,
  `startRecording()`, `stopRecording()`, `parseRecordingCallback()`, `downloadRecording()`,
  `deleteRecording()`, `sendMessage()`, and `parseMessageWebhook()`, the matching optional
  `TelephonyProvider` methods, their request and result types, `TelephonyDialConfig`,
  `TelephonyConferenceConfig`, and `TelephonyResponseRequest.dial`; `recordingKeeper()` with
  `RecordingKeeper`, `RecordingKeeperOptions`, `RecordingRetention`, and `KeptRecording`;
  `telnyxRealtimeAudioOptions()`; the `TelephonyProviderInfo.supports` flags `transfer`,
  `conference`, `recording`, and `messaging`; `TelephonyWebhookValidationRequest.publicKey`;
- `NexusAI.telephony`; the operation family `messaging`;
- `Sandbox` extends `Workspace`, so it gains `writeBytes()`, optional; `SandboxGlobOptions`,
  `SandboxGrepOptions`, and `SandboxMatch` are the workspace's types under their old names, and
  `SandboxPathError` is a `WorkspacePathError`.

Changed, each to keep a promise the surface already made:
- A sandbox's file methods take a leading `/` as the sandbox's root, as the workspace contract
  says; a path that leaves the root is still refused. The container sandbox rejects reading a
  missing file with `ENOENT`.

## 2.10 stage (2.10.0)

Everything 2.10 adds is stable from the start and additive, unless this section says otherwise.

New and stable:
- on `nexus-ai-pro/protocols/a2a`: `A2aHandler`, which `a2aHandler()` now returns, with `owner`,
  `recover()`, and `close()`; `A2aRecoveryReport`; `A2aTaskLease` and `A2aTaskRun`, and
  `A2aTaskRecord.lease`, `run`, and `version`; `A2aTaskStore.claim()`, optional;
  `A2aServerOptions.lease` and `recoverEveryMs`. The handler is still the request function it was;
- on `nexus-ai-pro/protocols/acp`: `AcpTurnRecord` and `AcpSessionRecord.turn`;
- on `nexus-ai-pro/deep-agent`: `DeepSubagent.remote`, `DeepRemoteHelper`, and `DeepRemoteRunResult`;
- `CompiledGraph.continueWith()`, and `ProtocolGraph.continueWith()` and `state()`, both optional;
- on `nexus-ai-pro/lifecycle`: `ProviderHealthOutcome`, `OperationLifecycle.reportHealth()`,
  `OperationLifecycleLike.reportHealth()`, optional, and `LifecycleRuntime.providerHealth`;
- on `nexus-ai-pro/realtime`: `RealtimeSessionConfig.tenantId`, `userId`, `principal`,
  `traceContext`, `conversationStore`, and `resume`; `RealtimeConversationOptions.replayItems`;
  `RealtimeConversationStore`, `MemoryRealtimeConversationStore`, `storeRealtimeConversations()`,
  `replayRealtimeConversation()`, and `REALTIME_REPLAY_PREFIX`; `GeminiLiveProvider` and
  `GeminiLiveProviderOptions`; `RealtimeSession.sendImage()` and `sendVideoFrame()`,
  `RealtimeSessionConfig.video`, `RealtimeImageInput`, `RealtimeImageOptions`, and
  `RealtimeVideoOptions`; `RealtimeTransport.sendVideo()`, optional;
- `RealtimeConversationOptions.retainRawAudio`, `RealtimeConversationReducerOptions.inPlace`, and
  `RealtimePeerConnectionLike.addTransceiver()`, optional;
- `nexus-ai-pro/realtime/gemini-live`, with `GeminiLiveTransport` and `GeminiLiveTransportOptions`;
- `nexus-ai-pro/realtime/openai-sip`, with `answerOpenAISipCalls()` and its types, also on
  `nexus-ai-pro/realtime`;
- on `nexus-ai-pro/realtime/openai-server` and `nexus-ai-pro/realtime`: `acceptOpenAIRealtimeCall()`,
  `rejectOpenAIRealtimeCall()`, `referOpenAIRealtimeCall()`, `hangupOpenAIRealtimeCall()`,
  `verifyOpenAIWebhook()`, `OpenAIRealtimeCallOptions`, `OpenAIWebhookEvent`, `OpenAIWebhookHeaders`,
  and `OpenAIWebhookVerifyOptions`; `OpenAIWebSocketTransportOptions.callId`.

The realtime family stays experimental, and all of these with it.

Changed, each to keep a promise the surface already made:
- A realtime session's raw events keep an audio event's size, `delta_bytes`, in place of its audio,
  unless `retainRawAudio`: a session held every frame it played, about 13 MB a 3-minute call. The
  `openai-events` export follows.
- A realtime session announces `conversation.updated` only to a listener, as before, but no longer
  copies its record for each event when none is listening.
- A realtime session given a lifecycle runs each turn as a `realtime.turn` operation and charges it
  as it finishes; its `realtime.session` operation reports the total and charges nothing more. Its
  usage and cost are the sum of its turns', where each turn's replaced the last before.

## 2.9 stage (2.9.0)

Everything 2.9 adds is stable from the start and additive, unless this section says otherwise.

New and stable:
- `OperationFamily` gains `video`;
- `nexus-ai-pro/video`, `/video/google`, `/video/runway`, and `/video/mock` are new and experimental:
  video generation graduates by the checklist once Veo and Runway pass its conformance suite live;
- `RunQuery.after` with `RunCursor`, and `runCursor()` and `isAfterCursor()` on
  `nexus-ai-pro/tracing`. A store of your own that does not honour `after` still works with the
  online-evaluation worker, which falls back to offsets;
- `OnlineSchedulePass.paging`;
- on `nexus-ai-pro/lifecycle`: `BudgetReservation.id`; `BudgetReservationStatus`,
  `BudgetReservationRecord`, `BudgetReservationOutcome`, `BudgetRecoveryReport`, and
  `BudgetRecoverOptions`; `BudgetLedger.renew()`, `settle()`, and `recover()`, all optional;
  `BudgetLedgerOptions.leaseMs` and `uncertain`; `BudgetLedgerWithReport.reservation()`; and the five
  optional reservation methods of `BudgetUsageStore` with `BudgetReservationChange`. The statuses,
  and which outcome moves a reservation from which status, are part of the promise;
- `hold()`, `settle()`, `renew()`, `lapsed()`, and `reservation()` on `MemoryTenantUsage`,
  `RedisTenantUsage`, and `PostgresTenantUsage`, and the tenancy migration version 2 with its
  reservations table;
- on `nexus-ai-pro/media/rag`, experimental with the media family: `MediaIndexOptions.manifest` and
  `name`, `MediaIndexEntry`, `MediaIndexReader`, `MediaIndexReport.unchanged`, and
  `MediaIndex.delete()`, `reindex()`, `describe()`, and `list()`.

Changed, each to keep a promise the surface already made:
- `TraceStore.query()` orders runs that share a start time by id, from last to first, as code units,
  so a page means the same on every store. Before, that order was not defined.
- `MemoryBudgetUsage`, the ledger's default store, records reservations, so `reserve()` returns one
  with an `id`. An online-evaluation worker reserves every run of its evaluators, a run after a crash
  included, where before it reserved only a first.
- A deep agent with a checkpointer writes the helpers it creates to `<thread>/helpers-created`, so
  `createHelpers.max` holds for a thread across processes, as it says.

## 2.8 stage (2.8.0)

Everything 2.8 adds is stable from the start and additive, unless this section says otherwise.

New and stable:
- `ChromaVectorStoreOptions.filterFields`;
- `EmbeddingCacheStore`, `EmbeddingCacheEntry`, and `EmbeddingCacheConfig` with its `store`;
  `nexus-ai-pro/embeddings/cache`: `embeddingCacheKey()` and `EmbeddingCacheContext`, `packVector()`
  and `unpackVector()`, `MemoryEmbeddingCache` and its options, `RedisEmbeddingCache` with its client
  and options, and `tieredEmbeddingCache()`. A key's derivation and a vector's packed layout are part
  of the promise, so a cache one release wrote is read by the next;
- `EmbeddingModality`, `EmbeddingPart`, and `EmbeddingParts` as `EmbeddingInput`;
  `EmbeddingProviderRequest.contents` with `EmbeddingProviderPart`, `EmbeddingProviderTextPart`, and
  `EmbeddingProviderMediaPart`; `inputModalities` on `EmbeddingProviderCapabilities` and
  `EmbeddingModelCapabilities`, and `costPerImage`; `MockEmbeddingProviderOptions.describeMedia`. An
  adapter that declares no `inputModalities` is sent text only, as before;
- `nexus-ai-pro/embeddings/batch`: `EmbeddingBatchManager` and its config and runtime, its request,
  item, result, and output types, the `EmbeddingBatchProvider` contract with its info, capabilities,
  request, and item, `batchEmbedder()` with `BatchEmbedder` and its options; the OpenAI and mock
  adapters on `/openai` and `/mock`. `GoogleEmbeddingBatchProvider` on `/google` is experimental
  until a live run is recorded;
- on `nexus-ai-pro/images/assets`: `AssetStore.putStream()` and `getStream()`, both optional;
  `putAssetStream()` and `readAssetStream()`, `DEFAULT_BUFFERED_ASSET_BYTES`, `AssetByteSource`,
  `StreamAssetInput`, `AssetByteRange`, `AssetStreamOptions`, `StreamAssetDescriptor`, and
  `PutAssetStreamOptions`; `S3LikeClient.putObjectStream()` and `getObjectStream()`, both optional.
  The images family stays experimental, and these with it;
- `nexus-ai-pro/models/data`: `RegistryData`, `REGISTRY_DATA_FORMAT`, `checkRegistryData()`,
  `parseRegistryData()` with `RegistryDataError`, `bundledRegistryData()`, `withRegistryData()`,
  `diffRegistryData()` with `RegistryDiff` and `RegistryFieldChange`, and `registryDataAgeDays()`;
  `ModelRegistryConfig.verifiedAt` and `DoctorOptions.models`; the commands `nexus models export`,
  `check`, and `refresh`. The `nexus-registry/1` format is read by every 2.x release;
- `nexus-ai-pro/media`, `/media/mistral`, `/media/mock`, and `/media/rag` are new and experimental: media
  understanding graduates by the checklist, once a vision model and the OCR service pass its
  conformance suite live. `OperationFamily` gains `media`, and `AssetProvenance.operation` gains
  `upload`, both stable;
- `IngestionPipelineOptions.batchEmbed` and `batchesInFlight`, and `IngestionReport.batches` and
  `batchesRecovered`. `IngestionPipelineOptions.embed` is optional when `batchEmbed` is given;
- `nexus-ai-pro/postgres/embedding-cache`: `PostgresEmbeddingCache`, its options, and
  `embeddingCacheMigrations()`; `nexus-ai-pro/sqlite/embedding-cache`: `SqliteEmbeddingCache`, its
  options, and `sqliteEmbeddingCacheMigrations()`.

Changed, each to keep a promise the surface already made:
- `OpenAIBatchProvider` refuses `endpoint: '/v1/embeddings'` with a `BatchCapabilityError`; it sent
  chat bodies there, which never embedded anything.
- The Qdrant, Redis, Pinecone, Weaviate, and Chroma stores refuse a `topK` that is not a positive
  integer with a `RangeError`. An absent `topK`, or 0, still means 5.
- A replacement in Chroma removes the metadata fields the new version lacks; before, Chroma merged
  them. A Weaviate filter is typed by the collection's schema, so a value of another type than its
  property matches nothing instead of failing the search.

### Graduation

The Qdrant, Redis, Weaviate, and Chroma vector stores leave experimental status, by the same
checklist as in 2.4. The live evidence comes from the scheduled live workflow, which runs every
check against each server; the stand-in evidence runs in CI on every change.

**The remote vector stores** (`QdrantVectorStore`, `RedisVectorStore`, `WeaviateVectorStore`,
`ChromaVectorStore`):

| Requirement | Evidence |
| --- | --- |
| API stability | Covered by the 2.x compatibility rules, with no planned breaking change |
| Conformance | The vector store contract and typed filters, against each real server, Redis over RESP3 and RESP2: `tests/vector-stores.live.ts`; against stand-ins that fail as the servers do: `tests/vector-stores.test.ts` |
| Crash recovery | n/a: a store keeps no run state, and a reload upserts the same ids |
| Concurrency | Ten writers replacing the same twenty ids at once leave each id as one writer's version, found only by that writer's filters, live and against stand-ins that interleave as a network does |
| Load | 2,000 chunks written, then 200 searches within a p95 budget of 200 ms on each server: `tests/vector-stores.live.ts` |
| Tenant isolation | Each store under `tenantVectorStore()`, live and in `tests/tenant-isolation.test.ts` |
| Security | [Review](./SECURITY.md#the-qdrant-redis-weaviate-and-chroma-vector-stores): four findings fixed, two documented limitations |
| Upgrade | What the published 2.7.0 wrote is read, filtered, replaced, and deleted by this release, live and in `tests/upgrade-remote-stores.test.ts` |
| Soak | 1,000 write, search, and delete cycles on each server, with the heap within 8 MB |

Chroma graduates in both modes. Without `filterFields`, two writers replacing one id at once can
leave a stale filter field, which its guide states; with `filterFields`, a replacement stays whole
under any race.

**Still experimental**, each with what remains:
- **The Pinecone store.** It passes every check against a stand-in, and the live workflow runs the
  same checks against Pinecone once the repository's Pinecone secret is set.
- **Images.** Recorded live conformance must pass on all three backends. The live workflow records it
  for each backend whose credential is set.

## 2.7 stage (2.7.0)

Everything 2.7 adds is stable from the start and additive.

New and stable:
- on `nexus-ai-pro/agent/sandbox`: `Sandbox.glob()`, `grep()`, and `readBytes()`, all optional;
  `SandboxGlobOptions`, `SandboxGrepOptions`, and `SandboxMatch`; `SandboxToolsOptions.maxMediaBytes`;
  and the tool names `glob_files`, `grep_files`, and `read_media`;
- `DocumentContent` and the `document` member of `ContentPart`, exported from
  `nexus-ai-pro/runtime`; the `base64` source of `AudioContent`;
- `nexus-ai-pro/agent/sandbox/container`: `containerSandbox()` and `ContainerSandboxOptions`, with
  their defaults: no network, every capability dropped, no new privileges, 512 MB of memory and swap,
  one CPU, and 256 processes;
- `nexus-ai-pro/agent/interpreter`: `Interpreter`, `InterpreterFunction`, `InterpreterRunOptions`,
  `InterpreterResult`, `InterpreterCall`, `workerInterpreter()` and `WorkerInterpreterOptions`,
  `codeTool()` and `CodeToolOptions` with the tool name `run_code`, and `runInterpreterConformance()`
  with `InterpreterConformanceReport`; `DeepAgentOptions.interpreter`;
- `DeepAgentOptions.createHelpers` and `maxBackgroundHelpers`, `DeepHelperLimits`, the `background`
  and `instructions` arguments of `delegate`, the tool name `collect_helper`, the helper thread id
  `<thread>/helper/<name>@<call id>`, and the helper namespace `<name>@<call id>`;
- `ToolContext.forward`;
- `ToolDefinition.deferred` and `tool({ deferred })`; `ToolReferenceContent` and the `tool_reference`
  member of `ContentPart`, exported from `nexus-ai-pro/runtime`; on `nexus-ai-pro/agent`,
  `toolSearchTool()`, `ToolSearchOptions`, `searchTools()`, `ToolSearchHit`, and `TOOL_SEARCH_NAME`
  with the tool name `search_tools`.

- `nexus-ai-pro/evaluate/generate`: `syntheticDataset()` with `SyntheticDatasetOptions`,
  `SyntheticDocument`, `SyntheticPassage`, `SyntheticDraft`, `SyntheticCitation`, `SyntheticModelClient`,
  and `SyntheticDatasetResult`; `adversarialDataset()` with `AdversarialDatasetOptions`,
  `AdversarialInputs`, `AdversarialExpected`, `AttackCarrier`, and `AttackDisguise`; `resistsAttack()`;
  `promoteIssue()` with `PromoteIssueOptions`;
- on `nexus-ai-pro/security`: `attackSeeds()`, `AttackSeed`, and `AttackCategory`;
- `nexus-ai-pro/evaluate/online`: `onlineEvaluationWorker()`, `OnlineEvaluationWorker`,
  `OnlineEvaluationWorkerOptions`, `OnlineEvaluationWorkerReport`, and `OnlineSchedulePass`; the
  operation kind `online-evaluation:<source>` and the score mark `value.evaluation`;
- `EvaluateOptions.budget` and `EvaluationBudgetError` on `nexus-ai-pro/evaluate`.

An agent with no deferred tool offers exactly the tools it did before. Adversarial example ids and
canaries are stable across runs, so experiments over the same options compare.

A graph run's first checkpoint carries the run's `metadata`, as every later checkpoint already did.

`sandboxTools()` offers the new tools only for a sandbox that implements the methods behind them, so
a sandbox of the application's own offers the same tools as before. A deep agent over
`processSandbox()` gets the three new tools and a line of its system prompt naming them.

On OpenAI, a tool message whose media is not only images is followed by a user message that starts
`Files the tools returned:`; one with only images keeps `Images the tools returned:`.

`ContentPart` and `AudioContent['source']` gain members, as the compatibility rules allow in a minor
release. Code that switches over either exhaustively should handle the new ones.

## 2.6 stage (2.6.0)

Everything 2.6 adds is stable from the start and additive.

New and stable:
- `ToolDefinition.stream`, `ToolContext.progress`, and `ToolBody`; `tool()` with `stream`. The
  AG-UI event name `tool_progress`, and the `progress` and `callId` fields of a tool run's metadata;
- `McpClient.callTool()`'s `progress` option, and `notifications/progress` from `McpServer`;
- `AgUiOptions.stateDeltas`, `messagesSnapshot`, and `frontendTools`, and `AgUiPatchOperation`;
- `CreateAgentOptions.toolSource` and `ToolSourceContext`; `ClientToolDefinition`, the `clientTools`
  channel, and the `AgentStopReason` member `client_tools`;
- the `step` member of `GraphEvent`;
- on `nexus-ai-pro/protocols/a2a`: `A2aTaskStore`, `A2aTaskRecord`, `A2aTaskQuery`,
  `MemoryA2aTaskStore`, `storeA2aTasks()`; `A2aServerOptions.store`, `pushNotifications`,
  `extendedCard`, `pollMs`, and `onError`; `A2aPushOptions` and `A2aPushNotificationConfig`; the
  methods `ListTasks`, `SubscribeToTask`, the four push notification config methods, and
  `GetExtendedAgentCard`; the client's new methods, `A2aListTasksOptions`, and `A2aTaskPage`; and
  the error codes `pushNotificationNotSupported` and `extendedCardNotConfigured`.

- on `nexus-ai-pro/protocols/acp`: `AcpSessionStore`, `AcpSessionRecord`, `MemoryAcpSessionStore`,
  `storeAcpSessions()`, `AcpOptions.sessions` and `editor`, `AcpEditorBridge`, `AcpConnection`,
  `AcpClientCapabilities`, `session/load`, image and audio blocks, and the fourth argument of
  `AcpOptions.input`;
- `nexus-ai-pro/protocols/acp/editor`: `acpEditor()`, `AcpEditor`, `AcpEditorOptions`, and the tool
  names `editor_read_file`, `editor_write_file`, and `editor_run_command`.

An AG-UI run reports a node's tasks running side by side as one step, and finishes every step it
started before `RUN_FINISHED` or `RUN_ERROR`. A2A `ListTasks` refuses a status that is not a task
state, and reads `TASK_STATE_UNSPECIFIED` and `UNRECOGNIZED` as no filter.

A streamed A2A task now reports its move to `TASK_STATE_WORKING` as a status update, before its
other events.

`GraphEvent` and `AgentStopReason` gain members, as the compatibility rules allow in a minor
release. Code that switches over either exhaustively should handle the new ones.

## 2.5 stage (2.5.0)

Everything 2.5 adds is stable from the start and additive.

New and stable:
- `putIfVersion()`, optional on `Store` and `ServerStateStore`, and its implementations;
  `Deployments.atomicChanges`; `ScalingSnapshot.atomicChanges`;
- `nexus-ai-pro/postgres/tenancy`: `PostgresTenantUsage`, `PostgresTenantUsageOptions`,
  `tenantUsageMigration()`, and `tenantUsageMigrations()`; the `tenancy` member of
  `PostgresAdapter`;
- `OperationStoreFilter.kinds`, `OperationStoreStats.queuedByKind`, `RevisionRequest.queued`,
  `RevisionChoice.local`, `ScalingSnapshot.queuedByRevision`, and the
  `nexus_server_runs_queued_by_revision` metric.

- signing on `nexus-ai-pro/context-hub`: `ContextHubOptions.signer`, `keyring`, and `requireSignature`;
  `ContextHub.verify()` and `ContextVerification`; `ContextBundle.signatures`; `ContextSignatureError`;
  `generateContextKey()`, `ed25519Signer()`, `ed25519Keyring()`, `contextDigest()`, and the signing
  types. The signed digest's format is `nexus-context-signature`, format version 1, and a later release
  verifies it;
- `metadata.context` on a traced model call;
- on `nexus-ai-pro/server/deployments`, experimental with the rest of it: `BucketStrategy`,
  `DeploymentRecord.bucketStrategy`, `RevisionedAssistantOptions.bucketStrategy`, the `bucketing`
  change and `Deployments.bucketing()`, the strategy argument of `bucket()`; `recordRun()`,
  `rollups()`, `flushRollups()`, `stats({ deployment })`, `DeploymentRollup`, `RevisionRollup`, and
  `DeploymentsOptions.rollupMs`; `CanaryGuardOptions.from`. `ServerDeployments` gains an optional
  `recordRun()`, and `RunManagerOptions.deployments`. The positions `fnv1a-v1` and `hash-v2` give a key
  never change;
- the doctor's `settings` check; `RedisOperationStore.indexed`; `Tracer.store` and
  `Tracer.incremental`;
- `nexus-ai-pro/evaluate/threads`, experimental: `simulatedUser()`, `scriptedUser()`,
  `conversationTarget()`, `graphThreadAgent()`, `formatThread()`, `goalReached()`,
  `goalCompletion()`, `turnCount()`, `turnScores()`, and their types. The score keys `goal_reached`,
  `goal_completion`, `turns`, and `turn_score` are stable once the entry point graduates.

The studio now refuses, as the compatibility rules allow for security: a change whose body is not
`application/json`, with `415`; a body that is not a JSON object, with `400`; a body past
`maxBodyBytes`, with `413`; and a list `limit`, `hours`, or `days` past its ceiling, with `400`. With
accounts, only a bearer `Authorization` header stands in for the page token. The agent server refuses
an assistant or revision named after a built-in property, such as `constructor`. Also stable from
2.5: `StudioOptions.maxBodyBytes`, `Studio.routes` and `StudioRoute`, and
`MemoryStudioJournalOptions.maxComments`.

### Graduation

Two more surfaces leave experimental status in 2.5, by the same checklist as in 2.4. Every test named
here runs in CI.

**Deployments** (`nexus-ai-pro/server/deployments`):

| Requirement | Evidence |
| --- | --- |
| Conformance | Atomic changes on the memory server store and the memory, Redis, Postgres, and SQLite stores: `tests/atomic-deployments.test.ts`; routing, canaries, and guards: `tests/deployments.test.ts`, `tests/fair-canaries.test.ts`; revision pools: `tests/revision-pools.test.ts` |
| Crash recovery | A change is one compare-and-set write, so a crash leaves the old record or the new one; a drained replica hands its runs to another: `tests/deployments.test.ts`. A replica that dies loses at most its last `rollupMs` of counts, which the guide states |
| Concurrency | Ten replicas making mixed changes at once land in one linear history on every store: `tests/atomic-deployments.test.ts` |
| Load | `deployments.route-canary` and `deployments.bucket` in `surface-budget.json` |
| Tenant isolation | n/a: a deployment is an assistant's configuration, not a tenant's data. The runs it routes are operations, which are isolated: `tests/tenant-isolation.test.ts` |
| Security | [Review](./SECURITY.md#deployments): four findings fixed, two documented limitations |
| Upgrade | A 2.3 replica's deployment routes the same here, and every thread keeps its bucket: `tests/upgrade-previous-minor.test.ts` |
| Soak | 50,000 route, admit, and release cycles, 500 replica lifecycles, and 20,000 runs counted and judged from rollups, with no timer left running |

**The studio and its accounts** (`nexus-ai-pro-studio`):

| Requirement | Evidence |
| --- | --- |
| Conformance | Every view over its sources, with and without accounts: `studio/test/studio.test.ts`, `studio/test/team.test.ts`, `studio/test/deployments.test.ts` |
| Crash recovery | n/a: the studio keeps no state but its journal, which appends one line per entry and skips a line a crash cut short: `studio/test/security.test.ts` |
| Concurrency | n/a: every change goes through its source's own API, where races are settled: prompt and bundle labels by compare-and-set, deployments by `putIfVersion()` |
| Load | 500 signed-in reads within a budget relative to the machine: `studio/test/security.test.ts` |
| Tenant isolation | A studio over `tenantScope()` views lists one tenant's runs, and another tenant's trace is not found: `studio/test/security.test.ts` |
| Security | [Review](./SECURITY.md#the-studio-and-its-accounts): ten findings, all fixed; every route fuzzed by every role and with hostile input: `studio/test/security.test.ts` |
| Upgrade | The journal reads what 2.4 wrote, and accounts and configuration load unchanged: `studio/test/security.test.ts`, `studio/test/team.test.ts` |
| Soak | 2,000 sign-in, read, and change cycles with flat memory, and 200 servers started and closed with no handle left open: `studio/test/security.test.ts` |

**Still experimental**, each with what remains:
- **Images.** Recorded live conformance must pass on all three backends. The scheduled live workflow
  records it wherever a backend's credential is set, and none has run with all three yet.
- **The Qdrant, Redis, Pinecone, Weaviate, and Chroma vector stores.** The contract must pass against
  the real servers. The scheduled live workflow runs it against each one, Pinecone when its secret is
  set, and its evidence is published when it has passed against all five.

A queued run's kind now names its revision, as `assistant:<id>@<revision>`. Code that reads the
server's runs by the `assistant:` prefix, as the server itself does, is unaffected.

A state store without `putIfVersion()` keeps its earlier behavior.

## 2.4 stage (2.4.0)

Everything 2.4 adds is stable from the start and additive. These security fixes tighten behavior,
as the compatibility rules allow:
- the upload scanner checks the larger of the declared and actual sizes;
- an allowlist refuses a file with no MIME type;
- textual bytes are scanned;
- secret findings carry a preview instead of the value;
- card and phone matches must pass their checks.

New and stable:
- `confidence`, `minErrorRateIncrease`, `minFeedbackDrop`, and `resamples` on `watchCanaries()`;
  the same options and `seed` on `compareRuns()`; `Regression.interval`;
- `Deployments.evaluate()` with `RunEvaluationOptions` and `RunEvaluationReport`;
- `RunRecord.feedback`, `RunManager.addFeedback()`, and `POST /runs/:id/feedback`;
- `nexus-ai-pro/adapter-kit`: the `define*()` builders, `Adapter`, `AdapterDefinition`,
  `AdapterReport`, `AdapterError` and its codes, the contract runners, and the capability types.
  Contract checks may be added in a minor release when they test something the contract already
  promised. A new check for a new promise arrives behind a capability an adapter must declare.
- `nexus-ai-pro/protocols/ag-ui`, `nexus-ai-pro/protocols/a2a`, and `nexus-ai-pro/protocols/acp`:
  the handlers, `a2aClient()`, `a2aTool()`, `serveAcp()`, and their types. Each speaks the
  protocol version it names. A minor release may send an event or answer a method it did not
  before, as a protocol adds them. An event or field already sent keeps its meaning. A new major
  version of a protocol arrives beside the old one, not in its place.
- `nexus-ai-pro/runtime`, `runtimeInfo()`, `RuntimeInfo`, and `RuntimeName`, which may gain
  names in a minor release; the `fetch` option on the OpenAI, Anthropic, Google, and Cohere
  provider configurations.
- Portability, for the client, the kernel, the provider adapters, graphs, agents, and the agent
  protocols: none of them starts needing Node.js in a minor release, and the build fails if one
  does.

Without `confidence`, a canary is judged as before.

These security fixes tighten more behavior, each closing a finding of a graduation review in
[SECURITY.md](./SECURITY.md#threat-reviews):
- the Git loader never follows a symbolic link, nor a path that leaves the checkout;
- a gzipped sitemap may inflate to 50 MB at most, the sitemap protocol's maximum;
- a local MCP server inherits only what a process needs to run, not this process's environment.
  `inheritEnv` restores the old behavior, by name or entirely;
- a canary is judged on runs, so each run counts once per feedback key, as the mean of its scores;
- the file prompt store's label compare-and-set holds within one process;
- changes to one deployment through one `Deployments` object apply in order.

### Graduation

A surface left experimental status in 2.4 only when every requirement in
[How an item graduates](#how-an-item-graduates) had evidence. That evidence is below.
"n/a" names a requirement that does not apply, and says why. Every test named here runs in CI.
Load and soak budgets are checked by `npm run bench:surfaces` and `npm run bench:runtime`, which are
part of `check:release`. Upgrade tests run against the published 2.3.0 package, installed as a
development alias.

Every graduated surface is covered by the 2.x compatibility rules, with no breaking change planned.
That is the "API stability" requirement, met by all of them.

**The SQLite adapters** (`nexus-ai-pro/sqlite` and its subpaths):

| Requirement | Evidence |
| --- | --- |
| Conformance | The operation store, store, and vector store contracts: `tests/sqlite.test.ts`, `tests/vector-stores.test.ts` |
| Crash recovery | A crash at every durable boundary resumes to the uninterrupted state: `tests/crash-injection.test.ts` |
| Concurrency | Ten workers race one thread, one idempotency key, one queue, and one budget: `tests/ten-workers.test.ts` |
| Load | `checkpoint.sqlite` and `operations.claim-10k-queued` in `runtime-budget.json`; `sqlite.vectors-query-2k` and `sqlite.store-put-get-search` in `surface-budget.json` |
| Tenant isolation | Every SQLite store: `tests/tenant-isolation.test.ts` |
| Security | [Review](./SECURITY.md#sqlite-adapters): no findings |
| Upgrade | A 2.3 file upgrades in place, and a 2.3 worker shares its queue: `tests/upgrade-previous-minor.test.ts`; likewise from 2.0: `tests/upgrade.test.ts` |
| Soak | 20,000 queue lifecycles and 2,000 open, migrate, and close cycles, with flat memory and no handle left open |

**The Postgres and SQLite vector stores** (`PostgresVectorStore`, `SqliteVectorStore`):

| Requirement | Evidence |
| --- | --- |
| Conformance | The vector store contract on pgvector and on SQLite: `tests/vector-stores.test.ts`. The retriever contract over each: `tests/graduation-conformance.test.ts` |
| Crash recovery | n/a: a store keeps no run state, and a reload upserts the same ids |
| Concurrency | Ten writers upserting the same ids at once leave one whole row per id: `tests/graduation-conformance.test.ts` |
| Load | `sqlite.vectors-query-2k` and `postgres.vectors-query-1k` in `surface-budget.json` |
| Tenant isolation | Both stores: `tests/tenant-isolation.test.ts` |
| Security | [Review](./SECURITY.md#the-postgres-and-sqlite-vector-stores): no findings |
| Upgrade | A table 2.3 wrote is searched and filtered here: `tests/upgrade-previous-minor.test.ts` |
| Soak | 1,000 write, search, and delete cycles on pgvector |

**The loaders** (`nexus-ai-pro/loaders/*`):

| Requirement | Evidence |
| --- | --- |
| Conformance | Every format, the web loader through the SSRF-safe fetch, and Git cloned and in place: `tests/loaders.test.ts` |
| Crash recovery | n/a: a loader keeps no state, and reloading replaces the same chunks |
| Concurrency | n/a: a loader shares no records |
| Load | `loaders.markdown-csv-html` in `surface-budget.json` |
| Tenant isolation | n/a: a loader stores nothing; the store it loads into is isolated |
| Security | [Review](./SECURITY.md#loaders): two findings, both fixed |
| Upgrade | The same files give the same ids and text as in 2.3: `tests/upgrade-previous-minor.test.ts` |
| Soak | 2,000 load cycles |

**The retrievers** (`nexus-ai-pro/rag/retrievers`):

| Requirement | Evidence |
| --- | --- |
| Conformance | The retriever contract on `KeywordIndex`, on a vector retriever over memory, SQLite, and pgvector, and on `hybridRetriever()`: `tests/graduation-conformance.test.ts` |
| Crash recovery | n/a: an index is held in memory, and rebuilt by loading |
| Concurrency | n/a: an index belongs to one process |
| Load | `retrievers.keyword-query-5k` and `retrievers.hybrid-query` in `surface-budget.json` |
| Tenant isolation | Filters narrow every retriever, by the contract; the stores behind them are isolated |
| Security | [Review](./SECURITY.md#retrievers): no findings |
| Upgrade | The same corpus ranks the same, score for score, as in 2.3: `tests/upgrade-previous-minor.test.ts` |
| Soak | 50,000 add and delete cycles |

**The MCP registry** (`nexus-ai-pro/mcp/registry`):

| Requirement | Evidence |
| --- | --- |
| Conformance | Servers over the protocol in process and as a real child process: `tests/mcp-registry.test.ts` |
| Crash recovery | A server that fails to start is reported, and the rest serve: `tests/mcp-registry.test.ts` |
| Concurrency | n/a: a registry shares no records |
| Load | `mcp.registry-tools-10-servers` in `surface-budget.json` |
| Tenant isolation | n/a: a registry stores nothing |
| Security | [Review](./SECURITY.md#mcp-registry): one finding, fixed |
| Upgrade | A configuration reads the same as in 2.3: `tests/upgrade-previous-minor.test.ts` |
| Soak | 1,000 connect, call, and close cycles, with no handle left open |

**The context hub** (`nexus-ai-pro/context-hub`):

| Requirement | Evidence |
| --- | --- |
| Conformance | The same behavior on memory, files, Redis, and Postgres: `tests/graduation-conformance.test.ts` |
| Crash recovery | A commit is content-addressed, so repeating an interrupted one writes the same version: `tests/context-hub.test.ts` |
| Concurrency | Two hubs racing for one label never both win, on every backend: `tests/graduation-conformance.test.ts` |
| Load | `context-hub.commit-resolve` in `surface-budget.json` |
| Tenant isolation | Context bundles: `tests/tenant-isolation.test.ts` |
| Security | [Review](./SECURITY.md#context-hub): one finding, fixed |
| Upgrade | A bundle has the same version as in 2.3, and a 2.3 export imports: `tests/upgrade-previous-minor.test.ts` |
| Soak | 2,000 commit, resolve, and render cycles |

**Insights** (`nexus-ai-pro/insights`):

| Requirement | Evidence |
| --- | --- |
| Conformance | The same issues from runs in memory and in Postgres: `tests/graduation-conformance.test.ts`; proposals in memory and in files: `tests/insights.test.ts` |
| Crash recovery | n/a: analysis is read-only, and a proposal is one write |
| Concurrency | n/a: a proposal is decided once, by a person |
| Load | `insights.find-issues-5k-runs` in `surface-budget.json` |
| Tenant isolation | Insights read through the trace store, which is isolated: `tests/tenant-isolation.test.ts` |
| Security | [Review](./SECURITY.md#insights): no findings |
| Upgrade | A 2.3 proposal reads here, and errors group under the same signatures: `tests/upgrade-previous-minor.test.ts` |
| Soak | 300 clustering cycles |

**Tenant limits and the server's worker queue** (`nexus-ai-pro/server/tenancy`, and the queue
`createAgentServer()` runs on its operation store):

| Requirement | Evidence |
| --- | --- |
| Conformance | Tenant usage in memory and in Redis; the queue on every operation store: `tests/deployments.test.ts`, `tests/operations.test.ts` |
| Crash recovery | A crash at every durable boundary resumes to the same state, and a draining worker hands a run to another: `tests/crash-injection.test.ts`, `tests/deployments.test.ts` |
| Concurrency | Ten workers drain one queue, and spend one tenant budget without over-granting a slot: `tests/ten-workers.test.ts` |
| Load | `tenancy.admit-release` in `surface-budget.json`; `operations.claim-10k-queued` and `server.sse-fanout` in `runtime-budget.json` |
| Tenant isolation | Operations: `tests/tenant-isolation.test.ts` |
| Security | [Review](./SECURITY.md#tenant-limits-and-the-worker-queue): no findings |
| Upgrade | A 2.3 worker and a 2.4 worker drain one queue: `tests/upgrade-previous-minor.test.ts` |
| Soak | 50,000 admit and release cycles, and 500 replica lifecycles with no timer left running |

**Still experimental**, each with what remains:
- **Deployments** (`nexus-ai-pro/server/deployments`). Two replicas changing one deployment in the
  same instant can lose one change, because the state store has no atomic compare-and-set. Every
  other requirement has evidence above or in its review.
- **Images.** Recorded live conformance must pass on all three backends. The scheduled live workflow
  now records it, wherever a backend's credential is set.
- **The Qdrant, Redis, Pinecone, Weaviate, and Chroma vector stores.** The contract must pass against
  the real servers, not only stubs. The scheduled live workflow now runs it against each one as a
  service container, and Pinecone runs when its secret is set.
- **The studio and its accounts.** A threat review of its sign-in, sessions, and roles, and a soak
  of its server, are still to do.

## 2.3 stage (2.3.0)

Everything 2.3 adds is stable from the start and additive: no default changes meaning.

- `ToolDefinition.capabilities` and the capability grammar; `permissionPolicy()`, `PermissionPolicy`,
  `PermissionRules`, `PermissionRequest`, `PermissionVerdict`, `PermissionDecision`,
  `PermissionPolicyLike`, `parseCapability()`, `capabilitiesOf()`, `isSensitiveCapability()`, and
  `normalizePath()` (`nexus-ai-pro/agent/permissions`); `CreateAgentOptions.permissions`;
- the sandbox contract: `Sandbox`, its option, result, and isolation types, `sandboxTools()`,
  `runSandboxConformance()` and its report types, `SandboxPathError` (`nexus-ai-pro/agent/sandbox`).
  `processSandbox()` is a development reference and may change; conformance checks may be added;
- `NodeOptions.effects` and `interrupts`, `CompileOptions.tools`, `GraphToolDescription`, and the lint
  codes `SENSITIVE_TOOL_WITHOUT_APPROVAL`, `UNDECLARED_TOOL_CAPABILITIES`, and
  `SIDE_EFFECT_BEFORE_INTERRUPT`;
- `nexus-ai-pro/agent/middleware` and every middleware and option type on it;
  `AgentMiddleware.wrapModelCall`, `AgentMiddlewareContext`, `AgentModelCallContext`, the third
  argument of `wrapToolCall`, `next(call)`, and `AgentStopReason` `'stopped'`. The PII patterns
  may be tightened in a minor release to cut false positives, and new `PiiKind` members may be
  added;
- `nexus-ai-pro/server/auth`: `jwtAuth()`, `apiKeyAuth()`, `trustedProxyAuth()`, `anyAuth()`,
  `createJwtVerifier()`, `hashApiKey()`, `JwtError` and its codes, and every option type. New
  `JwtAlgorithm` and `JwtErrorCode` members may be added;
- `Principal.roles`, `method`, and `claims`; `GraphRunOptions.principal`, `NodeContext.principal`,
  `WorkflowRunOptions.principal`, and `WorkflowContext.principal`; `ToolContext` and the second
  argument of `ToolDefinition.execute`; `AgentMiddlewareContext.principal` and
  `PermissionRequest.principal`. A checkpoint's `metadata.userId` is recorded when a run has a
  principal with a subject;
- `SparseRetriever`; `PostgresKeywordIndex` and `keywordIndexMigrations()`
  (`nexus-ai-pro/postgres/fulltext`); `ElasticsearchKeywordIndex` and `ElasticsearchError`
  (`nexus-ai-pro/rag/elasticsearch`); and every reranker and option type on
  `nexus-ai-pro/rag/rerankers`. The default endpoints of hosted rerankers follow their providers'
  current API versions;
- `nexus-ai-pro/rag/pipeline`: `createIngestionPipeline()`, `ingestionExecutor()`, and their types.
  The manifest's layout under `['nexus', 'ingestion', <name>]` is stable: a later release reads a
  manifest an earlier one wrote;
- `nexus-ai-pro/deep-agent`: `createDeepAgent()`, `DeepAgentOptions`, `DeepSubagent`, and
  `DeepSkill`, and the names and arguments of the tools it gives the model (`write_todos`,
  `delegate`, `load_skill`, `edit_file`). The wording of its system prompt may change in any release.

## 2.2 stage (2.2.0)

Everything 2.2 adds is stable from the start and additive: no default changes meaning.

- versioned migrations: `SchemaMigration`, `AppliedMigration`, `MigrationStatus`, `MigrationResult`,
  `SchemaMigrationError`, `migrationChecksum()`, `applyPostgresMigrations()`,
  `postgresMigrationStatus()`, `postgresMigrations()`, `PostgresMigrateOptions`, every adapter's
  `…Migrations()`, and the SQLite twins (`nexus-ai-pro/postgres`, `nexus-ai-pro/postgres/migrations`,
  `nexus-ai-pro/sqlite`, `nexus-ai-pro/sqlite/migrations`). A migration, once released, never
  changes; later releases add versions. `migrate()` now resolves to a `MigrationResult` instead of
  nothing;
- `nexus db status` and `nexus db migrate`, and `nexus db sql --record`;
- `RedisOperationStoreOptions.index` and `reindex()`;
- `CircuitBreakerConfig.shareObservations`, `CircuitStateStore.observe()`, `CircuitObservation`,
  `CircuitWindow`, and `CircuitSnapshot.shared`;
- `PostgresRollupStore` (`nexus-ai-pro/postgres/rollups`);
- the tenant model (`nexus-ai-pro/tenancy`): every `tenant…()` view, `tenantScope()`,
  `TenantAssetStore`, `TenantStores`, `assertTenantId()`, `TenantIdError`, and `TENANT_NAMESPACE`.
  The prefix a view stores names under (`tenant/<id>/`) is part of the contract and does not change
  within 2.x; `GraphRunOptions.tenantId`, `NodeContext.tenantId`, the `tenantId` options of `state()`,
  `history()`, `fork()`, and `updateState()`, `OperationRecord.tenantId`,
  `OperationSubmitOptions.tenantId`, `OperationStoreFilter.tenantId`, `RateLimitedRequest.tenantId`,
  and `rateLimit.key: 'tenantId'`;
- `CacheConfig.namespace`, `CacheNamespace`, `CacheOutcome` and `ResponseMeta.cache`,
  `CompletionRequest.responseCache`, `MemoryCache.lookup()`, and the `scope` of `SemanticCache`'s
  `get()` and `set()`;
- `diagnose()` and its types (`nexus-ai-pro/doctor`), and `nexus doctor`. Checks may be added in a
  minor release; a check's id and what makes it fail do not change within 2.x;
- workflow steps' `timeout`, `onError`, `StepTimeout`, `StepFailure`, `StepRecovery`,
  `StepContext.heartbeat()`, `WorkflowOptions.stepDefaults`, `WorkflowRunOptions.control` and
  `tenantId`, `WorkflowContext.tenantId`, and `WorkflowStepTimeoutError.kind`;
- `RunHandOffError`, `AssistantRunContext.saveProgress()` and `progress`.

Three races are closed rather than added: a busy thread is claimed atomically, and the in-memory and
Redis operation stores refuse a second record under a held idempotency key, as the SQL stores
already did. The semantic cache no longer matches across tenants, and a failing response cache no
longer fails a request.

## 2.1 stage (2.1.0)

Everything 2.1 adds is stable from the start and additive: no default changes meaning.

- graph durability modes, `flush()`, `maxPendingWrites`, `onError` with `NodeFailure` and
  `RecoveredFailure`, `nodeDefaults`, `NodeTimeout`, `context.heartbeat()`, `SendOptions`,
  `RunControl` and `RunControlLike`, and `GraphDrainedError` (`nexus-ai-pro/graph`);
- `lintGraph()` and its finding types (`nexus-ai-pro/graph/lint`). New rules may be added in a minor
  release; a rule's code and severity do not change within 2.x;
- `OperationContext.previousHeartbeat` and `OperationRecord.heartbeatDetails`;
- `AssistantRunContext.control`;
- the graph event stream: `events()`, `resumeEvents()`, `continueEvents()`, `GraphEventStream`, its
  event and option types, `GraphStreamOverflowError`, `context.message()` and `context.tool()`, and
  `subgraphEvents`. New projections may be added in a minor release;
- `TracerOptions.incremental` and `exporters`, `TraceExporter`, `traceparent` in and out, `flush()`,
  `closeAbandonedRuns()`, `w3cTraceId()` and `w3cSpanId()` (`nexus-ai-pro/tracing`);
- `OtlpTraceExporter` and `runToOtlpSpan()` (`nexus-ai-pro/tracing/otlp`). Attributes may be added as
  the GenAI conventions settle; the ones listed in the tracing guide stay;
- rollups (`nexus-ai-pro/tracing/rollups`), timed replay (`timing`, `pace`), evaluator provenance
  (`withProvenance()`, `Experiment.evaluators` and `framework`, `LLMJudge.asEvaluator()`),
  `createAgent({ streamTokens })`, and `graphAssistant(graph, { events })`;
- `rateLimit.algorithm` and `burst`, `RateLimitStore.gcra()`, `RateLimitDecision`, and `gcraDecide()`;
  `health.observationTtlMs`, `ProviderHealthStatus`, a snapshot's `status` and `stale`, and
  `checkProviders({ staleOnly })`.

The studio's `rollups` source follows the studio's experimental label.

## 2.0 stage (2.0.0)

2.0 removes what 1.x deprecated and promotes the surfaces that have settled.

**Promoted to stable.** These follow the 2.x rules without an experimental label:

- graphs, with their advanced APIs: `Send`, `Command`, breakpoints, `updateState()`, `fork()`, node
  caching, and functional workflows (`nexus-ai-pro/graph`, `/graph/functional`, `/graph/visualize`);
- the store (`nexus-ai-pro/store`, `/store/redis`), agents (`nexus-ai-pro/agent`), and tracing
  (`nexus-ai-pro/tracing`);
- evaluation (`nexus-ai-pro/evaluate`) and prompts (`nexus-ai-pro/prompts` and `/postgres/prompts`);
- the agent server (`nexus-ai-pro/server`): its routes, `RunRecord`, `ThreadRecord`, `RunEvent`, and
  recovery.

**New in 2.0, and stable from the start:**

- the operation lifecycle: the stage order, `LifecycleConfig`, `LifecycleHooks`, the
  `OperationDescriptor`, `OperationOutcome`, and `OperationTicket` shapes, the `BudgetLedger`
  contract, and `ProviderCallContext`. New families and stages may be added in a minor release;
  existing ones keep their names;
- mixed content: `AssetContent`, `ToolOutput`, and `response.assets`;
- checkpoint schema version 2. Both checkpointers read version 1 throughout 2.x.

**Still experimental in production readiness**, each following the 2.x rules all the same:

- images, until recorded live conformance passes on all three backends;
- the studio package;
- deployments, tenant limits, and the server's worker queue (1.24);
- the context hub, insights, and the studio's accounts (1.23);
- the loaders, the vector stores added in 1.20 and 1.22, the retrievers, and the MCP registry;
- the SQLite adapters (1.21).

**Root import.** The root exports the core client, its config builders, its types, the errors it
throws, the lifecycle, and `tool()` and `toolOutput()`. Adding anything else to the root waits for a
major release, and a check in CI fails when the root grows.

**Dependencies.** The package has no required dependency. `zod`, `ajv`, `ajv-formats`, and
`@types/node` are optional peers; making any of them required again would need a major release.

## Capability negotiation stage

`negotiateCompletionRequest()`, `NexusCapabilityError`, `CapabilityPolicy`, `CapabilityWarning`, and
the `nexus-ai-pro/capabilities` subpath are public and follow the 1.x rules.

Which options a given model accepts is registry data, not a compatibility guarantee. A model's
declared capabilities, cache lifetimes, reasoning effort levels, and prices can change in a minor
release when a provider changes its lineup, and new normalized warning entries may be added. What is
guaranteed is the behavior of each policy: `strict` throws before the provider call, `warn` records a
warning and continues, `off` sends the request unchanged, and an option a model does not mention is
always passed through rather than refused.

`ResponseMeta.usage` and `ResponseMeta.cost` are public normalized shapes. Reported token counts and
prices originate with the provider or with bundled defaults and are not compatibility guarantees;
`cost.basis` distinguishes the two. `ResponseMeta.estimatedCost` is deprecated in favor of
`cost.amount` and will be removed in the next major release.

## Telephony API stage

The `nexus-ai-pro/telephony`, `telephony/twilio`, and `telephony/realtime-bridge` subpaths are public
and follow the 1.x rules. This covers `TelephonyManager`, the `TelephonyProvider` contract, the
normalized call, media-stream, status-callback, and phone-number types, the TwiML helpers, and the
realtime bridge.

Provider payloads under `raw`, TwiML markup details beyond the documented helper options, webhook
signature formats, upstream call-status vocabularies, and carrier behavior are controlled by the
provider and are not normalized guarantees. `TelephonyProvider` methods are optional by design, so a
provider adapter may implement any subset; adding a new optional method is a minor change.

## Realtime API stage

The opt-in `nexus-ai-pro/realtime` family is public in the current development line. This includes the
explicit `realtime/session`, `realtime/tools`, `realtime/conversation`, `realtime/openai-webrtc`,
`realtime/openai-websocket`, `realtime/openai-server`, and `realtime/mock` subpaths. Its exported symbols,
normalized event names, configuration fields, and conversation/export shapes follow the same patch
compatibility rules as the rest of the package.

The realtime surface follows the 1.x compatibility rules for its normalized public API. Provider-specific
wire events returned by `openai-events`, values
under `raw`, SDP/ICE behavior, and upstream model or voice availability are controlled by the provider
and are not normalized compatibility guarantees. New normalized event variants or optional metrics may
be added in a minor release. Incompatible normalized API changes require a major release unless an urgent
security fix makes preserving the old behavior unsafe.

Platform interfaces for WebRTC and WebSocket are structural so applications can inject browser, server,
mobile, or test adapters without a framework dependency. A documented structural member is public;
private transport internals and unexported protocol helpers are not.

`VoiceSession` and `RealtimeSession` are independent public APIs. `VoiceSession` remains a batch,
turn-oriented transcription/completion/speech workflow. `RealtimeSession` owns a persistent transport,
live events, interruption, and normalized realtime conversation state. Neither is a compatibility alias
for the other.

## Image API stage

The `nexus-ai-pro/images` family is experimental in production-readiness, but published normalized
types, manager methods, operation events, and explicit subpath exports still follow the 1.x semantic
versioning rules. The experimental label does not permit an incompatible minor or patch release.

Provider payloads under `raw`, provider-specific error metadata, temporary delivery URLs, upstream model
availability, and generative output are not normalized compatibility guarantees. The current local
operation handle is process-bound and does not promise durable recovery, distributed cancellation, or
exactly-once provider execution. Those capabilities will use additive adapters and contracts when added.
`MemoryAssetStore` is likewise process-local and does not promise cross-process durability or shared
tenant state.

### Image portability (1.10.0)

The following are public under the same 1.x rules. None is exported from the root or from
`nexus-ai-pro/images`:

- `nexus-ai-pro/images/google`
- `nexus-ai-pro/images/comfyui`
- `nexus-ai-pro/images/transform`, which re-exports the PNG codec and header helpers
- `nexus-ai-pro/images/inputs`
- `nexus-ai-pro/images/moderation`
- `nexus-ai-pro/images/evals`

The following changes are additive:

- `ImageConfig.inputResolver` and `ImageConfig.tenantId` are optional. With no resolver, requests are
  handled exactly as before.
- `ImageAssetResolver` and `ImageAssetResolverContext` are new types.
- Output from the new modules is still generative. Backend wire shapes such as Imagen `:predict` and
  the ComfyUI queue API are upstream contracts outside this policy, like `raw`.

Behaviour that changed:

- **OpenAI masks and transparency.** `OpenAIImageProvider` now declares `supportsMask: true` and
  `supportsTransparency: true`, and accepts masked edits. A mask whose size differs from the input is
  still refused unless `resizeMode` allows resampling. `background: 'transparent'` is refused only with
  JPEG output.
- **Withheld outputs.** A provider finding with `source: 'output'`, `action: 'block'`, and
  `metadata.withheld: true` describes an image the provider removed before responding. The manager
  accepts one fewer asset per such finding, and does not let that finding block the images that were
  returned. Any other blocking finding still blocks the whole result.
- **Masked-edit conformance case.** `runImageProviderConformance` adds a `masked-image-edit` case for
  providers that declare `supportsMask`. Pass `testMask: false` to skip it.
- **`UrlPolicyError`.** Safe-fetch policy refusals are now this subclass of `Error`. Their messages are
  unchanged.

## Embeddings API stage

The `nexus-ai-pro/embeddings` family is public and follows the 1.x rules, including the explicit
`embeddings/adapters`, `embeddings/mock`, and `embeddings/models` subpaths. This covers
`EmbeddingManager`, `ai.embed()` and `ai.embedOne()`, the `EmbeddingsProvider` contract, the
normalized request, embedding, and meta shapes, the error subclasses, and `toEmbeddingFunction()`.

`EmbeddingsProvider` is a distinct contract from the pre-existing `EmbeddingProvider` function type
used by `MemoryVectorStore`, the semantic cache, and RAG ingestion. That function type is unchanged
and remains supported; `toEmbeddingFunction()` converts one into the other.

Two behaviors are guaranteed and differ deliberately from completions. Vectors are returned in input
order regardless of how the request was split, deduplicated, or served from cache. And an option the
target model or adapter declares it cannot honor is **refused**, not dropped, because a vector built
with different dimensions or a different input type is silently incompatible with vectors already in
a store. An option nothing in the registry describes is still passed through, on the same
absence-means-unknown rule the completion path uses.

Embedding model entries are registry data, not compatibility guarantees. Bundled dimensions, input
limits, batch limits, and prices can change in a minor release when a provider changes its lineup,
and are overridable through `embeddings.models.registry`. Which concrete model an alias such as
`embed-quality` resolves to can also change in a minor release: the vectors two targets produce are
not interchangeable, so pin a concrete model whenever a stored index must stay valid across
upgrades. Provider payloads under `raw` and reported token counts are controlled by the provider.

## Operations API stage

The `nexus-ai-pro/operations` family is public and follows the 1.x rules, including the
`operations/adapters` and `operations/webhooks` subpaths. This covers `OperationRunner`,
`LocalOperationHandle`, `MemoryOperationStore`, the `OperationStore` and `OperationDispatcher`
contracts, the Redis and BullMQ adapters, the webhook signing and verification helpers, the state
machine predicates, and the normalized record, event, and error shapes.

The lifecycle types moved out of `types/images.ts` and are re-exported from it, so existing image
imports of `OperationStatus`, `OperationEvent`, `OperationHandle`, `OperationEventBase`, and
`OperationErrorDescriptor` resolve unchanged. `OperationStatus` gained `retrying`, and
`OperationEvent` gained `progress` and `retrying` variants and an `attempt` field on `running` —
additive changes this policy permits in a minor release. Code that switches exhaustively on either
union should add the new members.

Three behaviors are guaranteed. Terminal statuses are final, so a late provider callback cannot
resurrect a settled operation. Store writes are compare-and-set on `sequence`, so a losing writer
is told it lost rather than silently overwriting. And a record carrying raw bytes is refused with
`OperationSerializationError` rather than persisted, because a base64 round trip through a queue
payload is a failure mode that otherwise appears only as a truncated job.

Durability is a property of the configured store, not of the runner. `MemoryOperationStore` does
not survive a restart and does not promise cross-process recovery. `RedisOperationStore` is atomic
only when the client exposes `eval`; without it the compare-and-set degrades to a read-compare-write
that narrows but does not close the race, and that limitation is documented rather than hidden.
Webhook delivery is best-effort and never fails the operation it describes.

## Resilience API stage

`CircuitBreaker`, the `RateLimitStore` contract, `MemoryRateLimitStore`, `RedisRateLimitStore`, and
the `ops/circuit-breaker` and `ops/rate-limit-adapters` subpaths are public and follow the 1.x rules,
together with `circuitBreaker` and `rateLimit.store` in `NexusAIConfig`.

`RateLimiter.check()` keeps its synchronous signature and in-memory behavior; `checkAsync()` is the
additive store-aware path. `NexusRateLimitError` gained an optional `resetAt` and a
`retryAfterSeconds` accessor. `RouterContext` gained an optional `openCircuits`, and `Router.route()`
an optional trailing parameter, both additive.

Which provider a breaker excludes at a given moment is a runtime decision, not a compatibility
guarantee: thresholds, scoring, and the exact ordering of candidates can change in a minor release.
What is guaranteed is the state machine — closed, open after a threshold is crossed, half-open after
the cooldown, closed again only after the configured probe successes — and that routing still selects
a provider when every circuit is open rather than failing the request untried.

Breaker state is per process unless `circuitBreaker.store` is set (since 1.16.0). With a store,
workers converge on each other's decisions within about `syncIntervalMs`, and one worker at a time
holds the probe; this is eventual agreement, not consensus, and two workers may briefly disagree.
Failure counting stays per worker either way. The `CircuitStateStore` contract and the memory, Redis,
and Postgres adapters follow the 1.x rules. Since 1.16.0 a caller's cancellation does not count as a
failure unless `isFailure` says so, and probe limits apply to every attempt the failover executor
makes. Rate-limit counters are only shared when a store is configured, and the default in-memory
counter gives each worker its own budget.

## Batch API stage

`BatchManager`, the `BatchProvider` contract, the OpenAI and Anthropic adapters, the deterministic
mock, and the `nexus-ai-pro/batch`, `batch/openai`, `batch/anthropic`, and `batch/mock` subpaths are
public and follow the 1.x rules, as are the normalized request, ref, state, and result shapes.

`BatchJobRef` is guaranteed to stay JSON-serializable and sufficient on its own to poll, collect, or
cancel a batch. That is what makes a persisted ref survive a restart, and narrowing it would break
every stored ref, so it requires a major release.

Two behaviors are guaranteed. Results are matched by `customId`, never by position, and a duplicate
`customId` is refused before submission. And a failed batch returns no items rather than fabricated
ones.

Provider vocabulary is not normalized beyond `BatchJobStatus`. Anthropic reports only `in_progress`
and `ended`, with the real outcome on the per-item results; that adapter maps `ended` to `completed`
and lets item errors carry the detail rather than inventing a batch-level failure. Payloads under
`raw`, provider quotas, completion windows, and the discount rate itself are provider-controlled and
may change without a major release.

## Asset store stage

`FilesystemAssetStore`, `S3AssetStore`, the `S3LikeClient` contract, and the
`nexus-ai-pro/images/stores` subpath are public under the 1.x rules and implement the existing
`AssetStore` contract unchanged. The shared contract, errors, and validation moved to an internal
`asset-support` module and are re-exported from `images/assets`, so every existing import resolves
as before.

All three stores guarantee that a missing asset and one owned by another tenant are
indistinguishable, that stored bytes carry a SHA-256 checksum, and that a lapsed asset is unreadable
before it is purged. `S3AssetStore.purgeExpired()` lists the record prefix on every call; on a large
bucket prefer the provider lifecycle rules. Neither durable store coordinates concurrent deletes
across processes.

## Model registry generation stage

`data/models/*.json` is the versioned source of truth in the repository, and
`scripts/generate-model-registry.mjs` emits `src/models/generated.ts` from it.

Neither is published, and neither is public API. They duplicate `KNOWN_MODELS` exactly, so shipping
them would add roughly 310KB to every install for data nothing reads. Generation is a build-time
guarantee about how the registry is maintained, not a runtime surface: the resolver continues to read
`KNOWN_MODELS`, and a test asserts the two match. Switching the resolver over is a later change, so
that introducing generation cannot alter pricing behavior in the same release.

## Graph API stage

`createGraph`, `StateGraph`, `CompiledGraph`, the channel constructors, both checkpointers, the error
classes, and the `nexus-ai-pro/graph` subpath are public and follow the 1.x rules, as are the
normalized checkpoint, result, and step-event shapes.

Four behaviors are guaranteed. A superstep is atomic with respect to checkpointing: state is reduced
and written before the next set of nodes is computed. A node that finished is never re-run by a
resume, so a completed branch's side effect happens once. `interrupt()` returns the supplied value on
replay rather than throwing again, keyed by node, step, and position, so a node may ask more than one
question. And `GraphCheckpoint` stays JSON-serializable, because a checkpoint that cannot be written
to Redis is not a checkpoint.

`GraphCheckpointer` is deliberately not generic over the channel schema: it persists opaque state,
and threading the schema through it would force an annotation on every construction for no benefit.
The graph casts at that boundary and hands typed state back through `state()` and `history()`.

`compile()` without a checkpointer uses an in-process `MemoryGraphCheckpointer` capped at 1,000
threads (since 1.10.0; earlier releases created none). `checkpointer: false` disables checkpointing. A
run started without a `threadId` reports its generated id on the result.

Also since 1.10.0:

- A paused or failed superstep records its already finished nodes in the optional `completed` field.
  Those nodes are not run again, and their edges still count when the step completes.
- Writing checkpoint N drops any stored checkpoints at or after N, so a rewound thread has one
  timeline. Keeping several branches is planned work (forks), not current behaviour.
- A subgraph interrupt surfaces as an interrupt of the parent node.
- `onProgress` receives `context.report()` calls.

Scheduling within a superstep is not a guarantee, and 1.11.0 makes use of that: tasks in a
superstep now run concurrently, bounded by `maxConcurrency` (default 16, settable per compile and per
run; `1` is strictly sequential). A node still must not depend on observing another node's write
within the same superstep — that is what channels are for. What is guaranteed is that writes are
reduced in task order rather than completion order, so a replay of the same decisions produces the
same state. The default `maxSteps` and `maxConcurrency` may change; pass them explicitly when a run
depends on the bound.

Also since 1.11.0, all additive:

- `Send` creates one task per value, with the task's input on `context.input`. Task ids are derived
  from the step and the order produced, and appear in the checkpoint's optional `tasks` field, which
  is written only when it says more than `next` does.
- `NodeOptions` on `addNode` carries `retry`, `timeoutMs`, and `ends`; `compile({ retry })` sets the
  default policy. Retrying is off unless asked for. A timeout raises `GraphNodeTimeoutError`.
- `onNodeError` chooses `fail-fast` (default) or `settle` for the siblings of a failed task.
- A paused checkpoint and result carry `interrupts`, every question the step asked;
  `interrupt` remains the first of them. `resumeInterrupts()` and `resumeInterruptsWith()` answer any
  subset by interrupt id. Interrupt ids are keyed by task, so ids written by earlier releases for
  ordinary nodes still resolve.
- `context.taskId` and `context.attempt` are new. Step events carry `tasks` and `attempts` when there
  is something to report.

Also since 1.12.0, all additive:

- `Command` (with `Command.PARENT`) may be returned from a node; `NodeResult` names what a node may
  return. `goto` routes are added to the node's outgoing edges, and a paused checkpoint records them in
  the optional `gotos` field.
- `interruptBefore` and `interruptAfter` on compile and run options; a paused checkpoint and result
  carry `breakpoint`, and step events may have type `breakpoint`.
- `NodeOptions.defer`, `GraphRunOptions.onEvent` with the `GraphEvent` union, and `context.emit()`.
  New `GraphEvent` variants may be added in a minor release.
- `updateState()`, `fork()`, and `describe()` on a compiled graph, and the `nexus-ai-pro/graph/visualize`
  subpath. The exact Mermaid text `toMermaid()` produces is not a guarantee — diagram layout may
  improve in a minor release — but `GraphDescription` is a normalized shape.
- A subgraph node's thread id is `<parent thread>:<task id>`, which equals the previous
  `<parent thread>:<node>` for any node not reached through `Send`.

Also since 1.16.0, all additive:

- `createGraph({ channels, input, output })`. `StateGraph`, `CompiledGraph`, and `GraphResult` gained
  defaulted type parameters for the input and output channels, so existing annotations such as
  `CompiledGraph<S>` keep their meaning. A write to an undeclared input rejects with
  `GraphValidationError`. Checkpoints and stream events still carry every channel.
- `NodeOptions.cache` and `CompileOptions.cache`. Guaranteed: a failure, an interrupt, a node that
  consumed an interrupt's answer, and a `Command.PARENT` result are never served from the cache, and a
  cache that throws is treated as a miss. Not guaranteed: the default key's exact text, so entries
  written by one minor release may miss after an upgrade.
- `task_end` events may carry `cached: true`; `GraphDescription` may carry `input`, `output`, and a
  node's `cache` flag.

## Agent, store, MCP, and tracing stages (1.14.0)

The `nexus-ai-pro/agent`, `nexus-ai-pro/store`, `nexus-ai-pro/store/redis`, and `nexus-ai-pro/mcp`
subpaths are public and follow the 1.x rules. None is exported from the root import.

- `createAgent()` returns a compiled graph, so the graph guarantees above apply to an agent: a
  superstep is atomic with respect to checkpointing, a finished task is never re-run by a resume, and
  an approval survives a restart. The agent's state channels (`messages`, `iterations`, `answer`,
  `stopReason`) are a normalized shape; what a model decides to put in them is not.
- `Store` is the contract; `MemoryStore` and `RedisStore` implement it. Ranking from a semantic search
  depends on the embedding function a caller injects and is not a guarantee. `MemoryStore` is
  process-local and bounded, and drops the least recently written item past `maxItems`.
- The MCP subpath implements a subset of the Model Context Protocol: initialize, tools, resources, and
  prompts, over stdio and HTTP. Protocol versions and server behaviour are upstream contracts outside
  this policy. Server-initiated streaming, sampling, and roots are not implemented; new methods may be
  added in a minor release.
- `nexus-ai-pro/tracing` is public: `Tracer`, the `TraceStore` contract, `MemoryTraceStore`,
  `JsonlTraceStore`, `traceGraph()`, `traceModelClient()`, `compareTraces()`, and `AlertEvaluator`.
  The `Run`, `RunTree`, and `RunQuery` shapes are normalized; what a model puts in a run's inputs and
  outputs is not. New `RunKind` values and query fields may be added in a minor release. Sampling is
  probabilistic by definition, so which traces are kept is not a guarantee, but the tail rules are:
  an error, a run past `keepSlowerThanMs`, or one past `keepCostlierThan` is always kept.
- `agentAsTool()` and the bundled middleware (`summarizeHistory`, `redactMessages`, `limitToolCalls`)
  are public and additive.
- `AgentLoop`, `tool()`, and `ToolExecutor` keep their behaviour and are re-exported from
  `nexus-ai-pro/agent` as well as the root.

## Evaluation stage (1.15.0)

`nexus-ai-pro/evaluate` is public and follows the 1.x rules, and is not exported from the root.
`Dataset`, `DatasetExample`, `Experiment`, `ExampleResult`, `MetricSummary`, and the comparison shapes
are normalized; new optional fields and new bundled evaluators may be added in a minor release.

- A dataset version is derived from example content. The hash itself is an implementation detail, but
  the guarantee is not: identical examples produce identical versions, and any change produces a
  different one.
- `compareExperiments()` is statistical. Its intervals come from a seeded paired bootstrap, so the
  same inputs give the same verdict; the exact interval bounds may change if the method improves, and
  a verdict is a judgement about evidence rather than a compatibility guarantee.
- `evaluate()` reports a target's failure as a failed example and an evaluator's failure as a failed
  measurement; neither throws. Since 1.16.0 an aborted evaluation rejects with the abort reason and
  stores nothing.
- `EvalRunner` and `MediaEvalRunner` keep their APIs. Since 1.16.0 both run on `evaluate()` and carry
  the resulting experiment in an optional `experiment` field; their reports are otherwise unchanged.
- Since 1.16.0, additive: `underCost()`, `FileExperimentStore`, `readExperiment()`, the `cost`
  option, per-example `repetitions`, and `signal` on the target's context.

## Postgres, command-line, and record-and-replay stages (1.16.0)

The `nexus-ai-pro/postgres` subpaths, `nexus-ai-pro/ops/circuit-store`, and
`nexus-ai-pro/testing/record` are public and follow the 1.x rules. None is exported from the root.

- Each Postgres adapter implements the same contract as the corresponding in-memory store, and is
  tested against it. Ordering among rows that tie on a timestamp is not a guarantee.
- `PostgresLikeClient` is the whole driver contract: `query(text, values)` resolving to `{ rows }`.
- The schema is guaranteed additive for the 1.x line: a minor release may add a column or an index,
  never drop or rename one, and `migrate()` stays idempotent. A change that needs a data migration
  waits for 2.0.
- Unique idempotency keys in `PostgresOperationStore` are a guarantee, and `OperationDuplicateError`
  is the signal the runner relies on.
- The `nexus` commands and flags listed by `nexus help` are public. Human-readable output is not a
  guarantee and may improve in any release; `--json` output follows the 1.x rules; exit codes are
  guaranteed — 0 for success, 1 for a failed check, 2 for a usage error.
- The fixture file format and the default match key of `testing/record` are stable for the 1.x line,
  so recordings made with one release keep replaying with the next. A change that would invalidate
  recordings waits for 2.0.

## Prompt stages (1.17.0)

The `nexus-ai-pro/prompts` subpaths and `nexus-ai-pro/postgres/prompts` are experimental in
production readiness, as the image family is: their exported types, functions, and classes follow the
1.x semantic versioning rules, and the experimental label does not permit an incompatible minor or
patch release. None is exported from the root.

- The content version algorithm is a guarantee. `promptVersion()` gives the same version for the
  same content in every 1.x release, so an upgrade never re-versions a committed prompt.
- Version, label, and history records only gain fields in the 1.x line, and the Postgres schema
  follows the additive rule above.
- The wording of gate reasons, of the note a forced promotion records, and of `formatPromptDiff()`
  output is for people and may improve in any release.

## Agent server stage (1.18.0)

The `nexus-ai-pro/server` subpaths are experimental in production readiness, as the image and prompt
families are: their exported types, functions, and classes follow the 1.x semantic versioning rules,
and the experimental label does not permit an incompatible minor or patch release. Neither is exported
from the root.

- The routes listed in the server guide, their status codes, and the shape of `RunRecord`,
  `ThreadRecord`, and `RunEvent` follow the 1.x rules. A minor release may add routes and fields.
- Event ids are per run and strictly increasing, which is what `Last-Event-ID` depends on. A client
  that reconnects with an id never receives it again.
- The server is not a security boundary on its own: authentication, and authorization beyond tenancy
  and scopes, are the application's, through the hooks. Per-tenant limits are opt-in, through
  `tenants`.
- Recovery repeats the step a run died in for graphs and workflows served by `graphAssistant()`, and
  the whole run for any other assistant, so the repeated part should be idempotent or the run left at a
  single attempt.

## Studio stage (1.19.0)

`nexus-ai-pro-studio` is a separate package, experimental in production readiness. It is versioned
with nexus-ai-pro and declares the core versions it works with as a peer dependency.

- The `nexus-studio` command, its flags, and the shape of the config module's default export follow
  the 1.x rules. A minor release may add sources and flags.
- `createStudio()`, `startStudio()`, and the exported types follow the 1.x rules.
- The JSON routes behind the page are for the page and may change in any release; build on the
  stores themselves rather than on the studio's routes.
- The access rules are a guarantee: loopback binding by default, a token on every request, the token
  in a header for every change, and loopback hosts only unless others are allowed.

## 2.0 preparation stage (1.25.0)

The last 1.x minor deprecates what 2.0 removes. Deprecated APIs keep working, unchanged, for the rest
of the 1.x line.

- The 2.0 root import keeps the core client, its config builders, the types of its configuration,
  requests, and responses, and the errors it throws. Every other root export is deprecated in favour of
  the subpath that already provides it, under the same name or, where noted, another.
- Every deprecated option warns once per process through Node's `DeprecationWarning` channel, so
  `--throw-deprecation` finds them in a test run. Deprecated exports and fields are marked in the type
  declarations, where editors show them.
- `nexus migrate` and its map follow the root's deprecations; both are generated from the export map,
  so they cannot disagree.
- A 1.x checkpoint stays readable throughout 2.x, by both checkpointers and by `migrateCheckpoint()`.

## Deployment stage (1.24.0)

Tenant limits and the worker queue graduated in 2.4; see the 2.4 stage for their evidence.
Deployments remain experimental.

Deployments (`nexus-ai-pro/server/deployments`), tenant limits (`nexus-ai-pro/server/tenancy`), and the
server's worker queue are experimental in production readiness. Their contracts follow the 1.x rules.

- `bucket()` is stable across 1.x, so a thread keeps its place in a traffic split through an upgrade.
- The stored `DeploymentRecord` and `ReplicaReport` shapes may gain fields but not lose them, so
  replicas on different minor releases share one store.
- The metric names on `/metrics` and the fields of `/scaling` follow the 1.x rules; a minor release may
  add metrics and fields. Autoscaler configurations that read them keep working.
- `drain()` hands off only runs it could not finish; a hand-off never counts against the retry budget.
- The files under `deploy/` are templates in the repository, not part of the package, and may change in
  any release.

## Team platform stage (1.23.0)

The context hub and insights graduated in 2.4; see the 2.4 stage for their evidence. The studio's
accounts remain experimental.

The context hub (`nexus-ai-pro/context-hub`), insights (`nexus-ai-pro/insights`), and the studio's
accounts are experimental in production readiness. Their contracts follow the 1.x rules.

- A bundle's content version covers its name, description, prompts, instructions, tools, skills, and
  config, and is stable across 1.x, so the same content is the same version in every release.
- The export format is `nexus-context-bundle`, format version 1; a later release reads it.
- A cached evaluation output is keyed by the fingerprint, the example's id and inputs, and the
  repetition. The key's derivation is stable across 1.x, so a cache survives an upgrade.
- The studio's roles keep their order and meaning; new actions are assigned to an existing role.

## Retrieval breadth stage (1.22.0)

The loaders, the SQLite vector store, the retrievers, and the MCP registry graduated in 2.4; see the
2.4 stage for their evidence. The Redis, Weaviate, and Chroma stores graduated in 2.8; see the 2.8
stage. The Pinecone store remains experimental.

The loaders (`nexus-ai-pro/loaders/*`), the Redis, Pinecone, Weaviate, Chroma, and SQLite vector
stores, the retrievers (`nexus-ai-pro/rag/retrievers`), and the MCP registry
(`nexus-ai-pro/mcp/registry`) are experimental in production readiness. Their contracts follow the
1.x rules.

- Every vector store keeps the `VectorStore` contract and its contract test, which checks that a
  metadata filter matches type as well as value.
- A loader's document ids are stable: the path relative to a directory or repository, the final URL
  of a page, and the file name plus key or position of a row or record. Chunk ids built from them do
  not change in a minor release, so a reload keeps replacing the same chunks.
- The REST stores' payload layout — `content`, `source`, `metadata` as JSON, and `meta_` filter fields
  — is stable across 1.x, so an index written by one release is read by the next.
- The registry reads `servers` and `mcpServers`; new configuration fields are optional.

## Durable execution stage (1.21.0)

The SQLite adapters graduated in 2.4; see the 2.4 stage for their evidence.

Functional workflows (`nexus-ai-pro/graph/functional`) and the SQLite adapters (`nexus-ai-pro/sqlite`)
are experimental in production readiness. Their contracts follow the 1.x rules.

- A workflow checkpoint is a graph checkpoint whose state holds the input, the recorded step results
  keyed by step name and call order, and the output. That layout is stable across 1.x, so a thread
  started by one release resumes on the next.
- The SQLite table layouts may gain columns in a minor release; `migrate()` adds what is missing.
- `ServerAssistant.recover()` and `AssistantRunContext.attempt` are optional additions: an assistant
  without `recover()` behaves as before.

## Retrieval stores stage (1.20.0)

`PostgresVectorStore` graduated in 2.4, and `QdrantVectorStore` in 2.8; see those stages for their
evidence.

`PostgresVectorStore` (`nexus-ai-pro/postgres/vectors`) and `QdrantVectorStore` (`nexus-ai-pro/rag/qdrant`)
are experimental in production readiness. The `VectorStore` contract they share with
`MemoryVectorStore` follows the 1.x rules: a minor release may add optional methods and options, not
remove or reshape these.

- The pgvector table layout may gain columns in a minor release; `migrate()` adds what is missing.
- A Qdrant point id is derived from the chunk id and is stable across releases, so a collection
  written by one version is read by the next.

## Deprecation process

Deprecated APIs are marked with `@deprecated` in declarations and described in the changelog. Removals
are reserved for major releases, except when an urgent security issue requires otherwise.

Report accidental compatibility regressions through the project issue tracker. Security issues should follow [SECURITY.md](./SECURITY.md).
