Upgrading
Dawn is pre-1.0. The API surface is still moving, and the safest posture is to pin a version, read what changed, and upgrade deliberately rather than floating on a range. This page is the workflow, not a changelog — the actual list of what changed between any two versions lives on GitHub, not here.
Fixed-group versioning
Every publishable Dawn package — @dawn-ai/cli, @dawn-ai/core, @dawn-ai/sdk, the other @dawn-ai/* packages, and create-dawn-ai-app — is released together under one version number via Changesets' fixed-group mode. If @dawn-ai/cli is at 0.8.12, so is every other package in that release. There's no independent versioning between packages to reason about, and no compatibility matrix to check — install matching versions and you're done.
The two Helm charts (dawn-sandbox-infra, dawn-app) track the same package train through their appVersion field, but they're published separately as OCI artifacts, not npm packages — see Kubernetes for how to pull a specific chart version.
How to read what changed
Each release is built from the changeset entries merged since the previous one — short, per-change Markdown files that describe what changed and why, written by whoever made the change. Two places to read them:
- GitHub Releases — github.com/cacheplane/dawnai/releases lists every published version with its changeset entries rolled up into release notes. This is the fastest way to scan what changed between two versions.
- Per-package
CHANGELOG.md— each package underpackages/*/CHANGELOG.mdin the repository carries its own changelog, generated from the same changeset entries but scoped to that package.
Upgrade workflow
- 1
Check the current version
textpnpm why @dawn-ai/clior check the
@dawn-ai/*entries in yourpackage.json. - 2
Read the release notes
Open GitHub Releases and read every entry between your current version and the target version.
- 3
Bump and reinstall
Bump every
@dawn-ai/*dependency inpackage.jsonto the same target version, then reinstall. - 4
Regenerate and verify
textdawn typegen dawn verify dawn testdawn verifyruns five phases: app discovery and config validation; route discovery; tool type extraction and typegen rendering; advisory checks for missing dependencies and provider environment variables; and runtime readiness for Node plus any configured sandbox provider.dawn testre-runs your scenario coverage against the new version. See CLI and Testing.
Node 24 is the minimum
Current Dawn packages declare node >=24.0.0, and create-dawn-ai-app refuses to scaffold on older releases. Before upgrading dependencies, move local version-manager files, CI runners, and custom container bases to Node 24 or later, reinstall with that runtime, and run dawn verify. Node 24 also supplies the npm and unflagged node:sqlite versions the scaffold expects.
Node-only imports moved to /node
If application tooling imports filesystem or process-backed helpers, update the import specifier while keeping the symbol unchanged:
- From
@dawn-ai/core/node:discoverRoutes,findDawnApp,assertDawnRoutesDir,extractToolSchemasForRoute,extractToolTypesForRoute, andregisterTsxLoader. - From
@dawn-ai/permissions/node:createPermissionsStore. - From
@dawn-ai/workspace/node:localFilesystemandlocalExec.
For example, change import { discoverRoutes } from "@dawn-ai/core" to import { discoverRoutes } from "@dawn-ai/core/node". Runtime-agnostic contracts and helpers remain on each package's main entry; use /node only for the explicit Node surface.
Pinning
Pin exact versions (no ^ or ~ ranges) for @dawn-ai/* packages until the project reaches 1.0 — a floating range means an unreviewed minor bump can land in CI or production without the deliberate read-the-notes step above.
toolOutput is now gated off the hono target
If your app configures toolOutput and names "hono" in build.targets,
dawn build and dawn check now fail with DAWN_E1005 where they used to pass.
Nothing about your app changed; the gate did. Tool-output
offloading spills oversized tool results to a file
under workspace/ and hands the model a pointer to it, and an edge runtime has
no filesystem to spill to — so the feature never worked on that target. It was
also the only gated feature whose config is plain JSON, which is exactly why it
slipped through: the other gated keys are live objects that get stripped at the
build boundary, while toolOutput was inlined into the bundle intact and then
ignored at runtime. A green build and a worker that silently never offloads is
worse than a failed build.
Two ways forward: remove toolOutput from dawn.config.ts, or drop "hono"
from build.targets and deploy with the node target, which serves offloading
normally. An empty toolOutput: {} expresses no intent and is not gated.
Node deployments are unaffected — the gate is specific to the hono build
target, and the matching runtime check (below) cannot fire on Node at all.
Gated features now fail loudly at request time, not just at build time
sandbox, route skills, and toolOutput used to be read and then
quietly do nothing on a runtime with no filesystem. All three now raise
DAWN_E1005 on every request instead, naming the feature and the config key that
introduced it. See What the edge cannot
serve.
This closes a gap the build gate could not: composing an entry by hand over
@dawn-ai/cli/fetch is a supported way to deploy, and such an app never runs the
hono target, so it never met the build gate at all.
No Node app can hit this, whatever it configures. The check short-circuits before it reads a single config key whenever the runtime supplied filesystem fallbacks, and every Node entry point supplies them unconditionally. An absent sandbox on Node remains the documented degrade it has always been. The deployments that can hit it are edge ones, and for them the affected config was already doing nothing.
@dawn-ai/postgres-storage now requires a pool on its main entry
connectionString has moved to a new @dawn-ai/postgres-storage/node subpath.
In 0.8.19 the main entry accepted either and built its own pg pool from a
connection string; it no longer does, because it now imports pg for types
only so that the package links on a runtime with no TCP sockets — which is
what makes the hono edge target
possible at all.
connectionString is gone from the main entry's option type, so passing it
there is a type error, and the factory throws at construction naming the missing
pool. Nothing fails silently. Two ways to migrate:
// 1. Change the import — same factories, connectionString still works, and the
// store still builds and owns its pool.
import { postgresCheckpointer } from "@dawn-ai/postgres-storage/node"
// 2. Or build the pool yourself and keep the main entry. This is what you want
// anyway when one pool serves all three stores.
import { Pool } from "pg"
const pool = new Pool({ connectionString: process.env.DATABASE_URL })
// Do not skip this — see below.
pool.on("error", (error) => {
console.error("postgres pool client error (connection dropped):", error)
})
postgresCheckpointer({ pool })The /node entry re-exports everything the main entry does, so option 1 is
usually one line. Pool ownership is unchanged in both shapes: a pool the store
built is ended by close(), an injected pool is left alone (ownsPool,
defaulting to false). This shipped as a patch despite being breaking: under
fixed-group versioning a minor bump would move every
@dawn-ai/* package to 1.0.0, which is not what a pre-1.0 project wants to say
about one entry-point split. It is also why the advice at the top of this page —
pin exact versions and read the notes — is not boilerplate.
kind: "reflection" is now accepted
Memory distillation wired the reflection kind.
defineMemory({ kind: "reflection" }) and the generated remember tool now
accept it — previously it was typed but threw "memory kind 'reflection' is not
yet wired" at write time. Reflections are append-only (like episodic
writes): a later insight never supersedes an earlier one, and ask mode never
prompts for one. dawn memory reflect produces them, as candidate records by
default.
procedural remains the one typed-but-unwired kind and still throws.
No action required. This change is purely additive — nothing that worked before behaves differently, and no existing app needs to change. Adopt it only if you want a reflection collection or want to run the distillation commands.
MemoryStore now requires browse and stats
The MemoryStore contract gained two required methods to power the
Inspector's Memory panel. If you implement a custom store for
config.memory.store, add both:
browse(q?)— cross-namespace/status listing, returning{ records, total }withrecordsorderedupdated_at DESC, id ASCandtotalcounting all matches (ignoringlimit/offset). The optional query narrows bynamespacePrefix,status,kind, andsourceType.stats(opts?)— aggregate counts:{ total, byStatus, byKind, byNamespace, bySourceType }, optionally scoped to anamespacePrefix.
The built-in SQLite and pgvector stores already implement both, and the
runMemoryStoreConformance kit in @dawn-ai/testing covers them — run it
against a custom store to verify the contract.
Two related behavior changes in the same release:
- The config-facing store type is now the full
MemoryStorecontract —deleteandlistCandidatesincluded — rather than the narrower capability-facing surface. A custom store that already satisfied the CLI'sdawn memorycommands is unaffected. dawn memory approve(and the Inspector's Approve) now reconciles supersession: approving a candidate that contradicts an active record with the same identity key supersedes the old record instead of leaving two active rows; approving an identical duplicate dedupes it.
MemoryStore now requires prune
Episodic memory added a required retention
method to the MemoryStore contract. If you implement a custom store for
config.memory.store, add it:
prune(opts: {
now: string // the clock — rows with expiresAt <= now are expired
namespacePrefix?: string // scope the pass to matching namespaces
cap?: number // per-namespace cap for episodic records
}): Promise<{ deletedExpired: number; deletedOverCap: number }>Semantics:
- TTL — delete every record whose
expiresAtis at or beforenow(records withoutexpiresAtnever expire). - Cap — within each namespace, keep at most
capepisodic records, deleting the oldest beyond it. Nocapmeans no cap pass.
The runtime episode recorder calls prune lazily after each write, and
dawn memory prune runs it manually.
Two related behavior changes in the same release:
searchandbrowseacceptsince/until(ISO instants;sinceinclusive,untilexclusive) comparing againsteffectiveAtwith acreatedAtfallback.- When a query supplies
now,searchandbrowsemust exclude expired rows (expiresAt <= now). Queries withoutnoware unchanged.
The built-in SQLite and pgvector stores already implement all of this, and the
runMemoryStoreConformance kit in @dawn-ai/testing covers it — run the kit
against a custom store to verify the contract.