Migration (pre-1.0)
Graphorin is pre-1.0. This page tracks how versions move and what to expect when upgrading.
Versioning policy
- Lockstep until 1.0. Every
@graphorin/*package shares one version and is released together. Install matching versions across the scope. - Pre-1.0 semantics. Per semver,
0.xminor bumps (0.1 → 0.2) may carry breaking changes. Patch bumps (0.1.0 → 0.1.1) are fixes only. - Breaking changes land in the root changelog as part of each version's thematic release notes (there is no dedicated "breaking" subsection today); the per-package
CHANGELOG.mdfiles point back to the root changelog for the full notes. The version notes below distil the upgrade-relevant changes per version.
Upgrading
# Upgrade the whole scope together (lockstep).
pnpm up "@graphorin/*@latest"
pnpm install
pnpm build && pnpm typecheck && pnpm testAfter upgrading:
- Re-read the changelog entry for the target version, plus the matching version notes section below.
- Rebuild so producer
dist/*.d.tsare current before downstream typecheck. - Run your tests. Strict TypeScript surfaces most contract changes at compile time.
Version notes
Before any upgrade
- Back up the SQLite file:
graphorin storage backup <dest>takes an online, consistent copy (safe under a live writer; an encrypted store produces an equally encrypted copy). - Read the root changelog entry for the target version.
- Bump every
@graphorin/*package together (lockstep):pnpm up "@graphorin/*@latest". Mixed versions across the scope are not supported.
0.12.0 -> 0.12.1
One patch, one observable behavior change:
- Schema-less structured requests send no
response_format. On the OpenAI-shaped adapters,outputType: { kind: 'structured' }WITHOUTjsonSchemaused to send a permissivejson_schema(strict: false) - the billed live pass showed the Anthropic OpenAI-compat endpoint rejects it (HTTP 400), silently failing schema-less structured runs there. The request body now carries noresponse_format; the agent's trailing JSON instruction and the localschema.parsegate carry the contract on every server. If you relied on server-side JSON mode, supply an explicitoutputType.jsonSchema- and keep it CLOSED (additionalProperties: false, all properties required) for the compat endpoint.
0.11.0 -> 0.12.0
0.12.0 is the durable-approvals release. One default flips; the rest is additive:
/v1/metricsnow requires auth by default.metrics.requireAuthflipped totrue: give your Prometheus scrape job a bearer token with theadmin:metrics:readscope (authorization.credentials_filein the scrape config), or restore the old behaviour explicitly withmetrics: { requireAuth: false }for trusted-network scrapes (the non-loopback WARN still fires on the opt-out).- Schema migration 038 (
suspended_runs) applies on the first server start (or a guardedgraphorin migratefor CLI-first workflows). Runs parked on durable HITL survive restarts from this version on; nothing to do beyond the normal backup-before-upgrade. - The
Agentinterface gainsserializeState/deserializeState. Agents built bycreateAgent(...)get them automatically; only hand-rolled implementations of theAgentinterface must add the two methods (delegate torunStateToJSON/runStateFromJSON). CustomServerAgentLikeregistry fixtures stay optional-codec: without one, suspended runs keep the previous in-memory-only behaviour. - Non-loopback binds WARN about plaintext HTTP until you set
server.tlsTerminatedUpstream: trueto acknowledge the TLS-terminating reverse proxy in front. The flag only silences the warning. graphorin token revoke/rotatenow print the verifier-cache propagation note (informational; scripts parsing stderr may see the extra line).
0.10.x -> 0.11.0
0.11.0 is the local-first first-run release. Everything is additive; nothing requires action:
ProviderEventfinishgains an optionalproviderMetadatafield. Exhaustive switches over the event union are unaffected (no new variant); consumers that serialize provider events verbatim will see the extra field on Ollama-backed streams.withTracingstamps additional span attributes (graphorin.provider.<vendor>.<key>) when an adapter reports vendor diagnostics. Dashboards keyed on an exact attribute allowlist may want to add them.graphorin doctorgains--smoke-local(plus--ollama-base-url/--ollama-model/--embed-model). Behavior of existing flags is unchanged; the doctor still opens no network connection unless--smoke-localis passed.@graphorin/clinow depends on@graphorin/provider(installed automatically with the lockstep bump).
0.10.0 -> 0.10.1 / 0.10.2
Two patch releases from the 2026-07 end-to-end campaign. Everything is a fix, but several defaults sharpen in ways you can observe:
RunBudget.maxCostUsdnow enforces. With apriceLookupconfigured, per-call cost lands on the run's accumulated usage and the ceiling trips runs that previously overran silently (the ceiling was inert). Raise the budget - or drop it - if you relied on the old fail-open behavior.- A bare
createMemory()no longer warns: compaction is simply off until you passproviderContextWindow(MEMORY-C-03). An explicitcompactionconfig without a window still throws. graphorin triggers prunerequires--before <cutoff>. The bare invocation was an epoch-0 no-op; it now errors instead of pretending to prune (OPERATOR-01).- Revoked server tokens stop authenticating immediately.
DELETE /v1/tokens/:idinvalidates the verifier cache; anything that relied on the buggy up-to-60-second grace window sees denials right away (TOKENS-RE-01). - Graceful shutdown closes WebSockets with close code
4007(server.shutdown); clients should treat it as a terminal close, not a network drop to retry instantly (WS-LIFECY-02). - tiktoken-backed counters treat special-token sequences as plain text -
<|endoftext|>counts as its BPE pieces instead of throwing, so token counts over such content shift slightly (PROVIDER-CT-01). - Session erasure works in the default vec0 mode again (STORE-SQ-02); if you scripted around the crash, remove the workaround.
0.9.x -> 0.10.0
0.10.0 is the external-audit remediation release (Ollama adapter operational controls, actionable native-binding failures). The upgrade is a lockstep bump; everything is additive or a sharpened failure mode. What can be observable:
- A forced
toolChoiceon the native Ollama adapter now throws.ollamaAdapternever could enforce'required'/{ tool: name }(the native/api/chatAPI has notool_choicefield) - it used to silently treat the forced choice as'auto'. It now throws the typedProviderToolChoiceUnsupportedErrorat request time. Fix: drop the forced choice and steer via the prompt, or useopenAICompatibleAdapteragainst the server's OpenAI-compatible endpoint (http://127.0.0.1:11434/v1), which mapstool_choice.toolChoice: 'none'is now actually enforced - the tool catalogue is withheld for that step. - Thinking models surface reasoning events on the Ollama adapter. Streamed
message.thinkingbecomesreasoning-delta(agentreasoning.delta) events instead of being dropped, so event consumers that assumed text-only streams from this adapter will now also see reasoning deltas when the model thinks. Passthink: falseto disable thinking (qwen3-style models think by default on recent Ollama releases). providerOptions.optionsmerges instead of replacing. A nestedoptionsobject passed throughproviderOptionsused to clobber the adapter-built block (droppingtemperature/num_predict); it now merges key-by-key with the per-request values winning.- Missing native bindings fail with
SqliteNativeBindingError. Code that matched the rawCould not locate the bindings filemessage frombindings.jsshould match the typed error (kindsqlite-native-binding) instead; the original driver error stays oncause.
0.8.x -> 0.9.0
0.9.0 is the bot-adoption release (channels, proactivity, memory quality loop, four-value permissions, workflow durability tail). The upgrade is a lockstep bump; almost everything is additive and off by default. What can be observable:
autoPromoteExtraction: true(orconsolidator.promotion) now requires aningestGate-createMemorythrowsIngestGateRequiredErrorotherwise. This enforces a precondition that was previously documented but not checked: auto-promoted writes skip the quarantine review, so the guardrail-verdict gate is the only thing standing between a poisoned turn and active memory. Fix: passingestGate: verdictIngestGate(or your own gate) next to the flag.- Schema migrations 034-036 apply on first
store.init()(pairing store, session-message security verdict, fact recall ledger). Forward-only as always - takegraphorin storage backup <dest>before upgrading. - Tool-argument policies: the rule vocabulary widens to
allow | deny | ask | defer. Existingallow/forbidpolicies behave byte-identically (forbidis now an alias ofdeny). The binaryevaluateToolArgumentPolicyprojectsask/defertoforbid(fail-closed) - only relevant once you write the new effects; use the agent runtime (orevaluatePermissionDecision) to get real ask/defer semantics. SessionMemory.flushImportantis deprecated (inert since it shipped); the pre-compactionmemoryFlushHookis the real flush path. The method still exists and warns.- The server warns at
start()when workflows are registered without a durable-timer driver - previously asleepForthread silently never woke. WirecreateTimerDriver(or ignore the WARN if you tick manually). - New packages
@graphorin/channelsand@graphorin/proactivejoin the lockstep scope - install them at the same version as the rest when you adopt them.
0.7.x -> 0.8.0
0.8.0 fixes the 30 defects confirmed by the 2026-07-11 end-to-end audit. Everything is a fix or an additive option; the upgrade is a lockstep bump with a handful of observable behavior changes:
graphorin token createprints the raw token to stdout (log chatter stays on stderr). Scripts that captured the token from stderr must capture stdout instead:TOKEN=$(graphorin token create ...)now works as written.- Conflict-pipeline thresholds compare raw cosine again. If you passed custom
conflictPipeline.thresholds, they are interpreted as raw cosine (the documented DEC-130 scale). Thresholds you tuned against the drifted 0.5.x-0.7.x behavior (store-scale(1+cos)/2) should be re-derived:raw = 2 * tuned - 1. Newfact_conflicts.similarityrows record raw cosine; rows written by older versions keep the store scale. - Distinct facts persist again under real embedders. If your application relied on the (buggy) aggressive dedup to keep fact counts low, expect more facts to commit; the deliberate knobs are
conflictPipeline.thresholdsand the consolidator. runEvalsatconcurrency > 1with a shared framework agent now fails fast withEvalConcurrencyErrorinstead of recording every case as a scorer failure. Pass the newagentFactory(one agent per worker) or setconcurrency: 1.- Cross-encoder reranker defaults changed: CPU loads use
q8(fp16 failed to initialize) and scores are real logit-derived values instead of a constant1.0. InjectedpipelineFactorytest stubs keep the old classifier-pipeline contract. GraphorinServer.stop()no longer closes stores you injected viacreateServer({ store }); close your own store when you own its lifecycle. Server-created stores are still closed.- CLI contract tightening:
tools lintexits 2 on a missing/broken--config(previously silent exit 0 on the default glob), andtriggers status/fire/disable/prunerefuse a behind-schema database instead of auto-migrating it - rungraphorin migratefirst. /v1/metricswithmetrics.requireAuth: truenow actually accepts a bearer withadmin:metrics:read(it answered 401 for every token before); scrapers need the scope, not workarounds.
0.6.x -> 0.7.0
0.7.0 lands the full 2026-07 project-review remediation train (six waves, 157 findings), so the upgrade surface is the largest since 0.5.0. The notes below are grouped; skim the bold group leads and read the groups that touch your deployment.
Environment and install.
- Node floor is now 22.12 (
engines.node: '>=22.12.0', previously>=22.0.0): the packages' export maps switched from animport-only condition todefault, so CommonJS consumers can plainrequire()them - stablerequire(esm)needs Node 22.12. Installs on Node 22.0-22.11 withengine-strictwill refuse; upgrade Node. There is no dual-instance hazard (no CJS build exists -require()returns the same ESM instance). - zod ^4 consumers now typecheck against
@graphorin/core,@graphorin/tools,@graphorin/memoryand@graphorin/mcpatskipLibCheck: false. Type-level note:ZodLikeError.issues[].pathwidened fromstring | numbertoPropertyKeyelements, and the published d.ts no longer bake concrete zod v3 generics. @graphorin/observabilityno longer declares@opentelemetry/*peer dependencies (they were phantom - the package has zero OTel imports), so installs stop demanding@opentelemetry/apiand theERESOLVEfrom the stale caret-pinned peers is gone. Upstream OTel SDK pipelines adapt via the exportedtoOtlpEnvelope.- Phantom workspace dependencies were removed across eight packages (for example,
agentno longer depends onproviderorobservability). If your code imports a@graphorin/*package it only received transitively, declare it as a direct dependency. - The server's sibling peer floors (
@graphorin/agent/memory/sessions/workflow) now track the current minor: mixed-version installs (sayagent@0.6.xunderserver@0.7.0) refuse to resolve. Upgrade the whole scope together, as the lockstep policy requires. - Tarballs now ship
src/so the published declaration maps resolve (go-to-definition lands in TypeScript source); installs get slightly larger. - Publishing moved to npm trusted publishing (OIDC). No consumer action: packages keep Sigstore build provenance.
Storage schema and data (back up first).
- The store schema advances to migration 032: 029 links HITL/workflow checkpoints to their session, 030 adds the span-end index, 031 drops the dead
trigger_fire_logtable (never written or read by any code), 032 addsworkflow_checkpoints.wake_atfor durable timers. Migrations run automatically when the store opens. - Migration 022 gained a data-repair preflight: databases that actually hit the session-sequence duplicate race can now upgrade (duplicates are deterministically renumbered inside affected sessions only; everything else is byte-for-byte untouched).
- Threads suspended before migration 032 carry no
wake_atand stay invisible to the new timer driver until one manualtickor a resume re-persists them. - Read-only CLI commands (
memory inspect/activity,traces status|prune,triggers list|status|fire|disable|prune,consolidator status,dlq-list) no longer auto-migrate: pointed at a behind-schema or never-migrated database they refuse with instructions instead of upgrading the schema under a live server.
Retention: the server now deletes old data by default.
- A unified retention sweep (default every 6 hours) prunes derived data out of the box: spans older than 30 days, consolidator run counters older than 90 days, exhausted DLQ batches older than 30 days, and expired idempotency records. On upgrade, old span telemetry and expired idempotency bodies start being deleted - set
retention: { enabled: false }or widen the windows to keep them. Primary content (sessions, audit, memory history, workflow threads) is pruned only through explicit opt-in windows. - An existing cron
graphorin traces prunenow actually deletes old spans - the command used to be a silent no-op against a table that never existed. - Session hard-delete now erases everything session-scoped: distilled facts, insights, spans and working blocks (via the
SESSION_SCOPED_PURGESregistry) plus suspended-run checkpoints.session.replay()for a deleted session no longer reconstructs the run - that is what hard-delete means.
Security and auth behaviour.
- The
audit.cipherconfig setting is now honoured; it was silently ignored, so every existingaudit.dbis inchacha20format and the default is pinned tochacha20to stay byte-compatible. A config that has long carriedaudit.cipher: 'sqlcipher'will now fail to open its existing file - remove the setting or re-encrypt the audit database. Unknown cipher values fail fast. pruneAuditfails closed on customAuditDbbindings without the new optionaltransactmember. After any destructive prune, sign and distribute a fresh Merkle checkpoint (the CLI prints the reminder; the security guide carries the runbook).- Skill trust root: the
publishersleg now counts only for keys resolved through thewell-knownchannel, and the key URL's host must match the publisher domain (or a subdomain). Breaking for publisher ids that are not DNS names or keys hosted off-domain - align them, or switch those entries to thefingerprintsleg. - Rule-of-Two profiles that give up the
untrustedInputleg now actually forbid untrusted-source tools (web search, MCP) - expect denials where the leg was previously bookkeeping-only. POST /v1/tokensis attenuation-only: minting a scope the minter's own grant does not cover answers403 scope-escalation-denied. Grant minting integrations the full target scope set (oradmin:*); syntactically invalid scopes now answer 400.- Scopes are symmetric per resource: session REST reads and the SSE fallback accept
sessions:<verb>:<sessionId>grants (session-scoped tokens gain the REST/SSE access they already had on WS), while run control (REST/runs/:runId/*, WSrun.cancel) resolves the run and requires the owningagents:...:<agentId>/workflows:...:<workflowId>grant - a three-segment grant for another agent's run is now denied. POST /v1/agents/:id/streamrejects a malformed body with400 config-invalid; clients that relied on the silent 202 (which launched the agent on an empty prompt) must send a valid body.- With an audit chain configured, security-relevant tool events (dataflow flags, sanitization hits, approvals, collisions) are now appended to it by default; tune with
audit.toolEvents: 'security' | 'all' | 'off'.
MCP identity and pinning.
- MCP server identity is now transport-derived (the self-reported name survives as display-only
reportedServerName). Existing TOFUpinStorerecords keyed by old server-controlled ids orphan and re-pin under the transport id on firsttoTools(); operators runningonPinMismatch: 'reject'must re-pin. Registry auto-prefix namespaces (model-visible prefixed tool names) may change. - With a
pinStore, a tool added after the first-use recording is now rejected by default (MCPToolPinningError). Servers that legitimately extend their catalogue: useonPinMismatch: 'warn'or the new'accept-and-update'. - Model-facing bytes may change: MCP tool-schema annotations and
isErrortext are sanitized at the boundary (semantic schema keywords are never modified; input validation is byte-identical).
Agent runtime behaviour.
- Sub-agent suspension (composed durable HITL): a handoff or
toToolchild that suspends on an approval-gated tool now parks on the parent - the run endsawaiting_approvalwith the child's approvals mirrored intopendingApprovals- instead of surfacing a terminalexecution_failed. EchosubRunToolCallId(alongsidetoolCallId) back inApprovalDecision. Failed and aborted children still surface as tool errors. - Child-run usage now folds into the parent's
usage/usageByModel: budget hooks and pricing start seeing the real (larger) numbers, and run-levelgen_ai.usage.*attributes include delegated tokens. RunState.currentAgentIdis restored to the parent when a handoff child returns: post-handoffRunStep.agentId, resume attribution and session JSONL exports now identify the parent agent (the child stays durably recorded inRunState.handoffs).RunStep.stepNumberis strictly monotonic across suspend/resume: the resume step takes max + 1 instead of a hard-coded 0. Dashboards that expected resume steps numbered 0, or post-resume numbering to restart, must adapt.- Abort policy: aborting with an empty approval queue now ends
'aborted', never'failed'(onPendingApprovals: 'fail'fails the run only when approvals are actually pending);'deny'commits a tool message per drained approval. - Streaming provider failures are typed: a 429/500/529 arriving before any content now throws a
ProviderHttpError(so retry and fallback engage) instead of yieldingstream-startplus an inert error event; a mid-stream error finishes withfinishReason: 'error'instead of a synthetic'stop'. Breaking for consumers that relied on a yield-first error event or onstream-startalways preceding the throw. - Transcripts may carry several reasoning parts per step now that per-block thinking signatures round-trip.
- Writes through
ctx.stateare compile errors:RunContext.stateis typed as the newReadonlyRunStateprojection (mutating run bookkeeping from tools and hooks was never supported).
Tools behaviour.
- An inline timeout now actually aborts the tool (
ctx.signalfires), and a timeout on aside-effecting/external-statefultool without anidempotencyKeyreportsrecoverable: falsewithreport_to_userinstead of inviting a blind retry. ToolReturnunwrapping is brand-based: objects with extra fields beyond{output, contentParts, taint}now reach the model whole instead of being silently stripped to.output. Brand deliberate envelopes with the newtoolReturn()factory; a plain{output: X}stays ambiguous by contract.read_resulthandles are run-scoped by default; passcreateReadResultTool({ allowCrossRun: true })for deliberate cross-run reads (for example, folding a sub-agent's handle).- Streaming aggregation is bounded: past
streamingMaxBufferBytes(default 8 MiB) chunks keep reaching subscribers but the assembled output is truncated and flagged (bufferTruncated). - Auto-prefix collision losers are now always renamed (deterministic hash fallbacks) or observably suppressed - model-visible names of colliding tools may change.
Workflow behaviour.
maxStepsnow caps steps per invocation of execute/resume/retry/tick (the documented infinite-loop safeguard), not per thread lifetime. If you relied on the lifetime cap, set the newWorkflowConfig.maxTotalSteps.- Positional
pause()replay verifies which pause a journaled value answered: a workflow whose pause order depends on time, state or model output now fails with the typedpause-replay-divergenceerror instead of delivering values to the wrong wait. - The JSON-safety gate now covers pause values, approval payloads,
Dispatchargs and operator directives - aDateinDirective({ resume })fails at resume entry instead of silently becoming a string on the next replay. - Custom
CheckpointStoreimplementations used with the new timer driver must implementlistSuspended(otherwiseTimerDriverStoreUnsupportedError). - Step-journal semantics, documented honestly: journaled channel writes replay exactly once, side effects are at-least-once - keep effects idempotent.
Memory behaviour.
- Supersede defers interval closure: while an extraction-provenance successor sits in quarantine, recall returns the old fact (previously it returned nothing until validation).
autoPromoteExtractionnow applies on the update/conflict routes too, restoring immediate closure for injection-clean successors when opted in. - A standard-phase slice that exhausts its DLQ retries force-advances the cursor past the failed window (bounded, logged,
messageIdskept for manual replay): deliberate bounded fact loss instead of consolidation wedging forever. - Quarantined insights are exempt from reflection pass-decay until validated; the unreviewed queue is bounded by
reflectionMaxQuarantinedInsights(default 100; beyond the cap the oldest are pruned). - Compaction summary-trust fails closed: an injection-scanner timeout yields
summaryTrust: 'untrusted-derived', never'trusted'. - Default search page membership can change where foreign-provenance or quarantined facts are in play - the trust discount now applies before the final top-k cut. Purely first-party result sets stay byte-identical.
Exhaustive switches and typed contracts.
ProviderEventgains{ type: 'reasoning-end' }.AgentEventgainssubagent.event- and parents of handoff/toToolchildren emit it by default (forwardEvents: 'lifecycle').SpanTypeopens up (KnownSpanTypeplus thex.-prefixed custom domain): add a default branch; span-type analytics must tolerate unknown strings.WorkflowErrorCodegainspause-replay-divergence.- The client's
GraphorinClientErrorKindgainsflow-overflow. - Structural contracts:
CostSnapshot(and its byModel entries) gains requiredcachedReadTokens/cacheWriteTokens; the tools executor now passes the tool's trust class intoToolArgumentPolicyGuard.evaluate(type-level breaking for custom structural guard implementations);SerializedRunState.messages/.stepsare typed as their wire projections, and the run-state schema isgraphorin-run-state/1.2(1.0/1.1 snapshots stay readable, with corrupted byte payloads repaired best-effort).
Accounting and observability numbers.
Cost.amountis whole currency units (dollars), as every producer already emitted - if you added a divide-by-100 against the old core TSDoc, remove it.CostTrackermemory is bounded by default (10 000 span/scope entries, oldest-first eviction, evicted ids read zero);retention: falserestores the old unbounded behaviour.- Per-type sampling rules now thin child spans under the parent-based decision maker - previously-inert rules take effect and child-span export volume drops accordingly.
- The
graphorin_replay_buffer_eventsgauge now reports buffered events (it was wired to the subscription count); dashboards reading it see the corrected semantics. llamaCppNodeAdaptertokenizes the assembled response once, so streamedcompletionTokenscounts drop to accurate values (per-chunk counting over-billed).
Clients and CLI.
- WS consumers decoding raw event payloads: binary-bearing events (
file.generated,tool.execute.partialchunks, a multimodalagent.endstate) now arrive as JSON-safe wire projections (EncodedBytesenvelopes) - decode withfromWireAgentEventfrom@graphorin/core. - Client subscriptions are bounded (
subscriptionQueueLimit, default 10 000): a slowfor awaitconsumer now gets a typedflow-overflowclose instead of unbounded heap growth (0restores the old behaviour). On a WS-to-SSE reconnect fallback, surviving WS subscriptions close withTransportFailedErrorinstead of hanging - forcetransport: 'ws'if you need those streams. - CLI
--jsonmode now honours exit codes: pipelines that (incorrectly) relied on exit 0 on failure - worst of allgraphorin audit verify --jsonon a broken chain - now see the documented exit 1. graphorin initno longer prints a bootstrap admin token (the printed token never verified);InitCommandResult.bootstrapTokenis removed - breaking for scripts that parsed it. Follow the printed path instead: pepper via stdin,graphorin migrate,graphorin token create.graphorin storage encrypt --swap(andencryptDatabase({ swap: true })) refuses while another connection holds the database - stop the server first.
Evals and lint.
- Re-seed any locally seeded LOCOMO baselines: the ingested text changed (speaker names are rendered, numeric reference answers are stringified, and empty-reference QA pairs are skipped).
no-implicit-network-callnow activates only for packages whose name matchespackagePrefixes(default['@graphorin/']): downstream monorepos stop getting errors on their ownfetch()calls; pass your own prefix to police your scope.graphorin tools lint: repeated-filler descriptions now cap the description axis at 16 instead of 40, so--thresholdgates in CI may newly fail on degenerate descriptions.
0.5.x -> 0.6.0
- Durable HITL resume is exactly-once. With a
checkpointStorewired, an approved call's resume writes a write-ahead intent checkpoint before dispatch and the journaled post-dispatch state after it, so resuming from the latest state executes the tool exactly once. Re-check operator flows that re-resume stale pre-execution snapshots; those stay bounded at one re-execution per stale resume. - Tool parameters are real JSON Schema on the wire. Plain Zod input schemas are now converted by a structural Zod v3/v4 converter (
@graphorin/tools/schema) instead of leaking serialized validator internals to providers; unprojectable schemas degrade loudly (WARN plus a permissive{}). - Workflow durability changes. The
'async'durability source is removed.CheckpointStore.putgained an optional{ expectedLatestId }compare-and-set argument; both bundled stores honour it (a lost race throwsCheckpointConflictError), and custom store implementations should adopt it.WorkflowConfig.versionpins a workflow definition to its checkpoints (workflow-version-mismatchon divergence). - Store schema advances to migration 028 (fact-supersede indexes, the memory owner column, fact access counters, rules FTS). Migrations run automatically when the store opens; back up first.
0.4.0 -> 0.5.0
- Approved-tool resume now fires side effects. Resuming a granted HITL approval dispatches the approved call through the executor for real, so the side effect happens and its output reaches the model. Update HITL integrations that assumed resuming a grant was side-effect-free.
- Structured tool outputs spill to handles by default. Over-cap object outputs no longer bypass truncation; on the default strategy the full body is stored behind a result handle (re-fetchable via
read_result) instead of being inlined whole. Raise a tool'smaxResultTokenswhere the model must see more inline. - Store schema advances to migration 024. Migrations run automatically inside a transaction when the store opens; back up before upgrading production.
0.3.0 -> 0.4.0
- The memory program is additive. Temporal
as_ofreads, provenance + quarantine, insights, the entity graph, iterative retrieval, and procedural induction are new, opt-in surfaces; existing call sites keep working. - Store schema advances through migrations 013-017 (provenance, insights, fact importance, entities, procedures); they run automatically on open.
Data & schema migrations
- SQLite store migrations run automatically at startup inside a transaction. Take a backup and test on a staging copy before upgrading production. See Storage backends.
- Embedder changes are governed by the migration policy (
lock-on-first/multi-active/auto-migrate) - see Embedders. Changing the embedder model is a data migration, not just a config change. - Durable run / workflow state carries a schema version (
graphorin-run-state/1.x). Forward-compatible fields are synthesized when reading older snapshots; a major schema bump is documented in the changelog.
Wire & export formats
- The WebSocket protocol is versioned via the
graphorin.protocol.v1subprotocol; a futurev2will negotiate explicitly. - Session JSONL export and tool-cassette formats are versioned (schema
1.0) and read back compatibly within the documented support window.
When in doubt
Pin exact versions, upgrade in a branch, and run the full gate (pnpm run mvp-readiness) before promoting. Report regressions on the issue tracker with the from/to versions.