Upgrading
chamvi is pre-1.0: a minor release may break the API. Each section lists what changed and the exact replacement.
Upgrading to 0.5
Breaking changes
| Change | Before (0.4) | Replacement (0.5) |
|---|---|---|
| Node | >=24.3.0 | >=26.0.0 |
| Base image | ghcr.io/chamvi/node:24 | ghcr.io/chamvi/node:26; build stages move to node:26-trixie-slim. The 24 tag stops receiving rebuilds |
chamvi build target | node24 | node26 |
Sandbox image ghcr.io/chamvi/sandbox-node:24 | Rebuilt as a deprecated workspace image | Not built anymore; use the default ghcr.io/chamvi/sandbox-dev (mise use -g node@26) or your own image |
Upgrading to 0.4
Breaking changes
| Change | Before (0.3) | Replacement (0.4) |
|---|---|---|
| Store package renamed | @chamvi/store-typeorm | @chamvi/store-db; replace the dependency and every import. Tables and migration history keep their names |
| Store aggregate | new MemoryStore(options), new TypeormStore(options), facade methods such as engine.store.getRun(id) | createMemoryStore(options), createDatabaseStore(options), role methods such as engine.store.runs.findOne(id); every method is mapped on Stores and repositories |
| Repository interfaces | IRunStore, IStepStore, ICheckpointStore, IIdempotencyStore, ILockStore, IEffectStore, IStateStore, IArtifactStore, IAgentRevisionStore; IStore extended them | IRunRepository, IStepRepository, ICheckpointRepository, IIdempotencyRepository, ILockRepository, IEffectRepository, IStateRepository, IArtifactRepository, IAgentRevisionRepository; IStore holds one of each (store.runs, store.steps, ...) |
| Database store options | TypeormStoreOptions | DatabaseStoreOptions; createDatabaseStore() returns DatabaseStore |
| Purging expired rows | store.purgeExpired(now?) resolving PurgeResult | store.idempotency.purgeExpired(now?) and store.locks.purgeExpired(now?), each resolving the number of deleted rows (@chamvi/app schedules both) |
| Agent instructions | AgentDefinition.instructions: string | instructions: AgentInstructions (a string or an array of system, user and assistant messages). A string still works; code that reads agent.definition.instructions as a string must handle the array form |
| Channel context | ChannelContext.engine: Engine; no conversation intake | ChannelContext.engine: IEngine (type against the interface, not the class); ChannelContext.dispatchConversation(envelope, target) is required: a hand-built context needs one, get both from engine.channelContext(channel) |
| Engine typing in apps | AppTokens.ENGINE: Engine, ILifecycleHook.engineCreated(engine: Engine) | Both typed IEngine. Cast to Engine only where you need a member outside IEngine |
Engine.stop() | Stopped polling and the channels | Also halts the queue and waits for an in-flight start() and prepare() (skill preparation). A skill provider that ignores cancellation holds stop() until it settles |
| Cron schedule lock | createStoreScheduleLock(store, owner) | createRepositoryScheduleLock(store.locks, owner) (@chamvi/channel-cron, takes an ILockRepository) |
| Engine concurrency option | new Engine({ maxConcurrentRuns }) | new Engine({ concurrencyMaxRuns }); the variable CHAMVI_MAX_CONCURRENT_RUNS is unchanged |
| App config concurrency | readConfig(env).engine.maxConcurrentRuns | readConfig(env).engine.concurrencyMaxRuns (AppConfig); the variable CHAMVI_MAX_CONCURRENT_RUNS is unchanged |
| Run references (memory, database and custom stores) | steps.insert, checkpoints.save and conversations.beginTurn accepted any run id on the memory store; Postgres failed with a raw foreign key error | All three throw RunNotFoundError for a run the store does not hold and write nothing, on memory and Postgres alike. describeRepositoryContract() checks it, so a custom store must implement it to pass (Run references). Insert the run first; a test that spreads { ...createMemoryStore(), runs: custom } builds steps/checkpoints/conversations with hasRun over custom (see Memory repositories) |
| Node | >=22 | >=24.3.0 |
Git credential mode (@chamvi/sandbox) | credentialMode (CredentialMode) and CHAMVI_GIT_CREDENTIAL_MODE chose tools-only or workspace-installed credentials | Removed; a set variable or option is refused with InvalidAgentDefinitionError naming them: unset the variable and drop the option. Workspace-installed credentials are the only mode; PushPolicy binds git_push only, so restrict writes through token scope and branch protection |
Image requirements (@chamvi/sandbox) | IMAGE_REQUIREMENTS ['sh', 'git', 'timeout'] | ['sh', 'git', 'timeout', 'head', 'base64', 'tr']: the image probe also refuses an image without /bin/sh or without head, base64 and tr in /usr/bin or /bin (coreutils or busybox provide them; ghcr.io/chamvi/sandbox-dev has them) |
write_file over a file holding an issued token | Wrote a literal [redacted] over the token | Each [redacted] the model read is restored in order; content whose markers do not line up, or with [redacted] over an existing file that cannot be read as text, is refused (ValidationError) and nothing is written |
Event-started runs (github, webhook) | A delivery started a run whose write tools ran at once, so an event-driven auto-merge or auto-reply flow acted without a human | The github and webhook channels are untrusted ingress: their runs are tainted from the first step, and github_merge_pull_request, github_create_pull_request, github_create_issue, github_propose_change, git_push and a github_create_comment on another issue than the source one wait for an approval (the source issue gets a generic "An outbound operation requires review" comment, never the arguments). Answer it in the approval inbox or through POST /runs/:id/approve (MCP approve_run). An agent whose own policy already decides opts out per tool: trust: { tools: { github_merge_pull_request: { guardWhenTainted: false } } } (Approvals in event-started runs) |
| Run taint | No taint column | Database migration ConversationsRunTaint1792713600000 adds chamvi_runs.taint and backfills unfinished runs; it needs downtime (below). Custom stores must persist Run.taint (Persist run taint) |
New in 0.4
Run taint, the outbound gates and repository guidance are new, so 0.3 apps have nothing to migrate beyond the rows above. In their final 0.4 form:
- The coding read and shell tools (
read_file,grep,glob,list_files,git_diff,bash,bash_output) taint the run in every workspace, repo-less ones included; opt a tool out withtrust.tools: { bash: { taint: false } }only where its output is trusted. git_pushwithpush: { mode: 'approve' }asks once in a tainted run: the outbound gate's private approval carries the push title and commit log (ToolTrust.approvalDetail,outboundApproved(ctx)) and is answered in the approval inbox or API;push.channelis not asked then. A push the policy refuses is denied before the gate asks.- A failing repository guidance collection gives
instructions: nullplusinstructionsErrorincreate_workspaceandread_file;read_filein a repo-less workspace omitsinstructions. ghcr.io/chamvi/sandbox-devships the GitHub CLI (gh, the current release at build time); pin the image digest for a fixed version.
Conversation memory is opt-in and new, so 0.3 apps have nothing to migrate: storage is store.conversations (both built-in stores carry it), policy is createApp({ conversations: { authorize } }) or EngineOptions.conversations. See Conversations for the setup.
Database migration
0.4 ships one new migration, 1792713600000-conversations-run-taint.ts (ConversationsRunTaint1792713600000): the conversation tables, the run and checkpoint conversation bindings, the foreign keys (the two on legacy steps and checkpoints NOT VALID, see Validating the legacy constraints), indexes on the new foreign key columns, chamvi_runs.taint and the backfill that taints every queued, running, waiting and interrupted run with { at: <migration time>, source: 'migration:legacy-run' }.
Development databases
A database that ran a pre-release build of this branch recorded Conversations1792713600000 (and maybe RunTaint1792800000000). Migrating it fails with "relation already exists". Recreate the disposable database; never reset a production one for this.
Upgrade procedure (downtime)
Old and new runtimes must never share the store, not even during a rolling update. An old approval poller can post a tainted run's private approval body to its source channel, and an old worker or job can create or checkpoint untainted runs after the one-time backfill. Claims and locks do not prevent this.
- Suspend the run-job CronJobs (
kubectl patch cronjob <name> -p '{"spec":{"suspend":true}}', orsuspend: trueon eachcronJobs[]entry of theagent-appchart) and wait for running job pods to finish. ARecreateDeployment does not cover CronJobs, andCHAMVI_MIGRATEdefaults totruein job mode too: a 0.3 job pod started during the upgrade writes runs the backfill never sees. - Stop every old process: scale the Deployment to 0 (ingress, workers, approval pollers) and wait for the pods to exit. Draining executions alone is not enough.
- Migrate in a maintenance window: run
chamvi migrateonce with the new image (DATABASE_URLset). Alternatively start the new Deployment with the defaultCHAMVI_MIGRATE=true; withCHAMVI_MIGRATE=falseeverywhere,chamvi start --checkrefuses the unmigrated database. - Start only 0.4 binaries: scale the Deployment up, then resume the CronJobs with the new image. Resumed legacy runs are tainted, so their guarded tools ask for approval.
See Multiple replicas for why each step matters.
Release-time owner actions
These are not code changes; the maintainer does them when 0.4.0 is published:
- After the first
@chamvi/store-dbpublish:npm deprecate @chamvi/store-typeorm "renamed to @chamvi/store-db". - Check that the
NPM_TOKENused byrelease.yml(monoship) may create new packages:@chamvi/skillsand@chamvi/store-dbpublish for the first time. @chamvi/skillsdepends on a Git URL (git+https://github.com/tada5hi/skills-cli.git#<sha>): registry-only proxies and the agent-template lockfile check refuse it. Publish or vendor the fork before relying on it (#97).- Downstream: bump chamvi/helm and chamvi/agent-template from
^0.3.xto^0.4.0after publishing (Node 24, the CronJob suspend note in the chart README, noCHAMVI_GIT_CREDENTIAL_MODE).