Skip to main content
An action runs a platform-provided operation as a near-final step of the handler (after the writes and the emit). There are exactly four action types, and you cannot define your own — calling your tools is a different step, activity:, covered at the end of this page. Actions are platform operations; activities are your integrations. action comes in two forms, and which one an action uses depends on its parameters:
  • Name plus sibling fields, when the parameters are simple. create_flow_instance and record_evidence write action: <name> and put their parameters next to it on the handler.
  • An object { id, <block> }, when the action carries a structured config block. mailbox_write nests its config under action.mailbox, and artifact_repo_commit under action.artifact_repo.
Every example below passes swarm verify.

create_flow_instance

Spin up a new instance of a template flow, passing data in through config_from:
This is the spawn half of cross-flow composition, so its “whole flow” is two flows. The complete worked example, including how the child returns a result, is in Composing flows.

record_evidence

Append the triggering event’s payload to an entity accumulator, building an audit trail. It is append-only and never replaces; evidence_target names the accumulator:
Not the same as data_accumulation. data_accumulation sets named entity fields and overwrites them (last write wins): use it for the entity’s current state, such as category or a running total. record_evidence appends the whole event payload as a new entry to a list and never overwrites: use it for an append-only history of what happened. They are complementary, fields versus a log.
An incident log: open an incident, append evidence to it, then close it.
package.yaml
schema.yaml
entities.yaml
events.yaml
nodes.yaml
What happens: incident.opened mints the incident and records its id and title. Each evidence.submitted selects that incident by incident_id and appends its payload to the evidence.items accumulator (the accumulator is created on first use; you do not declare it as a field). incident.closed moves the incident to its terminal state. The accumulated evidence is the durable audit trail.

Binding values in action blocks

The next two actions, mailbox_write and artifact_repo_commit, take field values that can be either a constant or a reference, and you say which with an explicit key:
  • { ref: payload.x } reads a path (payload.*, entity.*, event.*).
  • { literal: "review_request" } is a constant.
  • { cel: "entity.a + entity.b" } is a computed expression (expression: is an accepted alias).
Give exactly one. The trap worth internalizing: a bare scalar in these blocks is a literal, not a reference. content: entity.body commits a file whose contents are the text entity.body; you almost always mean content: { ref: entity.body }. This is the opposite of emit.fields, where a bare scalar is evaluated as an expression. When in doubt inside an action block, be explicit. (Handler fields collects how every block treats a bare scalar in one table.)

mailbox_write

Put a decision in front of a human. The runtime materializes a durable mailbox row and the flow waits for the decision to come back as an event. The config nests under action.mailbox; item_type and summary are required, severity defaults to normal:
A refund desk: route a large refund to a human, then resume on their decision.
package.yaml
schema.yaml
entities.yaml
events.yaml
nodes.yaml
What happens: refund.requested creates the refund, writes a pending row to the mailbox, and parks the entity in awaiting_approval. The flow does nothing else until an operator decides (see Mailbox). The decision arrives as refund.approved or refund.rejected, which selects the refund by id and advances it to a terminal state. The mailbox row is idempotent per triggering event, so a replayed delivery does not duplicate it.

artifact_repo_commit

Commit service-owned files to a local git repository and expose them at a swarm-artifact:// URL. This is the most involved action: paths must be allowlisted and relative, each file needs a content_type, and an output block must bind the runtime’s result fields to entity fields. The config nests under action.artifact_repo:
A report publisher: commit a generated report to a git-backed repo.
package.yaml
schema.yaml
entities.yaml
events.yaml
nodes.yaml
What happens: report.ready creates the report entity and records its body, then commits reports/summary.md to the reports repo. The output block writes the commit’s result (the repo URL, the new ref, a file manifest, status) back onto the entity, so later handlers can read where the artifact landed. request_id makes the commit idempotent: the same id with the same content is a safe repeat, while the same id with different content is rejected rather than silently overwriting. For the full field list (provenance, limits, success/failure events) see Handler fields.

Durable activities

Where action runs the four platform operations, activity: durably calls one of your own declared tools (from tools.yaml) — with the persistence, retry, and approval semantics an external side effect deserves:
nodes.yaml
The durability contract:
  • The request is persisted in the handler’s transaction (platform.activity_requested); the actual tool call executes only after the transaction commits and the entity lock is released.
  • A crash between the two replays the persisted request — the call is never lost and never runs inside your state transaction.
  • The outcome comes back as generated typed result events (success and failure families derived from the tool declaration), which your handlers subscribe to like any event — no hidden runtime state.
  • Writes that cannot safely be retried — charging a card, sending an email — can require human approval. Add approval: {decision: <stable-decision-id>} and the platform records a pending decision card (proposed_effect) instead of the request — no attempt, no credential lookup, no provider call exists until a human approves. Approval releases the exact recorded request through the same durable path.
  • In mock mode, activities fail closed — a test run cannot leak a real external call (see Testing).