Feeds
Fold settled results from every Flow into one shared value that later Flows can read.
Shared Values Across Flows
An Attribute lives within one Flow. A Feed carries a value across all of them: a running order total, the latest exchange rate a pricing Step reported, or a count of approved claims. Each time a Step settles an output the Feed watches, Argyll folds that value into the Feed. Steps in later Flows read the result as an input.
A Feed carries an ID, a display name, an optional description, a seed value, the output Attributes it folds, and a fold script. It may also name a Space:
{
"id": "order-total",
"name": "Settled Order Total",
"description": "Sum of every settled order amount",
"value": "0",
"attributes": ["order_amount"],
"fold": {
"language": "argyll",
"script": "feed + order_amount"
}
}
value is the seed, written as a JSON literal. The Feed holds the seed until its first fold.
Contributing Steps
A Feed names the output Attributes it collects. Steps that produce those Attributes become contributors automatically, within the Feed’s selected Space when one is configured. Each Attribute must already be the output of a registered Step when the Feed is registered or updated.
List several Attributes to fold related outputs into one value. This Feed nets charges against refunds:
{
"id": "net-revenue",
"name": "Net Revenue",
"value": "0",
"attributes": ["order_amount", "refund_amount"],
"space_id": "billing",
"fold": {
"language": "argyll",
"script": "feed + (order_amount ?? 0) - (refund_amount ?? 0)"
}
}
space_id limits contributors to the Steps a Space selects.
Contributors are resolved when a Flow is planned. POST /engine/plan lists them under feeds, with each Feed’s contributing Steps as sources. A Flow keeps folding under the Space membership it was planned with.
The Fold Script
The fold runs once for a contributing Step that supplies at least one watched Attribute. It receives the current value as feed, and each of the Feed’s Attributes the Step produced, bound by name. Absent Attributes are missing values in Argyll Expressions and nil in Lua. The net-revenue fold uses ?? to default each missing Attribute to zero before subtracting. Explicit null values remain null. The script’s result is the new value.
A fold whose result equals the current value records nothing. This Feed stores the latest exchange rate:
{
"id": "usd-eur",
"name": "USD to EUR",
"value": "null",
"attributes": ["usd_eur_rate"],
"fold": {
"language": "argyll",
"script": "usd_eur_rate"
}
}
Folds can use any registered script language. A fold that fails, or returns a value that can’t be stored as JSON, leaves the Feed as it was and records a feed_fold_failed event with the outputs and the error. The contributing Flow carries on unaffected.
When Values Fold
A Feed folds settled results only. Argyll never retracts a contribution, so it waits until an output can no longer be rolled back:
- A Step without a compensator folds when it completes
- A compensated Step folds when its Flow deactivates, using only the Work Items that were not compensated
Each contribution lands once, in the same transaction as the event that settles it. Folds use every output the Step produced, including outputs no other Step in the Flow consumes.
Contributions apply in the order they settle. Concurrent Flows settle in whatever order their transactions commit, and a compensated Step settles at deactivation rather than at completion. Write folds whose result is independent of that order, such as a sum, a count, or a maximum. In a fold that keeps the most recent reading, as usd-eur does, the most recent reading is the last one to settle.
Read a Feed
A Step reads a Feed through an Attribute with the feed role:
{
"id": "approve-order",
"name": "Approve Order",
"type": "script",
"attributes": {
"running_total": {
"role": "feed",
"feed": { "id": "order-total" }
},
"order_amount": { "role": "required" },
"approved": { "role": "output" }
},
"script": {
"language": "argyll",
"script": "{\n approved: running_total + order_amount < 100000\n}"
}
}
Argyll captures the value of every Feed the Flow’s Steps read when the Flow starts. Each of those Steps receives that captured value, so a Flow sees one consistent reading of each Feed from start to finish, even while other Flows fold into it. The Flow state returns the captured values under feeds. Child Flows capture their own when they start.
A Feed input is always available, so it never holds a Step back. Predicates can read it like any other input.
Versions
Changing the seed, the fold, the Attributes, or the Space starts a new version whose value begins at the seed. Flows planned under the earlier definition keep folding into the earlier version. Renaming a Feed, changing its description, or reordering its Attributes keeps the current version and its value.
GET /engine/feeds/{feed_id} returns the definition alongside the value of its current version:
{
"feed": {
"id": "order-total",
"name": "Settled Order Total",
"value": "0",
"attributes": ["order_amount"],
"fold": {
"language": "argyll",
"script": "feed + order_amount"
}
},
"state": {
"id": "order-total",
"version": "9b2e41c07d5a6f38e1c4b0a7d92f6e15c3a8b47f0e6d1c29a5b83f7e4d0c6a12",
"value": 1840.5,
"last_updated": "2026-11-03T14:22:07Z"
}
}
To follow a Feed live, subscribe to its version over the WebSocket API.
Manage Feeds
| Request | Result |
|---|---|
GET /engine/feeds | Every registered Feed |
POST /engine/feeds | Register a Feed |
GET /engine/feeds/{feed_id} | One Feed definition and its current value |
PUT /engine/feeds/{feed_id} | Replace a Feed definition |
DELETE /engine/feeds/{feed_id} | Remove a Feed |
Registering an identical definition again succeeds and changes nothing. Registering a different definition under an existing ID returns 409 Conflict. Use PUT to replace it.
Referential Integrity
- Registering or updating a Step whose
feedAttribute names an unregistered Feed returns400 Bad Request - Deleting a Feed that a registered Step reads returns
409 Conflictand names the Step - Registering a Feed that names an unregistered Space, or an Attribute no registered Step outputs, returns
400 Bad Request - Deleting a Space that a Feed names returns
409 Conflictand names the Feed
Removing the last Step that outputs one of a Feed’s Attributes is allowed. The Feed keeps its value and folds again once a provider returns.