Skip to main content

Upgrading

Upgrading to v1.8.0​

go get github.com/dbos-inc/dbos-transact-golang@v1.8.0

If your application imports go.dbos.dev/dbos-enterprise-go, upgrade it to the same minor version:

go get go.dbos.dev/dbos-enterprise-go@v1.8.0

v1.8.0 is additive: existing code keeps compiling. This release adds system-database migrations (indexes on application_versions), applied by the first NewContext. New APIs and behavioral changes:

  • Auto-upgrade. A DBOS Transact Enterprise feature that moves workflows left on older application versions to the latest one, so each deployment can run under its own version without keeping old processes around to drain. Enable it for every workflow with Config.AutoUpgrade, or per workflow with the WithAutoUpgrade(bool) registration option, and tune it with Config.AutoUpgradeInterval and Config.AutoUpgradeGracePeriod. It requires importing go.dbos.dev/dbos-enterprise-go and an entitled DBOS key, Config.TransactEnterpriseKey or ConductorAPIKey standing in for it; NewContext (or Launch) fails with an error saying what is missing otherwise. See Auto-Upgrading Workflows and DBOS Transact Enterprise.
  • Config.EnablePatching is deprecated. Patch and DeprecatePatch no longer require it, and ErrorCodePatchingNotEnabled is no longer returned. The flag now only pins the application version to PATCHING_ENABLED, so every deployment shares one version, and NewContext logs a warning when it is set. To keep that behavior, set Config.ApplicationVersion explicitly; to give each deployment its own version instead, enable auto-upgrade.
  • New WithMergeAttributes() option on SetWorkflowAttributes, shallow-merging the given top-level keys into a workflow's attributes in one update instead of replacing them. SetWorkflowAttributes is variadic now, so existing calls are unchanged.
  • Portable workflow inputs. New WithEnqueuePortableWorkflow() option on Enqueue, storing the enqueued workflow's input in the portable JSON envelope so it runs as a portable workflow when dequeued; passing a PortableWorkflowArgs input still implies it. A child workflow started from a portable workflow, or a workflow enqueued or debounced from one, is now stored in its own format (portable only when it is itself started with WithPortableWorkflow, enqueued with WithEnqueuePortableWorkflow, or given a PortableWorkflowArgs input), where it used to be tagged as portable while its input was stored without the envelope, so that a struct input could decode to its zero value on dequeue or recovery.

Upgrading to v1.7.0​

go get github.com/dbos-inc/dbos-transact-golang@v1.7.0
  • Connecting to Conductor requires importing go.dbos.dev/dbos-enterprise-go. The Conductor client is no longer part of github.com/dbos-inc/dbos-transact-golang. An application that sets Config.ConductorAPIKey, or that runs in DBOS Cloud, must add the import once, anywhere in the program, after go get go.dbos.dev/dbos-enterprise-go:

    import (
    "github.com/dbos-inc/dbos-transact-golang/dbos"
    _ "go.dbos.dev/dbos-enterprise-go"
    )

    Conductor configuration is unchanged. Without the import, NewContext fails with an initialization error saying what to add. Applications that do not connect to Conductor need no change. See Connecting to Conductor.

  • Transient database errors in transactions are no longer checkpointed. A transient database error (a connection failure, or a serialization or deadlock conflict) returned by a RunAsTransaction function is now retried with a fresh transaction instead of being treated as an application error: it does not consume the step's retry budget and is never durably recorded as the step's error, so a transient failure can no longer fail a workflow for good. Application errors still follow the step retry policy.

Upgrading to v1.6.0​

go get github.com/dbos-inc/dbos-transact-golang@v1.6.0

v1.6.0 is additive: existing code keeps compiling. Behavioral changes and new APIs:

  • Workflow ownership (fencing tokens). Every execution of a workflow now holds a fencing token, so an execution that lost ownership (because the workflow was recovered, resumed, rewound, or cancelled elsewhere) can no longer checkpoint over the current owner. Its next DBOS operation returns an error matching dbos.ErrConflictingWorkflowID; return it from the workflow and the execution adopts the owner's outcome. A step run with Go that completes after its workflow has completed is discarded. See Concurrent Executions.
  • New error code ErrorCodeStepNondeterminism / ErrStepNondeterminism, returned when an execution records a step ID it had already recorded with a different result (a non-deterministic workflow function).
  • Workflow ID reuse policies. Starting a workflow with an ID that already exists still returns a handle to the existing workflow by default (WorkflowIDReusePolicyReturnExisting). Pass WithWorkflowIDReusePolicy(dbos.WorkflowIDReusePolicyReject) (or WithEnqueueWorkflowIDReusePolicy on Enqueue) to get an error matching the new dbos.ErrWorkflowIDInUse instead. The reject policy cannot be used with a debouncer.
  • Workflow timeouts are enforced by a monitor. Every launched process runs a monitor that durably cancels this application's workflows past their deadline, including ones no process is executing, and cancels the Context of those running locally. The monitor polls once per second, so a workflow's Context may be cancelled up to a second after its deadline. An execution parked on a conflicting ID now waits on the root context (cancelled only by Shutdown), so a caller may wait longer than the context's nominal timeout for the durable workflow's outcome.
  • Child workflows inherit their parent's deadline. A child started under its parent's context now carries the parent's deadline from the start, even when enqueued (it used to start its own timeout at dequeue). Pass a context from WithoutCancel or a shorter WithTimeout to detach it, as before.
  • New WithDeadlineCause(ctx, deadline, cause), the absolute-deadline counterpart of WithTimeout; see the context reference.
  • Self-recovery is skipped when Conductor is configured. When ConductorAPIKey is set or the application runs in DBOS Cloud, Launch no longer recovers this executor's PENDING workflows itself; Conductor drives recovery. Applications without Conductor are unchanged.
  • Idle-in-transaction timeout. DBOS now sets Postgres's idle_in_transaction_session_timeout on the connections it creates, to Config.SystemDBIdleTransactionTimeout (also on ClientConfig; default 1 minute). Set it to a negative duration to keep the server's setting.
  • Datasource migrations. A datasource's transaction_completion table is now versioned by a dbos_transaction_completion_migrations table, created or migrated by NewDataSource. New: MigrateDataSource migrates the schema out of band with a privileged role (WithMigrateApplicationRole grants a role the runtime permissions: SELECT, INSERT, DELETE on transaction_completion and SELECT on the version table); WithDataSourceSkipMigrations makes NewDataSource verify the schema instead of migrating it; DataSourcePermissionStatements returns the grants. A table pre-created by hand under the old scheme is adopted by the first migration, which needs DDL privileges once to create the version table. Likewise, Migrate and MigrationStatements now migrate the system database from code, which is what dbos migrate runs.
  • New Config.ConductorMetadataOnlyMode: send only workflow metadata to Conductor, never workflow data, regardless of the console setting. See Connecting to Conductor.
  • New dbos.GetAuthenticatedUser(ctx), returning the current workflow's authenticated user from a workflow or one of its steps. See the reference.
  • New WithSendToForks() option on Send and SendBulk, delivering a message to every workflow recursively forked from the destination. See the reference.
  • New WithFilterIsFork(bool) filter on ListWorkflows, selecting workflows that are (or are not) forks of another workflow.

Upgrading to v1.5.0​

go get github.com/dbos-inc/dbos-transact-golang@v1.5.0
  • NewDataSource must be called before Launch. On a Context created with NewContext, creating a datasource after launch now returns an error (it used to be allowed at any time). The context tracks every datasource created on it so that rewinding a workflow also deletes the transaction checkpoints it holds. NewDataSource now takes a Client, so a standalone client can create datasources too, at any time.
  • New RewindWorkflow. Re-execute a workflow from a given step while keeping its workflow ID, on a Context or a Client, with options WithRewindStartStep, WithRewindApplicationVersion, WithRewindQueue, WithRewindQueuePartitionKey, and WithRewindDataSources. See Rewinding Workflows and the reference. This release adds system-database migrations (index changes and a consumed_by_function_id column on notifications), applied by the first NewContext.
  • Panics in workflows are recovered. A panic in a workflow function, or in a step run with Go, no longer crashes the process: DBOS recovers it, logs it with its stack, and records it as the workflow's error under the new ErrorCodeWorkflowPanic code (dbos.ErrWorkflowPanic). When the panic value is an error, it is wrapped, so errors.Is/errors.As still reach it. Code that relied on a workflow panic terminating the process must now check the workflow's error.

Upgrading to v1.0.0​

This guide covers migrating an application from the last v0.x release to v1.0.0:

go get github.com/dbos-inc/dbos-transact-golang@latest

v1 is a breaking release. The sections below cover every breaking change; §10 suggests an order to apply them in.


1. Core renames: DBOSContext → Context​

The central interface was renamed. This is the highest-volume change and is purely mechanical:

v0v1
dbos.DBOSContextdbos.Context
dbos.NewDBOSContext(ctx, cfg)dbos.NewContext(ctx, cfg)
Config.SqliteSystemDBConfig.SQLiteSystemDB

Workflow and step signatures change accordingly:

// v0
func myWorkflow(ctx dbos.DBOSContext, input string) (string, error)
// v1
func myWorkflow(ctx dbos.Context, input string) (string, error)

Shutdown now returns an error (non-nil if the timeout expired before all resources stopped). Use the package-level functions for launch and shutdown — like the other interface methods, Shutdown now takes a leading Client receiver-argument, so method-form calls must be migrated:

// v0
ctx.Launch()
ctx.Shutdown(30 * time.Second)
// v1
err := dbos.Launch(ctx)
err := dbos.Shutdown(ctx, 30*time.Second) // accepts Client or Context

Behavioral (no code change): Launch() now recovers this executor's PENDING workflows before starting the queue runner and scheduler.

Behavioral (no code change): a failed Launch() is now terminal — the context is torn down (system database closed) and cannot be relaunched. Code that retried Launch on the same context after a transient failure must create a fresh context with NewContext for each attempt.

2. Client redesign — Client is now a subset of Context​

Client is no longer a separate wrapper type with its own method shapes. It is a formal sub-interface of Context (both embed context.Context), owning every operation that doesn't require Launch(): enqueue, send/get-event/read-stream, workflow management, queue management, schedule management, app-version management, Shutdown. Every Context is a Client, so a launched Context can be passed anywhere a Client is accepted.

  • dbos.NewClient(ctx, dbos.ClientConfig{...}) still exists and returns a Client. ClientConfig gained SystemDBStartupTimeout and renamed SqliteSystemDB → SQLiteSystemDB.
  • Client interface methods now take a leading Client receiver-argument like Context methods always did (c.ListWorkflows(c, ...)). In practice, always use the package-level generic functions instead.

The Client* generic helpers are gone​

The duplicated client-side generics were removed. The single package-level generic functions now accept a Client (and therefore also a Context):

v0 (client)v1 (unified)
dbos.ClientRetrieveWorkflow[R](client, id)dbos.RetrieveWorkflow[R](client, id)
dbos.ClientGetEvent[R](client, wfid, key, timeout)dbos.GetEvent[R](client, wfid, key, timeout)
dbos.ClientReadStream[R](client, wfid, key)dbos.ReadStream[R](client, wfid, key)
dbos.ClientReadStreamAsync[R](client, wfid, key)dbos.ReadStreamAsync[R](client, wfid, key)
dbos.ClientResumeWorkflow[R](client, id)dbos.ResumeWorkflow[R](client, id)
dbos.ClientResumeWorkflows[R](client, ids)dbos.ResumeWorkflows[R](client, ids)
dbos.ClientForkWorkflow[R](client, input)dbos.ForkWorkflow[R](client, input)
dbos.ClientForkWorkflows[R](client, input)dbos.ForkWorkflows[R](client, input)
dbos.ClientTriggerSchedule[R](client, name)dbos.TriggerSchedule[R](client, name)
client.Enqueue(queue, wf, input) (method)dbos.Enqueue[R](client, queue, wf, input)

Enqueue type parameters reordered​

Enqueue[P, R] became Enqueue[R, P] so the input type is inferred and you name only the result type:

// v0
handle, err := dbos.Enqueue[string, int](client, "queue", "MyWorkflow", "input")
// v1
handle, err := dbos.Enqueue[int](client, "queue", "MyWorkflow", "input")

Same reorder applies to both debouncer types: Debouncer[P, R] / NewDebouncer[P, R] → Debouncer[R, P] / NewDebouncer[R, P], and DebouncerClient[P, R] / NewDebouncerClient[P, R] → DebouncerClient[R, P] / NewDebouncerClient[R, P]. NewDebouncer's type parameters are still inferred from the workflow function, but it now also returns an error (see §7), and explicitly-typed variables must swap: var d *dbos.Debouncer[string, int] → *dbos.Debouncer[int, string]. NewDebouncerClient cannot infer its parameters — every call site must swap them by hand; watch for cases where input and result types are mutually assignable (e.g. both string), which compile unchanged with swapped meaning.

Functions that widened from Context to Client​

These package-level functions now take a Client first argument. Existing call sites passing a Context keep compiling — listed for completeness: Send, GetEvent, ReadStream, ReadStreamAsync, RetrieveWorkflow, CancelWorkflow(s), ResumeWorkflow(s), ForkWorkflow(s), DeleteWorkflows, SetWorkflowDelay, ListWorkflows, GetWorkflowSteps, GetWorkflowAggregates, GetStepAggregates, all queue-management, all schedule-management, all app-version functions.

Still Context-only (require a launched runtime / workflow scope): RunWorkflow, RunAsStep, RunAsTransaction, Go, Select, Recv, SetEvent, WriteStream, CloseStream, Sleep, Patch, DeprecatePatch, GetWorkflowID, GetStepID, RegisterWorkflow, ListenQueues.

3. Queues​

NewWorkflowQueue removed → RegisterQueue​

In-memory queues are gone; all queues are database-backed. The public WorkflowQueue struct was unexported; the Queue interface (GetName(), GetGlobalConcurrency(), Set* methods, …) is the public handle.

// v0
queue := dbos.NewWorkflowQueue(ctx, "queue", dbos.WithWorkerConcurrency(5)) // before Launch only
// v1
queue, err := dbos.RegisterQueue(ctx, "queue", dbos.WithWorkerConcurrency(5)) // works before or after Launch, and from a Client
  • RegisterQueue returns (Queue, error) — handle the error. This typically ripples outward: per-module registration helpers that were func Register(ctx dbos.Context) become error-returning, and every host binary that calls them needs the plumbing. Budget for that in apps with per-agent registration functions.
  • WithMaxTasksPerIteration option removed (no replacement).
  • Field access via struct (queue.Name) → interface getters (queue.GetName()).
  • Queue Set* methods now take a Client (any Context works).
  • Registering the reserved internal queue name is an error.

WithQueue takes a Queue, not a name​

// v0
handle, err := dbos.RunWorkflow(ctx, task, i, dbos.WithQueue(queue.Name))
// v1
handle, err := dbos.RunWorkflow(ctx, task, i, dbos.WithQueue(queue))

Apps that only kept the queue name (a common v0 pattern, since WithQueue took a string) must now retain the Queue handle returned by RegisterQueue — typically in the package var where the name constant used to suffice. If only the name is at hand, fetch the handle first with dbos.RetrieveQueue(ctx, name) — which now returns an error matching dbos.ErrQueueNotFound when absent, instead of (nil, nil). Update any q == nil existence checks:

// v1
q, err := dbos.RetrieveQueue(ctx, name)
if errors.Is(err, dbos.ErrQueueNotFound) { /* absent */ }

Listen set​

  • ListenQueues(ctx, queues ...WorkflowQueue) → ListenQueues(ctx, names ...string). Each call replaces the whole set; empty set = listen to every queue.
  • New: ListenedQueues(ctx) []string.
  • ListRegisteredQueues (deprecated in v0) removed — use ListQueues(ctx).
  • DeleteQueue on a nonexistent queue is not an error.

4. Scheduled workflows​

WithSchedule registration option removed​

Cron scheduling is no longer a registration-time option; schedules are database-backed rows created after Launch (or from a Client). The workflow must accept dbos.ScheduledWorkflowInput:

// v0
dbos.RegisterWorkflow(ctx, func(ctx dbos.DBOSContext, scheduledTime time.Time) (string, error) {
...
}, dbos.WithSchedule("* * * * * *"))

// v1
reportWorkflow := func(ctx dbos.Context, input dbos.ScheduledWorkflowInput) (any, error) {
// input.ScheduledTime is the cron tick time
...
}
dbos.RegisterWorkflow(ctx, reportWorkflow)
// ... after dbos.Launch(ctx):
err := dbos.CreateSchedule(ctx, dbos.ScheduleSpec{
ScheduleName: "report-schedule",
Schedule: "* * * * * *",
Workflow: reportWorkflow, // or WorkflowName: "..."
})

One input struct: ScheduleSpec​

CreateScheduleRequest + its functional options (WithScheduleContext, WithAutomaticBackfill, WithCronTimezone, WithScheduleQueueName, WithScheduleWorkflowClassName), ApplySchedulesRequest, and ClientScheduleInput all collapse into ScheduleSpec:

type ScheduleSpec struct {
ScheduleName string // required
Schedule string // required, cron expression
WorkflowName string // required unless Workflow is set
Workflow any // registered Go workflow fn (Context only; wins over WorkflowName)
WorkflowClassName string
Context any // serialized as JSON
AutomaticBackfill bool
CronTimezone string
QueueName string // defaults to the internal queue
}
  • CreateSchedule(ctx, fn, req, opts...) → CreateSchedule(ctx, spec); ApplySchedules takes []ScheduleSpec.
  • Field rename: ApplySchedulesRequest.WorkflowFn → ScheduleSpec.Workflow. Renaming the type alone leaves WorkflowFn: behind at every schedule literal — rename the field too (included in the §10 sed list).
  • ScheduledWorkflowInput.Context is now json.RawMessage; decode with the new dbos.DecodeScheduleContext[T](input). Creating schedules is unaffected — ScheduleSpec.Context stays any and still takes your struct directly; the SDK serializes it. Only code that hand-constructs a ScheduledWorkflowInput (test harnesses, manual-trigger paths that invoke a scheduled workflow directly to simulate a tick) must now json.Marshal the context into the field.

Gob-serializer apps: drain scheduled firings before upgrading. ScheduledWorkflowInput.Context changed from any to json.RawMessage under the same gob registration, and gob cannot decode the old interface-encoded field into the new concrete type. Any schedule firing still ENQUEUED (or otherwise not yet dequeued) at upgrade time in an app using NewGobSerializer will fail input decoding and error when it runs — those workflows do not make it through the upgrade. Let pending firings drain (or cancel them) before deploying v1. Apps on the default JSON serializer are unaffected.

  • GetSchedule returns (WorkflowSchedule, error) by value (was *WorkflowSchedule), with dbos.ErrScheduleNotFound when absent.
  • TriggerSchedule is now generic: TriggerSchedule[R](ctx, name).

5. Errors​

DBOSError/DBOSErrorCode renamed and sentinels introduced:

v0v1
*dbos.DBOSError*dbos.Error
dbos.DBOSErrorCodedbos.ErrorCode
ConflictingIDErrorErrorCodeConflictingID
InitializationErrorErrorCodeInitialization
NonExistentWorkflowErrorErrorCodeNonExistentWorkflow
ConflictingWorkflowErrorErrorCodeUnexpectedWorkflow (renamed concept)
WorkflowCancelledErrorCodeWorkflowCancelled
UnexpectedStepErrorCodeUnexpectedStep
AwaitedWorkflowCancelledErrorCodeAwaitedWorkflowCancelled
ConflictingRegistrationErrorErrorCodeConflictingRegistration
WorkflowUnexpectedTypeErrorErrorCodeWorkflowUnexpectedType
WorkflowExecutionErrorErrorCodeWorkflowExecution
StepExecutionErrorErrorCodeStepExecution
DeadLetterQueueErrorErrorCodeDeadLetterQueue
MaxStepRetriesExceededErrorCodeMaxStepRetriesExceeded
QueueDeduplicatedErrorCodeQueueDeduplicated
PatchingNotEnabledErrorCodePatchingNotEnabled
TimeoutErrorErrorCodeTimeout
NoApplicationVersionsErrorCodeNoApplicationVersions
— (new)ErrorCodeQueueNotFound, ErrorCodeScheduleNotFound, ErrorCodeInvalidOption

Prefer the new sentinels with errors.Is over constructing an &Error{Code: ...} probe:

// v0
if errors.Is(err, &dbos.DBOSError{Code: dbos.QueueDeduplicated}) { ... }
// v1
if errors.Is(err, dbos.ErrQueueDeduplicated) { ... }

Sentinels: ErrWorkflowCancelled, ErrAwaitedWorkflowCancelled, ErrQueueDeduplicated, ErrNonExistentWorkflow, ErrConflictingWorkflowID, ErrUnexpectedWorkflow, ErrMaxStepRetriesExceeded, ErrDeadLetterQueue, ErrTimeout, ErrQueueNotFound, ErrScheduleNotFound, ErrNoApplicationVersions, ErrInvalidOption. Timeout errors from an expired deadline also match context.DeadlineExceeded; context-driven cancellations (durable timeout, cancelled context) carry a cause matching context.Canceled or context.DeadlineExceeded. Every code and sentinel is documented in the errors reference.

Type assertions on the error struct: var e *dbos.DBOSError; errors.As(err, &e) → var e *dbos.Error; errors.As(err, &e).

6. Option renames​

ListWorkflows filters: With* → WithFilter* (several became variadic)​

v0v1
WithWorkflowIDs(ids []string)WithFilterWorkflowIDs(ids ...string)
WithStatus(s []WorkflowStatusType)WithFilterStatus(s ...WorkflowStatusType)
WithStartTime(t)WithFilterCreatedAfter(t)
WithEndTime(t)WithFilterCreatedBefore(t)
WithName(n ...string)WithFilterName(n ...string)
WithAppVersion(v ...string)WithFilterAppVersion(v ...string)
WithUser(u ...string)WithFilterUser(u ...string)
WithLimit(n) / WithOffset(n)WithFilterLimit(n) / WithFilterOffset(n)
WithSortDesc()WithFilterSortDesc()
WithWorkflowIDPrefix(p ...string)WithFilterWorkflowIDPrefix(p ...string)
WithLoadInput(b) / WithLoadOutput(b)WithFilterLoadInput(b) / WithFilterLoadOutput(b)
WithQueueName(q ...string)WithFilterQueueName(q ...string)
WithQueuesOnly()WithFilterQueuesOnly()
WithExecutorIDs(ids []string)WithFilterExecutorIDs(ids ...string)
WithForkedFrom(f ...string)WithFilterForkedFrom(f ...string)
WithParentWorkflowID(p ...string)WithFilterParentWorkflowID(p ...string)
WithCompletedAfter/Before(t)WithFilterCompletedAfter/Before(t)
WithDequeuedAfter/Before(t)WithFilterDequeuedAfter/Before(t)
WithWasForkedFrom(b)WithFilterWasForkedFrom(b)
WithHasParent(b)WithFilterHasParent(b)

Slice-taking filters became variadic — call sites passing a literal slice need ... or unwrapping: WithStatus([]dbos.WorkflowStatusType{dbos.WorkflowStatusSuccess}) → WithFilterStatus(dbos.WorkflowStatusSuccess).

Step retry options​

v0v1
WithBackoffFactor(f)WithStepBackoffFactor(f)
WithBaseInterval(d)WithStepBaseInterval(d)
WithMaxInterval(d)WithStepMaxInterval(d)
WithRetryPredicate(fn)WithStepRetryPredicate(fn)
— (new)WithTxIsolation(level IsoLevel)

Other option/function renames​

v0v1
WithMaxRetries(n) (workflow registration)WithMaxRecoveryAttempts(n)
CancelWorkflowOptions (type)CancelWorkflowOption
WithAuthenticatedRoles(roles []string)WithAuthenticatedRoles(roles ...string)
WithEnqueueAuthenticatedRoles(roles []string)WithEnqueueAuthenticatedRoles(roles ...string)
UpdateWorkflowAttributes(ctx, id, attrs)SetWorkflowAttributes(ctx, id, attrs)
WithReadStreamSnapshot(fromOffset int)WithReadStreamSnapshot() + WithReadStreamFromOffset(offset int)

7. Miscellaneous API changes​

  • Go[R](ctx, fn) now returns a receive-only <-chan StepOutcome[R] (was chan). Only breaks call sites that stored it in an explicitly-typed bidirectional variable.
  • GobSerializer struct unexported — construct via dbos.NewGobSerializer() (unchanged). Persisted v0 gob error payloads remain decodable.
  • ListRegisteredWorkflows(ctx) returns []WorkflowRegistryEntry — no error, no options; WithScheduledOnly / ListRegisteredWorkflowsOption removed.
  • GetLatestApplicationVersion returns (VersionInfo, error) by value (was *VersionInfo). nil checks → errors.Is(err, dbos.ErrNoApplicationVersions).
  • ClearRegistries removed from the public API (was testing-only).
  • New package-level accessors: dbos.GetApplicationVersion(ctx), dbos.GetExecutorID(ctx), dbos.GetApplicationID(ctx).
  • Config.DatabaseURL accepts Postgres/CockroachDB URLs or key=value DSNs, and sqlite URLs (sqlite:/path/to.db, sqlite:relative.db, sqlite::memory:).
  • SQLite now requires a driver import. The SQLite driver moved out of the core package into dbos/driver/sqlite, registered database/sql-style. Apps using a sqlite URL or Config.SQLiteSystemDB must add import _ "github.com/dbos-inc/dbos-transact-golang/dbos/driver/sqlite" (one blank import, anywhere in the binary); without it, startup fails with an error naming this import. Postgres-only apps need no change and no longer compile or link modernc.org/sqlite.
  • One additive system-database schema change. The first v1 startup applies an additive migration that adds two defaulted columns to workflow_status. Existing workflows (including long-DELAYED ones), queues, and schedule rows carry over as-is, and v0.x and v1 executors can share a system database during a rolling upgrade: old executors ignore the new columns and skip migration versions they don't know.
  • Debouncer changes. NewDebouncer now returns (*Debouncer[R, P], error) — call sites gain error handling (type parameters are still inferred). Debouncers can be created at any time, including after Launch() (v0 required creation before launch). New options: WithDebouncerQueue(name) runs the debounced workflow on a named registered queue instead of the DBOS internal queue, and WithDebouncerClassName(name) (client-side) targets workflows registered under a class name. Debounce now rejects options a debounce owns or cannot support (WithQueue, WithDeduplicationID, WithDelay, WithPriority, WithQueuePartitionKey, WithDeduplicationPolicy) with an error matching dbos.ErrInvalidOption. Pending debounced workflows are now visible to workflow management: they appear as DELAYED on their queue with IsDebounced set and can be listed with dbos.WithFilterIsDebounced(true). The debounce timeout is captured by the call that enqueues the workflow — later calls coalescing on the same pending workflow extend the delay up to that fixed deadline but never move it — and a deadline on the Debounce context is recorded as the debounced workflow's execution timeout (the clock starts at dequeue), never as an absolute deadline.
  • Drain debouncers before upgrading. A debounce still pending when you deploy v1 will not fire under the new version. Before upgrading, stop calling Debounce and let in-flight debounces fire (this takes at most the debounce delay or timeout); cancel any stragglers left after the upgrade with CancelWorkflow.

Custom serializers must handle non-user values​

Every checkpoint written to the system database is encoded with the configured custom serializer — including engine-internal step outputs: the int64 deadline recorded by DBOS.sleep (written by Sleep and, new in v1, by Recv/GetEvent timeouts), DBOS.writeStream's empty-string step output, and the ScheduledWorkflowInput struct for DB-backed schedule firings. This keeps every row in one format when inspecting the database from another program.

A Serializer[any] must therefore be total: it must encode and decode arbitrary Go values, and decoding must preserve concrete types (decoded step results are type-asserted, so an int64 must round-trip as int64, not float64). Strictly-typed serializers need a mapping for bare Go scalars — see the protobuf-serializer demo app, which packs proto.Message values into anypb.Any directly and wraps scalars in the protobuf well-known wrapper types (wrapperspb.Int64Value, StringValue, …), unwrapping them back to native scalars on decode. This keeps every stored row decodable by standard proto tooling in any language. See the serialization reference for the full serializer contract.

One deliberate exception: Recv and GetEvent checkpoint the sender's encoded payload verbatim under the sender's recorded format — the receiver's serializer is never asked to re-encode a message or event it didn't produce.

8. DBOS calls are rejected inside step bodies​

v0 let most DBOS operations run inside a step body and silently dropped their durability guarantees: CloseStream, workflow-management writes, and RunAsTransaction invoked from inside a step executed in a real database transaction but recorded no checkpoint, so recovery could repeat them. v1 rejects them instead with an ErrorCodeStepExecution error (cannot call <X> within a step). Newly rejected inside a step (v0 already rejected Send, Recv, GetEvent, Sleep, Patch, DeprecatePatch, and spawning a child workflow with RunWorkflow):

  • RunAsTransaction
  • Enqueue
  • Go
  • handle.GetResult() — awaiting another workflow's result from inside a step
  • CloseStream
  • Workflow-management writes: CancelWorkflow(s), ResumeWorkflow(s), ForkWorkflow(s), DeleteWorkflows, SetWorkflowAttributes, SetWorkflowDelay
  • Schedule writes: CreateSchedule, PauseSchedule, ResumeSchedule, DeleteSchedule
  • Debounce

Still allowed inside a step:

  • SetEvent and WriteStream — at-least-once, attributed to the enclosing step (a retried step may write again).
  • Read/list operations — ListWorkflows, GetWorkflowSteps, RetrieveWorkflow, ReadStream, GetWorkflowAggregates, GetStepAggregates, GetSchedule, ListSchedules, queue reads, app-version reads. Called from a step they run within the enclosing step's durability scope, without their own checkpoint; called from workflow code they are checkpointed as steps, as before.
  • Nested RunAsStep — the inner function runs inline as part of the enclosing step, without its own checkpoint (unchanged from v0).

To migrate, hoist rejected calls out of step bodies into the surrounding workflow code. If a step needs another workflow's result, return from the step and await the handle in the workflow.

9. Mocks / tests​

Mocks of the old DBOSContext must be regenerated from dbos.Context (which now embeds Client). With mockery, point the config at Context — see the Testing & Mocking tutorial for a sample .mockery.yml. Assertions on error types/codes need the §5 renames.

10. Suggested migration order per app​

  1. go get github.com/dbos-inc/dbos-transact-golang@latest; go build ./... to enumerate breakage.
  2. Mechanical renames (safe to sed, word-boundary matched): DBOSContext→Context, NewDBOSContext→NewContext, DBOSError→Error, DBOSErrorCode→ErrorCode, SqliteSystemDB→SQLiteSystemDB, UpdateWorkflowAttributes→SetWorkflowAttributes, CancelWorkflowOptions→CancelWorkflowOption, step-retry With*→WithStep*, list-filter With*→WithFilter* (per §6 table — names collide with unrelated options, so match exact identifiers), error-code constants per §5, Client<Fn>→<Fn> per §2, WorkflowFn:→Workflow: (schedule-spec literals, per §4).
  3. Structural fixes by hand: queue registration (NewWorkflowQueue→RegisterQueue + error handling + WithQueue(queue)), scheduled workflows (WithSchedule→CreateSchedule+ScheduleSpec, input signature), Enqueue type-param reorder, RetrieveQueue/GetSchedule/GetLatestApplicationVersion/NewDebouncer return-shape changes, Shutdown error handling, hoisting DBOS calls out of step bodies (§8).
  4. go vet ./... && go build ./... && run tests.