State: let / vars / persist
Three state blocks, three purposes.
let: base-url: "${config.endpoint}" # immutable, evaluated once at start greeting: "Hello, ${user.name}!"
vars: counter: !int 0 # mutable, transient (lost after run) buffer: !str-list
persist: cursor: !str # mutable, survives across invocations last-sync: !str # auto-loaded at start, auto-saved on successWhich block to use
Section titled “Which block to use”| Block | Mutable? | Lifetime | Use for |
|---|---|---|---|
let: | No | One invocation, evaluated once at start | Anything computed once at flow start. |
vars: | Yes | One invocation, transient | Loop accumulators, transient bookkeeping. |
persist: | Yes | Across invocations | Pagination cursors, last-seen timestamps, watermark IDs. |
persist: values are auto-loaded at the start of each invocation and auto-saved when the flow completes successfully. They live under activity/persistence/ in the storage tree.
Assigning state
Section titled “Assigning state”Assign with $name: syntax in steps:, or batch with set::
steps: - $counter: "${= counter + 1}" # single assignment - set: # batch — committed atomically cursor: "${products.last-id}" last-sync: "${= str(_invocation-id)}" seen-count: "${= seen-count + len(products.rows)}"Prefer set: over multiple $var: lines when updating several variables at once — it commits them atomically.
A worked cursor example
Section titled “A worked cursor example”A flow that pages through a source and remembers where it left off between runs:
persist: cursor: !str ""
steps: - $page: invoke: http.get with: Url: "https://api.example.com/items?after=${cursor}" - set: cursor: "${page.body.next-cursor}"Next invocation picks up from the stored cursor automatically. Pair persist: with the delta: transformer for change-detection pipelines.