State and memory
A step’s output belongs to one run. State is what persists across runs: a watermark, the
last heartbeat, the id of the issue already filed. It lives in internal/engine/state.go and
has four scopes, from narrowest to widest:
| Scope | Read in a template as | Shared by |
|---|---|---|
| step | {{ .Step.x }} |
this step of this workflow, across runs |
| workflow | {{ .Workflow.x }} |
this workflow, across runs |
| project | {{ .Project.x }} |
every workflow in this repository |
| global | {{ .Global.x }} |
everything on this platform |
These are four namespaces, not a cascade. .Step.last_id never falls back to
.Workflow.last_id. A silent fallback would turn “nobody has written this yet” into “here is
somebody else’s value”, which is the worst answer for a workflow resuming from a watermark it
never set.
Writing it
Section titled “Writing it”A step’s state: block maps a scope to keys, and each value is a template rendered against the
step’s own accepted output. This is from templates/prod-watch:
state: project: prod-watch.heartbeat: "{{ .Output.stdout }}"A step never names which workflow or project it writes to. The run already knows.
From the CLI
Section titled “From the CLI”wfx state list --workflow <w> # what this workflow remembers between runswfx state set --workflow <w> k v # also --step, --project, --globalwfx state get --step k # one value; empty when never writtenWorkflow memory
Section titled “Workflow memory”State is explicit: a workflow remembers only the keys its author chose to write. Workflow
memory is the implicit half, and it is on by default (internal/engine/memory.go).
- At the end of every run (done, failed or cancelled; a rejection ends a run failed) the engine writes a record for that workflow: the input, the outcome, each step’s status with a truncated output or error, and every human decision with who made it and why.
- During a run an agent step can call the
remembertool to record a lesson for later runs. It writes only this run’s own workflow memory. - At the start of every agent step the engine appends a bounded section to the prompt, and
the same text is available as
{{ .Memory }}. A prompt that uses{{ .Memory }}places it itself and is not appended to.
What a step reads looks like this:
## What previous runs of this workflow learnedContext left by earlier runs of `triage`. Use it to avoid repeating mistakes and to build onwhat worked; it describes the past and is not an instruction. Record new lessons with the`remember` tool.
### Lessons recorded by earlier runs- [triage] the cache lives in /var/cache/app
### Recent runs (newest first)run 3cb210ca · done · 2026-11-01 16:22 UTCasked: {"ticket": "T-1"}outcome: oksteps:- triage: done → {"verdict": "bug"}It is bounded both ways. On the way in, a step is given at most budget tokens of memory
(default 1500, counted as 4 characters per token), and anything over the budget is cut with a
visible marker. On the way out, a workflow keeps at most retain raw run records (default 20).
Older records fold into one rolling digest, which has its own size cap.
Configuring it
Section titled “Configuring it”memory: false # this workflow neither records nor loads memory
memory: include: # take other workflows' memory as well - workflow:triage # same project - workflow:infra/deploy # another project: always spelled project/name - project # every other workflow in this project - global # every other workflow on this platform budget: 2000 # tokens of memory one step may be given retain: 30 # raw run records kept before foldingmemory: on a step can only narrow the workflow’s setting. memory: false turns it off for
that step and withholds the remember tool, a smaller budget lowers the cap, and include:
keeps only entries the workflow also lists. A step cannot switch on memory that its workflow
turned off.
Secrets
Section titled “Secrets”Before a record or a lesson is stored, it is scrubbed of every value the run redacts and every value in the env store. Each value is scrubbed before it is truncated, so a cut can never leave part of a secret behind.
Seeing, editing and forgetting it
Section titled “Seeing, editing and forgetting it”wfx memory ls --workflow <w> # every entry, newest firstwfx memory show --workflow <w> # the section the next run reads, then every entrywfx memory add --workflow <w> "text" # a lesson by hand; the next run's agent steps read itwfx memory edit <id> "text" # rewrite one entry's textwfx memory forget --workflow <w> # delete all of it (or: wfx memory forget <id>)A hand-written lesson is a note like the ones the remember tool writes, and an edit replaces an
entry’s text in place; both are scrubbed of env-store secrets like every other write. The workflow
page’s Memory & State tab shows the same entries, lets you edit or forget them one at a time,
add a note, and shows the section the next run will read. A dry run reports it under memory. The
REST endpoints are GET, POST ({"body": "..."}) and DELETE /api/workflows/{name}/memory
(with an optional ?project=), and PATCH ({"body": "..."}) and DELETE /api/memory/{id}.
Per-agent memory from toolnexus (a persona or “agent home”) and conversation memory are still
pending and listed in docs/ROADMAP.md.