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_instanceandrecord_evidencewriteaction: <name>and put their parameters next to it on the handler. - An object
{ id, <block> }, when the action carries a structured config block.mailbox_writenests its config underaction.mailbox, andartifact_repo_commitunderaction.artifact_repo.
swarm verify.
create_flow_instance
Spin up a new instance of a template flow, passing data in throughconfig_from:
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.A complete minimal flow
A complete minimal flow
An incident log: open an incident, append evidence to it, then close it.What happens:
package.yaml
schema.yaml
entities.yaml
events.yaml
nodes.yaml
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).
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 underaction.mailbox;
item_type and summary are required, severity defaults to normal:
A complete minimal flow
A complete minimal flow
A refund desk: route a large refund to a human, then resume on their decision.What happens:
package.yaml
schema.yaml
entities.yaml
events.yaml
nodes.yaml
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 aswarm-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 complete minimal flow
A complete minimal flow
A report publisher: commit a generated report to a git-backed repo.What happens:
package.yaml
schema.yaml
entities.yaml
events.yaml
nodes.yaml
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
Whereaction 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 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).

