Repository navigation
Rollback
A step that changes the world (reserves stock, charges a card, sends a message) cannot be undone by
the framework: it does not know what a tool call, a sent message or an MCP side effect did, and an
undo can itself fail. So there is no automatic compensation — no sagas, no compensate
metadata. A rollback is ordinary, explicit error handling in the path: .onError(ErrorClass, <side path>) right after the step that throws, whose side path runs the undo actions and ends in a
finish (#272, ADR-0019).
@Workflow({
name: "order-workflow",
version: "1.0.0",
flow: [
start(OrderWorkflowStart)
.returns(OrderRequestDto)
.toAction(ReserveStockAction)
.returns(StockReservationDto) // data: ReleaseStockAction reads its reservationId
.toAction(ShipOrderAction)
.onError(
CarrierError, // the carrier failed: undo what was done, newest first
toAction(RefundCardAction) // side effects without output: no .returns
.onError(
PaymentProviderError, // the undo failed: a person takes over
toAction(NotifyFinanceTeamAction)
.toFinish(OrderFailedWorkflowFinish),
)
.toAction(ReleaseStockAction)
.toFinish(OrderFailedWorkflowFinish),
)
.returns(OrderShippedDto)
.toFinish(OrderWorkflowFinish),
],
})
export class OrderWorkflow {}- It stands right after the step that throws (an agent, an action, a router), before that
step's
.returns(…), or right after the source of an own path (agent(X).onError(E, …).returns(oneOf(…)).switch(…)). It catches that step's own failure only, never the next step's; the main path continues after it. - A step may have several; the most specific class wins, matched by class through the error's
cause chain — a subclass is caught by its parent's handler, and so is an error caused by it
(
ActionFailedErrorwraps what an action throws, the original incause). Two handlers for one class on one step fail assembly ([flow.duplicate-error-handler]). Without a handler the execution fails. - The side path is a path like any other:
toAction(…)/toAgent(…)continued to a finish, ortoFinish(F)alone; it may have its own.onError(…)for a failed undo. - The failure is a plain record in state (
lastError: its codes, message, the failed node), so a handler holds after a resume. The visited path shows the failed step before the side path. - Limits are errors too:
.onError(VisitsLimitExceededError, …)after a node withmaxVisits,.onError(LimitExceededError, …)for any limit.
A compensation the framework runs on its own would have to know the inverse of every step; for
model calls, tool calls and messages there is none, and a silent partial undo is worse than a
visible one. Writing the undo in the path keeps it reviewed, typed (the undo reads what the step
before returned, StockReservationDto here) and tested like the rest of the flow.
AGBoss
- Home
- Delivery Pipeline
- Architecture
- Configuration
- Environments
- Workflow — the paths
- Path scope
- Workflow as a step
- Parallel — quorums and arbiters
- Routing patterns
- Rollback
- Routers
- Guards
- CLI — agboss
- App — @Application and createApp
- Several instances
- Health checks
- Context and Authorization
- Testing
- The test split
- Suites and Comparisons
- Components
- Standard DTOs
- Examples: job-scout, flow-patterns
- Agents — the loop
- Model providers
- Tools
- Knowledge bases (RAG)
- FinOps
- Tracing
- Observability
- Terns and evaluation
- Profiles and comparisons
- Pauses — pending, resume by id
- Pause seam
- Decisions (ADR)