Automate itLesson 3 of 5

Remember between runs: state

Every run starts from nothing — unless it keeps something. State is a small key/value store that belongs to one script or flow and survives between runs: the place for a cursor (the last id you processed), a counter, or the list of messages already handled. Values are JSON, writes are kept even if the run fails afterwards, and you can read and edit every key in the State tab.

What you will learn
  • Read and write state from Python, TypeScript, JavaScript, C and C++
  • Count safely with incr and change a value safely with update
  • Inspect, edit and reset state in the State tab
  • Know whose state a flow step uses, and the limits

What state is

A script’s state is its own: another script never sees it. Inside a flow, every step reads and writes the flow’s state, so the steps share one store — a step can leave a value for a later step, or for the next run of the flow. Runs started from the editor use the same state as scheduled, API and event runs: a test that moves a cursor moves it for real.

docs-demoCodecounter.pyPublish
counter.py
1from waiso import state2 3def main():4    n = state.incr("runs")5    last = state.get("last_note", "none yet")6    state.set("last_note", f"run {n}")7    return {"run": n, "previous": last}
State2
Test runs from the editor share this state: a test that moves a cursor moves it for real.Values are shown in clear here. Never keep passwords or tokens in state — use Variables.
2 keys21 B of 1.00 MBAdd keyReset
last_note5 h ago · run #8E6A00"run 9"
runs5 h ago · run #8E6A009
Recent changes (18)Set last_note5 h ago · run #8E6A00Added runs5 h ago · run #8E6A00Set last_note6 h ago · run #41C2D7
The State tab: every key with its value and the run that last wrote it, the space used, and Recent changes.

Your first stateful script

  1. Save the script

    Create counter with the code below. State belongs to a saved item: code that was never saved has none, and a call fails with state_unavailable.

  2. Run it twice

    Press Run. The first output is {"run": 1, "previous": "none yet"}; run again and it reads {"run": 2, "previous": "run 1"}.

  3. Open the State tab

    Pick State on the side rail: runs holds 2 and last_note holds "run 2", each with the run that wrote it. Recent changes lists the writes as Added and Set.

counter.py
from waiso import state

def main():
    n = state.incr("runs")          # atomic: two runs at once never lose a count
    last = state.get("last_note", "none yet")
    state.set("last_note", f"run {n}")
    return {"run": n, "previous": last}
Expected outputSuccessThe second run’s output
{
  "run": 2,
  "previous": "run 1"
}

The calls

In Python use waiso.state (or from waiso import state). In TypeScript and JavaScript import state from waiso; every call returns a promise. C has waiso_state_*, returning WAISO_OK or a negative code, with values as JSON text; C++ has waiso::state, which throws WaisoError.

CallWhat it doesC / C++
get(key, default)The value, or the default when the key was never set.waiso_state_get · state::get
set(key, value)Writes the value; kept even if the run fails afterwards.waiso_state_set · state::set
incr(key, by=1)Adds atomically and returns the new number; runs at once never lose a count.waiso_state_incr · state::incr
cas(key, expected, value)Writes only if the key still holds expected (null = absent), and says whether it did.waiso_state_cas · state::cas
update(key, fn, default)Reads, applies fn, writes back only if nothing changed meanwhile; 5 tries, then state_conflict. Returns the value written.— (use cas in a loop)
delete(key)Removes the key and says whether it existed.waiso_state_delete · state::del
all()Every key and value as one object.waiso_state_all · state::all

Changing a value two runs may touch

A get followed by a set loses a write when two runs overlap: both read the same list, both write their own version. incr solves it for numbers; for anything else, update wraps your change in a compare-and-set and retries.

only-new.py
from waiso import state

def main(numbers: list = [1, 2, 3, 4, 5]):
    seen = state.get("seen", [])
    fresh = [n for n in numbers if n not in seen]
    # update() re-reads and retries if another run changed "seen" meanwhile
    state.update("seen", lambda s: s + [n for n in fresh if n not in s], default=[])
    return {"new": fresh}

The State tab

In the script editor, State is a side tab; on a flow page it is one of the tabs on the right; the script’s operator page shows it too. It lists every key with its value and who last wrote it (run … or by a person), and how much of the space is used. Add key and Edit value take any JSON (text needs quotes), Delete key removes one, and Reset deletes every key after a confirmation — the next run starts from its defaults. Recent changes shows the last 50 writes as Set, Added, Deleted or Reset.

Limits and error codes

  • Each script or flow keeps at most 1 MB and 1,000 keys; a key is 1–200 characters without line breaks.
  • The change log keeps the last 50 writes; values are not copied into it.
  • Deleting the script or flow deletes its state.
CodeWhen
state_too_largeA write would pass 1 MB or 1,000 keys.
state_conflictupdate found the value changed on all 5 tries.
state_invalid_valueThe value is not JSON, or incr met a non-number.
state_key_invalidEmpty key, over 200 characters, or with control characters.
state_unavailableThe run belongs to no saved script or flow.
3 / 5