Documentation / Exploitation / Référence / Workflow memory storage

Workflow memory storage

The workflow memory holds the values that memory nodes (memory.write, memory.read, memory.forget) keep from one run to the next, attached to a subject of the run. Authoring is covered in Remember values between runs; this page describes the storage an operator runs.

Source: apps/api/src/server/memory-store.ts (store), apps/api/src/restate/memory-client.ts (engine side), libs/shared/src/domain/memory.ts (contract).

Database

The memory lives in the application database, the PostgreSQL database of the workflow definitions and EAI exchanges, selected like them by DEFINITION_STORE_URL, or RUN_DB_URL when it is a PostgreSQL URL (see Environment variables). There is no separate setting and no other driver. The table is created at proxy boot, under the same advisory lock as the other schemas.

CREATE SEQUENCE IF NOT EXISTS workflow_memory_version_seq;
CREATE TABLE IF NOT EXISTS workflow_memory (
  subject_kind TEXT NOT NULL,      -- context kind, e.g. patient; '' for scope workflow-global
  subject_id TEXT NOT NULL,        -- '' for scope workflow-global
  scope TEXT NOT NULL,             -- workflow, subject or workflow-global
  workflow TEXT NOT NULL DEFAULT '', -- workflow slug; '' for scope subject
  key TEXT NOT NULL,
  type JSONB NOT NULL,             -- TypeRef declared by the writing node
  type_name TEXT NOT NULL,         -- the same type, readable: Observation, Decimal[]…
  value TEXT NOT NULL,             -- the value, serialized as JSON
  version BIGINT NOT NULL DEFAULT nextval('workflow_memory_version_seq'),
  written_at TIMESTAMPTZ NOT NULL DEFAULT now(),
  expires_at TIMESTAMPTZ,          -- set by every write
  written_by JSONB NOT NULL,       -- { workflow, run, node }
  CONSTRAINT workflow_memory_scope_workflow CHECK ((scope = 'subject') = (workflow = '')),
  PRIMARY KEY (subject_kind, subject_id, scope, workflow, key)
);
-- Added at boot when missing:
--   workflow_memory_scope_values  CHECK (scope IN ('workflow', 'subject', 'workflow-global'))
--   workflow_memory_scope_subject CHECK ((scope = 'workflow-global') = (subject_kind = '' AND subject_id = ''))

A table created before the workflow-global scope carries an unnamed check on the scope list (workflow_memory_scope_check): the first boot of a newer proxy replaces it with workflow_memory_scope_values, once, and adds workflow_memory_scope_subject. Existing rows are kept.

One row per address: a write replaces the row. The history of writes is the history of runs — each write node's observation, in the run index archive. A value is at most 256 KiB of JSON.

Versions

Every write takes a new version from workflow_memory_version_seq, shared by the whole table. Versions only increase, with gaps; a version is never reused, even after the item is forgotten or expires. 0 stands for "no item". A conditional write (expectedVersion in the request) is one atomic statement:

Expected version Writes when
none always (upsert)
0 no unexpired item exists at the address
n the unexpired item has version n

When the write is refused, the proxy reads the current item. If it was written by the same run and the same node path, the request is the retry of a write that already happened — for instance after a crash between the commit and the journal — and it is answered as a success. Otherwise the answer is a conflict with the current version.

Who reads and writes

Only the proxy opens the database. The engine endpoint, which executes the runs, calls the proxy on API_INTERNAL_URL — the address it already uses for agents and scheduler ticks:

Route Caller Purpose
POST /api/internal/memory/read endpoint Read one item ({ address } → { entry }).
POST /api/internal/memory/write endpoint Write one item ({ address, entry } → { written: true, entry }, or { written: false, currentVersion } on conflict); 413 above 256 KiB.
POST /api/internal/memory/forget endpoint Delete one item ({ address } → { forgotten }).
GET /api/memory/:kind/:id console List the unexpired items of a subject (scopes workflow and subject).
DELETE /api/memory/:kind/:id?scope=&workflow=&key= console Delete one item; 404 if it does not exist.
GET /api/workflows/:slug/memory console List the unexpired workflow-global items of a workflow.
DELETE /api/workflows/:slug/memory?key= console Delete one workflow-global item; 404 if it does not exist.

Each engine call runs inside the durable step of its node, so a replayed run reads its journal and never calls the proxy again. The endpoint retries a call up to five times over about five seconds on a network error or a 5xx; past that, or on a 4xx, the node fails. A proxy outage longer than a few seconds therefore fails the runs that reach a memory node, as an unreachable FHIR server would.

Like the rest of the console API, these routes have no built-in authentication (see Security posture): keep the proxy behind the stack's authenticating reverse proxy, and do not publish /api/internal/* beyond the cluster.

Expiry

Every write sets expires_at: 30 days after the write by default, or the retention of the write node, up to 100 years. The deadline is computed with the database clock, whichever replica writes. From that instant the item reads as absent and is no longer listed. The proxy deletes expired rows every ten minutes; with several proxy replicas each runs the same idempotent DELETE.

Backup

The table holds health data that no other system can rebuild, and that the running workflows rely on: include it in the backups of the application database (see Back up and restore). Losing the Restate volume does not lose the memory.

97 documents5 sectionssource : /docs · généré au build