Skip to content

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 ​

ChangeBefore (0.4)Replacement (0.5)
Node>=24.3.0>=26.0.0
Base imageghcr.io/chamvi/node:24ghcr.io/chamvi/node:26; build stages move to node:26-trixie-slim. The 24 tag stops receiving rebuilds
chamvi build targetnode24node26
Sandbox image ghcr.io/chamvi/sandbox-node:24Rebuilt as a deprecated workspace imageNot 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 ​

ChangeBefore (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 aggregatenew 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 interfacesIRunStore, IStepStore, ICheckpointStore, IIdempotencyStore, ILockStore, IEffectStore, IStateStore, IArtifactStore, IAgentRevisionStore; IStore extended themIRunRepository, IStepRepository, ICheckpointRepository, IIdempotencyRepository, ILockRepository, IEffectRepository, IStateRepository, IArtifactRepository, IAgentRevisionRepository; IStore holds one of each (store.runs, store.steps, ...)
Database store optionsTypeormStoreOptionsDatabaseStoreOptions; createDatabaseStore() returns DatabaseStore
Purging expired rowsstore.purgeExpired(now?) resolving PurgeResultstore.idempotency.purgeExpired(now?) and store.locks.purgeExpired(now?), each resolving the number of deleted rows (@chamvi/app schedules both)
Agent instructionsAgentDefinition.instructions: stringinstructions: 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 contextChannelContext.engine: Engine; no conversation intakeChannelContext.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 appsAppTokens.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 channelsAlso 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 lockcreateStoreScheduleLock(store, owner)createRepositoryScheduleLock(store.locks, owner) (@chamvi/channel-cron, takes an ILockRepository)
Engine concurrency optionnew Engine({ maxConcurrentRuns })new Engine({ concurrencyMaxRuns }); the variable CHAMVI_MAX_CONCURRENT_RUNS is unchanged
App config concurrencyreadConfig(env).engine.maxConcurrentRunsreadConfig(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 errorAll 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 credentialsRemoved; 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 tokenWrote a literal [redacted] over the tokenEach [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 humanThe 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 taintNo taint columnDatabase 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 with trust.tools: { bash: { taint: false } } only where its output is trusted.
  • git_push with push: { 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.channel is not asked then. A push the policy refuses is denied before the gate asks.
  • A failing repository guidance collection gives instructions: null plus instructionsError in create_workspace and read_file; read_file in a repo-less workspace omits instructions.
  • ghcr.io/chamvi/sandbox-dev ships 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.

  1. Suspend the run-job CronJobs (kubectl patch cronjob <name> -p '{"spec":{"suspend":true}}', or suspend: true on each cronJobs[] entry of the agent-app chart) and wait for running job pods to finish. A Recreate Deployment does not cover CronJobs, and CHAMVI_MIGRATE defaults to true in job mode too: a 0.3 job pod started during the upgrade writes runs the backfill never sees.
  2. 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.
  3. Migrate in a maintenance window: run chamvi migrate once with the new image (DATABASE_URL set). Alternatively start the new Deployment with the default CHAMVI_MIGRATE=true; with CHAMVI_MIGRATE=false everywhere, chamvi start --check refuses the unmigrated database.
  4. 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-db publish: npm deprecate @chamvi/store-typeorm "renamed to @chamvi/store-db".
  • Check that the NPM_TOKEN used by release.yml (monoship) may create new packages: @chamvi/skills and @chamvi/store-db publish for the first time.
  • @chamvi/skills depends 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.x to ^0.4.0 after publishing (Node 24, the CronJob suspend note in the chart README, no CHAMVI_GIT_CREDENTIAL_MODE).