Documentation / Conception / Guides pratiques / Remember values between runs

A run forgets everything when it ends. To carry a value from one run to the next — the previous result of a lab test, an alert already sent, a counter — use the workflow memory: a durable store of typed values, attached to the subject of the run (a patient, a stay…), or to the workflow for all its subjects. Three nodes work with it, in the Mémoire category of the palette: Mémoriser une valeur (memory.write), Lire une valeur mémorisée (memory.read) and Oublier une valeur mémorisée (memory.forget).

The memory is kept in the platform database, next to the workflow definitions: restarts, deploys and new versions of the workflow do not lose it.

Choose the address of the value

Every memory node addresses one item with three settings:

Setting Meaning
Portée (scope) workflow (default): the item belongs to this workflow, for the run's subject — its other versions see it, other workflows do not. subject: the item is shared by every workflow of the same subject, under the same key. workflow-global: the item belongs to this workflow, for all subjects — every run of the workflow sees the same item, whatever its subject.
Sujet (subject) The context kind of the subject, e.g. patient. The id comes from the run: two patients never share an item. The kind must be established by the trigger. Not set with workflow-global, which needs no subject: a scheduled workflow, which has none, uses that scope.
Clé (key) The name of the item, e.g. creatinine-precedente. It can be exposed as a port to compute it per run or per loop item (see Wire ports).

Pick workflow unless several workflows must really share the value: a private item cannot be overwritten by another workflow by mistake. Use workflow-global for state that belongs to the workflow rather than to one subject — the date of the last batch processed, a counter across patients. Every run of the workflow writes to that one item, possibly at the same moment: update it with a conditional write (below).

Store a value

Add Mémoriser une valeur, set its address, and wire the value to store into its value input. Declare its type (type): a primitive (Decimal, String, Boolean, DateTime…), an object type (Observation, CodeableConcept…), or a list (Observation[]). In the editor, wiring value first fills the type from the wire; a declared type is never replaced.

memoriser:
  type: memory.write
  config:
    scope: workflow
    subject: patient
    key: creatinine-precedente
    type: Observation
    ttl: 48            # retention: both fields, or neither (30 days)
    ttlUnit: hours     # seconds, minutes, hours or days

The value is checked against the declared type before it is stored: a value that does not conform fails the node with a data error naming the item. The type is stored with the value, explicitly, so a later read knows what was written. Writing again under the same address replaces the value and gives it a new version, emitted on the node's version output. A value is limited to 256 KiB of JSON; the memory is for small working state, not for documents.

Read it back

Add Lire une valeur mémorisée with the same address and a type. Its outputs:

  • value and writtenAt — emitted when an item exists;
  • version — always emitted: the version of the item, 0 when there is none;
  • missing — emitted only when there is none (never written, forgotten or expired);
  • convertedFrom — emitted only when the value was converted (below).

Wire the "found" branch from value and the "nothing yet" branch from missing; absence propagation stops the other one, without a guard (see Branching and absence).

The item is checked again on read. When it was written with a type you can read directly — the same, or a wider one (an Integer reads as a Decimal) — it is returned as stored. When it was written with another type but a conversion is registered between the two (the conversions offered by Convertir (From/Into), contributed by plugins), the value is converted automatically, element by element for lists, and convertedFrom names the stored type. Otherwise — a Decimal read as an Integer, a conversion that fails, a value that does not conform — the node fails instead of looking like "nothing in memory". Forget the old item (below) to start again.

A read node has no data input: to read after a write in the same run, wire the writer's done into the reader's after (see Sequence with after and done).

Update safely: conditional write

Two runs of the same subject — or any two runs, for a workflow-global item — can run at the same moment. The workflows that start on the same event run in parallel, in no guaranteed order: when they share a subject-scoped item, their writes to it happen in no guaranteed order either. If two runs read a value, compute, and write it back, the second write silently overwrites the first. To prevent it, tick Conditional write (« Écriture conditionnelle ») on the write node (conditional: true). The node gains an expectedVersion input and a conflict output:

  • wire the reader's version into expectedVersion — the write happens only if the item still has that version;
  • otherwise nothing is written and conflict is emitted, with no error: wire it to what should happen then (retry later, notify, stop).
connections:
  - from: precedente.version        # version read at the start of the run
    to: memoriser.expectedVersion   # write only if nobody wrote since
  - from: memoriser.conflict        # someone did
    to: memoire_modifiee.after

The wire also orders the nodes: the write waits for the read, so it can never overwrite the value before it was read. The write's done fires in both cases; to act only after a successful write, wire from its version output instead. When the upstream branch is skipped, expectedVersion is absent and the node is skipped: a conditional write never falls back to an unconditional one.

Do something once: expected version 0

Version 0 means "nothing stored". A conditional write with a fixed expectedVersion of 0 succeeds only for the first run that gets there — until the item expires or is forgotten. That is how to send an alert once per patient and per day, even when two events arrive together:

reserver:
  type: memory.write
  config: { scope: workflow, subject: patient, key: alerte-envoyee, type: Boolean,
            conditional: true, ttl: 24, ttlUnit: hours }
  inputs: { expectedVersion: 0, value: true }
connections:
  - from: reserver.version   # emitted only to the run that reserved the key
    to: alerte.after

content/clinical-demo/workflows/drafts/zz-memoire-creatinine.workflow.yaml is a complete example: each creatinine result is compared with the previous one stored for the patient during 48 hours, a rise of at least 26.5 µmol/L is recorded as an alert, and the new value is written only if no other run changed the memory in between. It is a draft; launch it from the event composer of the Executions screen.

Let a value expire

Every item expires: 30 days after its write by default, or after the retention set on the write (ttl + ttlUnit, Conservation in the editor, up to 100 years). Past that delay the item reads as missing (version 0), and the platform deletes it within ten minutes. Keep retentions short whenever the value only matters for a while: the memory holds health data.

Forget a value

  • In a workflow, add Oublier une valeur mémorisée with the item's address.
  • From the console, open a run in Executions: the record pane lists the memory of the run's subjects and the workflow's workflow-global items (key, version, scope, type, value, who wrote it, when, and its expiry) with an Forget button per item.

Test with simulation

A simulation never writes to the memory: writes and forgets appear in its captured effects (port: memory), and stay readable for the rest of the simulated run. The simulation reads the workflow's real workflow-global items as a starting point and, when you fill the simulation's Subject field, that subject's real memory too, versions included, whether or not its kind has a record port. Through the API, options.memory seeds the simulated memory explicitly — each item may carry its version — and wins over the real one (see Simulate a workflow).

Limits

  • An unconditional write does not look at what is stored: between concurrent runs, the last write wins. Use a conditional write when that matters.
  • An agent cannot use memory nodes as tools: a tool call carries no node configuration, hence no address.
97 documents10 sectionssource : /docs · généré au build