Upgrading
Each major release gets a guide here that covers every breaking change in one place, with the upgrade step for each. Smaller changes are in the release notes, and what counts as breaking is in the versioning policy.
Upgrading to 1.0
1.0 isn’t released yet. This guide lists the breaking changes already decided for 1.0 and grows as they land. It’s final when 1.0 ships.
It assumes you’re on 0.12. If you’re on an older release, read the Breaking and Upgrading notes for each release in between first; most apps only need the 0.12.0 steps.
Before you start, upgrade to the latest 0.12 and fix every deprecation warning
your editor shows. Each @deprecated tag names its replacement, and those
names are the ones removed below.
Node.js 22 or later
engines goes from Node.js 18 to 22, because 18 and 20 are past end-of-life.
CI tests Node.js 22 and 24. Upgrade your runtime and your CI image, and your
Next.js deployment target if you set one.
Deprecated aliases are removed
These names were moved or renamed in 0.12 and are gone from dendra:
Removed from dendra | Use instead |
|---|---|
resolveProc, invokeUnary, invokeStream | the same names from dendra/adapter |
types Proc, AnyProc, CallSite, Transport, ProcedureKind | the same names from dendra/adapter |
createMemoryStore, recallMemory, rememberFacts, formatMemoryBlock | the same names from dendra/memory |
toolSource | lazyToolSource |
type ToolSourceOptions | LazyToolSourceOptions |
Most of these are import changes:
// before
import { createMemoryStore, invokeUnary } from 'dendra'
// after
import { createMemoryStore } from 'dendra/memory'
import { invokeUnary } from 'dendra/adapter'The memory types (MemoryStore, MemoryRecord, RecallOptions) stay in
dendra.
Unexpected errors reach clients as INTERNAL
An error that isn’t a DendraError, such as a database driver’s error or a
TypeError, used to reach the client or model with its own message. It now
arrives as INTERNAL: Internal error over HTTP, streams, WebSockets, job
snapshots, the MCP bridge and tool calls, and the original is logged with
console.error. After-hooks still see the original error.
If a client or model needs to read a message, throw a DendraError with that
message:
// before: the client saw "Note title already taken"
throw new Error('Note title already taken')
// after
throw new DendraError('CONFLICT', 'Note title already taken')JSON bodies are capped
maxBodyBytes (default 10 MB) used to cap only uploads and form posts. It now
caps JSON, batch and REST bodies too, answering 413 PAYLOAD_TOO_LARGE. If an
endpoint takes JSON larger than 10 MB, raise it:
toNextHandler(appRouter, { prefix: '/rpc/', createContext, maxBodyBytes: 50 * 1024 * 1024 }).ai() inputs need a JSON Schema
A tool’s input schema is what the model sees. dendra now reads it from Zod 4,
ArkType and Valibot as well as s.*, and an .ai() procedure whose input has
none throws where it’s defined, instead of showing the model no parameters.
- Zod 3: move the tool’s input to Zod 4 or
s.*. - Valibot: wrap the schema in
toStandardJsonSchemafrom@valibot/to-json-schema. - A
z.date()or other type with no JSON form: take a string and parse it in the handler, or usez.iso.datetime().
Tool calls
createToolExecutoranswersNOT_FOUNDfor a procedure without.ai(), as it does for one that doesn’t exist, so a model can’t probe for procedures. It only throwsDendraErrors.createMcpToolSourcethrows a remote tool’s error asBAD_GATEWAY(502), with the server’s text. Code that matched another code for remote failures should matchBAD_GATEWAY.lazyToolsetthrows when two tool names become the same once sanitized (a.banda_b). It used to route calls to the wrong tool. Rename one of them.
After upgrading
Run your type checker first: removed names fail to compile, so it finds every
import to change. Then run your tests, paying attention to any that assert an
error’s message rather than its code.