Skip to main content
Memory GrainMemory Grain
GitHub
All articles
memory-typesworkflowsprocedural-memory

Memory Type Deep Dive: Workflows

Workflows are procedural memory in OMS — directed graphs of procedural steps that agents can replay. Learn the nodes-and-edges model introduced in OMS v1.4, how topology encodes forks and joins, node-to-Tool binding, versioning through supersession, and industry use cases.

13 min read

You know how to ride a bicycle. You know how to make coffee. You know how to deploy a new release to production. These are all examples of procedural memory — knowing how to do something, as opposed to knowing what is true.

In the Open Memory Specification, procedural memory is captured by the Workflow memory type. Defined in Section 8.4, a Workflow is a directed graph of procedural steps — plans, pipelines, and multi-path processes. Real procedures branch, fan out, wait on multiple predecessors, and retry. OMS v1.4 redesigned the type to say so directly: the flat steps array of earlier revisions was replaced by a graph of nodes and edges.

This post covers the Workflow memory type in depth: the graph model, how structure is inferred from topology rather than declared, node-to-Tool binding, how agents learn workflows from patterns, versioning through supersession, serialization details, and practical use cases across industries.

What is a Workflow?

The spec defines a Workflow as:

Directed graph of procedural steps — plans, pipelines, and multi-path processes.

A Workflow has one required structural component — a set of nodes — and three optional ones: the edges connecting them, the bindings that attach nodes to concrete tools, and the retries that bound repetition. An optional trigger describes when the procedure should activate.

The graph model matters because the alternative degrades badly. A flat ordered list can only express "do A, then B, then C." It cannot express "run the security scan and the compliance check in parallel, wait for both, then branch on the approval decision, retrying the staging deploy up to three times." Encoding that in a list means smuggling control flow into step text, where no implementation can read it reliably.

Workflows can be authored by humans, but they can also be extracted from observed patterns in an agent's behavior. When an agent notices that it keeps performing the same sequence of tool calls in response to the same kind of situation, that pattern can be formalized as a Workflow grain and reused.

Required Fields

The Workflow type has three required fields (Section 8.4):

FieldTypeDescription
typestringMust be "workflow"
nodesarray[string]Non-empty array of graph node identifiers, unique within the grain
created_atint64Creation timestamp in epoch milliseconds

Each element of nodes serves as both the node's ID and its human-readable label — there is no separate label field to keep in sync. Node IDs must be unique within the grain.

Note what is not required: trigger. A Workflow is a plan, and a plan is meaningful whether or not anything fires it automatically. In earlier OMS revisions trigger was mandatory; since v1.4 it is optional.

Here is a minimal Workflow — a linear pipeline:

{
  "type": "workflow",
  "trigger": "merge to main",
  "nodes": ["build", "test", "deploy"],
  "edges": [
    {"src": "build", "dst": "test"},
    {"src": "test", "dst": "deploy"}
  ],
  "created_at": 1768471200000
}

The header byte for Workflow is 0x04 (Section 3.1.1, Type enum), so this grain's 9-byte fixed header would begin with 01 00 04 — version 1, no flags set, Workflow type.

Optional Fields

FieldTypeDescription
triggerstringCondition that activates this workflow
edgesarray[map]Directed edges between nodes. When absent, nodes are unconnected
bindingsmap[string→string]Maps node IDs to Tool definition grain hashes
retriesmap[string→int]Maps node IDs to a maximum repeat count on failure

Workflows also inherit the common optional fields available to all grain types through the core field map (Section 6.1) — author_did, user_id, namespace, importance, structural_tags, derived_from, provenance_chain, related_to, and the rest.

The Edge Schema

Each element of edges is a map with two required fields and two optional ones:

FieldTypeRequiredDescription
srcstringyesID of the source node (must exist in nodes)
dststringyesID of the target node (must exist in nodes)
condstringnoOpaque condition string; absent means unconditional
max_cyclesintnoMaximum traversal count for back-edges; absent means unlimited

Like trigger, cond is an opaque string, not an expression the format evaluates. OMS is a data format, not an execution engine.

Structure Is Inferred, Never Declared

This is the most important design decision in the redesigned type: node roles come from graph topology, not from a declared node-type field.

TopologyInferred role
Multiple outgoing unconditional edgesParallel fan-out (fork)
Target of multiple edgesAND-join — all predecessors must complete
Multiple outgoing conditional edges (cond present)Decision point
No outgoing edgesTerminal node
First element of nodesEntry point

Back-edges — edges that form cycles — are permitted when bounded by max_cycles or by an entry in retries.

Here is a fork/join in practice — two review gates that run in parallel, both of which must finish before evaluation:

{
  "type": "workflow",
  "trigger": "PR opened",
  "nodes": ["lint", "security", "compliance", "evaluate"],
  "edges": [
    {"src": "lint", "dst": "security"},
    {"src": "lint", "dst": "compliance"},
    {"src": "security", "dst": "evaluate"},
    {"src": "compliance", "dst": "evaluate"}
  ],
  "created_at": 1768471200000
}

lint has two outgoing unconditional edges, so it forks. evaluate is the target of two edges, so it is an AND-join. No field says either of those things.

Validation Constraints

An implementation validating a Workflow grain checks (Section 8.4):

  • Every src and dst in edges MUST reference an element of nodes.
  • Every key in bindings MUST reference an element of nodes.
  • Every key in retries MUST reference an element of nodes.
  • Node IDs MUST be unique within nodes.
  • Unbounded cycles — a back-edge with no max_cycles and no retries entry for the target node — SHOULD be rejected.

That last constraint is the one worth dwelling on. A cycle without a bound is a procedure that may never terminate. The spec does not forbid cycles, because retry loops and iterative refinement are legitimate procedures; it requires that they be bounded.

Node-to-Tool Binding

A node is a step. What actually runs it? OMS defines a three-tier resolution:

TierConditionResolution
BoundNode ID present in bindingsFetch the Tool definition grain by hash; use its tool_name, input_schema, output_schema
NamedNo binding, but a Tool definition grain exists with a matching tool_nameResolve by convention (implementation-defined lookup)
AbstractNo binding, no matching toolThe node label is a human-readable instruction; the executor — an LLM or agent — interprets it

This tiering is what lets one type serve both a fully wired CI pipeline and a loosely sketched playbook. A node named "Notify the customer's account manager" needs no binding to be useful; a node named prod_deploy probably wants one.

Here is a full pipeline using conditional branching, a bounded retry, and bindings:

{
  "type": "workflow",
  "trigger": "release requested",
  "nodes": ["build", "unit_test", "lint", "integration_test", "stage_deploy", "approval", "prod_deploy", "rollback", "notify"],
  "edges": [
    {"src": "build", "dst": "unit_test"},
    {"src": "build", "dst": "lint"},
    {"src": "unit_test", "dst": "integration_test"},
    {"src": "lint", "dst": "integration_test"},
    {"src": "integration_test", "dst": "stage_deploy"},
    {"src": "stage_deploy", "dst": "approval"},
    {"src": "approval", "dst": "prod_deploy", "cond": "approved"},
    {"src": "approval", "dst": "rollback", "cond": "rejected"},
    {"src": "prod_deploy", "dst": "notify"},
    {"src": "rollback", "dst": "notify"}
  ],
  "bindings": {
    "build": "sha256:def111...",
    "stage_deploy": "sha256:def333...",
    "prod_deploy": "sha256:def444..."
  },
  "retries": {
    "stage_deploy": 3
  },
  "created_at": 1711324800000
}

approval has two outgoing edges, both carrying cond — a decision point. notify is reachable from either branch. stage_deploy may repeat up to three times on failure.

Execution Records Never Touch the Plan

When an agent executes a workflow node, it does not modify the Workflow grain — grains are immutable, and the plan must stay stable while runs come and go.

Instead, the agent creates a Tool grain (Section 8.5) with a relation of type mg:step_action:<node_id> targeting the Workflow grain's hash. One plan, many execution traces, all linked back by relation, none of them mutating the original.

This also means the plan and its history have separate lifecycles. You can re-run a two-year-old Workflow and the new Tool grains attach cleanly, because the Workflow's content address never moved.

Writing Workflows in CAL

Hand-writing edge arrays is tedious, so CAL — the Context Assembly Language — gives Workflows a dedicated graph syntax for ADD and SUPERSEDE rather than the usual SET clauses:

-- Simple linear workflow
ADD workflow "nightly backup"
  ON "cron 0 2 * * *"
  snapshot -> compress -> upload
  REASON "automate database backups"
 
-- Parallel fork/join
ADD workflow "code review"
  ON "PR opened"
  lint -> (security_review, compliance_review) -> evaluate
  REASON "parallel review gates"
 
-- Conditional branching with a bounded retry and a binding
ADD workflow "release gate"
  ON "release requested"
  build -> deploy * 3
  deploy -> promote WHEN "healthy"
  deploy -> rollback WHEN "degraded"
  BIND deploy = sha256:def333...
  REASON "approval-based routing"

The operators map directly onto the grain fields: -> produces an edge, (a, b) produces a fork/join pair, WHEN "cond" sets cond, * N sets retries, BIND populates bindings, and ON sets trigger. Precedence runs * N (highest), then WHEN, then -> (lowest). Clause order is fixed: name → ON → graph lines → BINDREASON.

Node names are bare identifiers or quoted strings; reserved words must be quoted. A SUPERSEDE with graph syntax replaces the graph in full rather than patching it — which is the right default for an immutable type, since a partial edit would be ambiguous about what the resulting topology is.

How Agents Learn Workflows

From Tools to Workflows

The Tool memory type (Section 8.5) records every tool invocation an agent makes: the tool name, input, content, error status, and duration. Over time, these records reveal patterns:

  1. Observation: The agent (or a meta-agent) notices that in response to situation X, it consistently executes tools A, B, C — sometimes B and C concurrently.

  2. Extraction: The repeated pattern is extracted as a Workflow grain whose nodes are the recurring steps and whose edges encode the observed ordering and concurrency.

  3. Provenance: The new Workflow carries derived_from pointing to the Tool grain content addresses that informed the pattern, and provenance_chain entries recording the extraction method.

Because the target is a graph rather than a list, extraction can preserve something a list would flatten away: steps that were observed running concurrently stay concurrent, and steps that were observed retrying carry their bound.

{
  "type": "workflow",
  "trigger": "service returns 503 errors",
  "nodes": [
    "check_health_endpoint",
    "review_deploy_history",
    "inspect_pod_resources",
    "check_db_pool",
    "diagnose",
    "scale_horizontally",
    "verify_recovery"
  ],
  "edges": [
    {"src": "check_health_endpoint", "dst": "review_deploy_history"},
    {"src": "check_health_endpoint", "dst": "inspect_pod_resources"},
    {"src": "check_health_endpoint", "dst": "check_db_pool"},
    {"src": "review_deploy_history", "dst": "diagnose"},
    {"src": "inspect_pod_resources", "dst": "diagnose"},
    {"src": "check_db_pool", "dst": "diagnose"},
    {"src": "diagnose", "dst": "scale_horizontally", "cond": "resource_exhaustion"},
    {"src": "scale_horizontally", "dst": "verify_recovery"}
  ],
  "retries": {"verify_recovery": 3},
  "created_at": 1768471200000,
  "namespace": "incident-response",
  "importance": 0.8,
  "derived_from": [
    "a1b2c3d4e5f6...",
    "b2c3d4e5f6a1...",
    "c3d4e5f6a1b2..."
  ],
  "provenance_chain": [
    {
      "source_hash": "a1b2c3d4e5f6...",
      "method": "pattern_extraction",
      "weight": 0.9
    }
  ],
  "structural_tags": ["devops", "incident-response", "auto-learned"]
}

The three diagnostic checks fan out from the health check and converge on diagnose — an accurate record of how a competent operator actually works, and one a flat list could not have held.

From Events to Workflows

Event grains (Section 8.2) capture raw interaction records. When multiple Events describe similar sequences of actions, consolidation can extract the common procedure as a Workflow: users describe their processes in conversation, a consolidation process identifies the repeated pattern, and the pattern becomes a graph.

Procedural vs. Declarative Memory

Understanding the distinction between Workflows and Facts is crucial for correct use of OMS memory types:

DimensionWorkflowFact
Memory typeProcedural (knowing HOW)Declarative (knowing WHAT)
Core structureDirected graph of nodes and edgesSubject-relation-object triple
PurposeEncode a procedure to followEncode a knowledge claim
Type byte0x040x01
Required fields3 (type, nodes, created_at)6 (type, subject, relation, object, confidence, created_at)
Has confidenceNoYes (required, [0.0, 1.0])
Standard relationmg:has_graphmg:knows

The absence of a confidence field on Workflows is notable. Facts require a confidence score because they are claims that may or may not be true. Workflows are procedures — they either work or they do not. Their effectiveness is tracked through other mechanisms: the success_count and failure_count fields (available on all grains via the core field map, Section 6.1) and through supersession when an improved workflow replaces an underperforming one.

A third type sits nearby and is worth distinguishing. A Skill grain (0x0B, Section 8.11) is a packaged, transferable capability — instructions, permitted tools, bundled resources, and optional learned proficiency. A Workflow is a single fixed procedure. A Skill may reference several Workflows as context-dependent strategies: the capability is "incident triage," and the strategies are the specific graphs it runs under specific conditions.

Workflow Versioning Through Supersession

Workflows evolve. An incident response playbook gets refined after each incident. A deployment pipeline gets improved when a new tool becomes available.

In OMS, workflow evolution is modeled through supersession, not mutation. Since all grains are immutable, you cannot edit an existing Workflow. Instead, you create a new Workflow grain that supersedes the old one.

Version 1 — deploy straight to production after staging:

{
  "type": "workflow",
  "trigger": "new deployment to production",
  "nodes": ["integration_tests", "stage_deploy", "smoke_tests", "prod_deploy", "monitor"],
  "edges": [
    {"src": "integration_tests", "dst": "stage_deploy"},
    {"src": "stage_deploy", "dst": "smoke_tests"},
    {"src": "smoke_tests", "dst": "prod_deploy"},
    {"src": "prod_deploy", "dst": "monitor"}
  ],
  "created_at": 1768471200000,
  "namespace": "deployment"
}

Content address: <hash-v1>

Version 2 — a canary stage is inserted, and the rollout now branches on canary health:

{
  "type": "workflow",
  "trigger": "new deployment to production",
  "nodes": ["integration_tests", "stage_deploy", "smoke_tests", "canary", "monitor_canary", "full_rollout", "rollback", "monitor"],
  "edges": [
    {"src": "integration_tests", "dst": "stage_deploy"},
    {"src": "stage_deploy", "dst": "smoke_tests"},
    {"src": "smoke_tests", "dst": "canary"},
    {"src": "canary", "dst": "monitor_canary"},
    {"src": "monitor_canary", "dst": "full_rollout", "cond": "canary_healthy"},
    {"src": "monitor_canary", "dst": "rollback", "cond": "canary_degraded"},
    {"src": "full_rollout", "dst": "monitor"},
    {"src": "rollback", "dst": "monitor"}
  ],
  "created_at": 1768557600000,
  "namespace": "deployment",
  "derived_from": ["<hash-v1>"],
  "provenance_chain": [
    {
      "source_hash": "<hash-v1>",
      "method": "workflow_revision",
      "weight": 1.0
    }
  ]
}

Content address: <hash-v2>

After the new grain is written, the index layer sets superseded_by: "<hash-v2>" on the v1 grain (Section 15.3). The system_valid_to timestamp is also set on v1, marking when it was replaced. Any agent looking for the current deployment workflow finds v2. But v1 is never deleted — it remains as an immutable historical record, reachable by its content address.

The derived_from and provenance_chain fields on v2 explicitly link it to v1, creating a traceable lineage. An auditor can follow the chain to see how the deployment procedure evolved, who made each change (via author_did), and when each version was active.

Industry Use Cases

IT Operations Runbooks

Runbooks are a natural fit. Most real runbooks have a diagnostic fan-out at the top — check several things at once — followed by a decision point:

{
  "type": "workflow",
  "trigger": "disk usage exceeds 90%",
  "nodes": ["survey_usage", "check_core_dumps", "archive_old_logs", "clear_pkg_cache", "recheck", "notify_infra", "document"],
  "edges": [
    {"src": "survey_usage", "dst": "check_core_dumps"},
    {"src": "survey_usage", "dst": "archive_old_logs"},
    {"src": "survey_usage", "dst": "clear_pkg_cache"},
    {"src": "check_core_dumps", "dst": "recheck"},
    {"src": "archive_old_logs", "dst": "recheck"},
    {"src": "clear_pkg_cache", "dst": "recheck"},
    {"src": "recheck", "dst": "notify_infra", "cond": "still_above_85pct"},
    {"src": "recheck", "dst": "document"},
    {"src": "notify_infra", "dst": "document"}
  ],
  "created_at": 1768471200000,
  "namespace": "ops-runbooks",
  "importance": 0.9,
  "structural_tags": ["infrastructure", "disk-management", "runbook"]
}

The three cleanup actions are independent, so they fan out and rejoin. Escalation happens only on the conditional edge. Both paths document.

Manufacturing Standard Operating Procedures

Manufacturing SOPs are full of quarantine branches — a quality check either passes or it does not, and the two outcomes diverge sharply:

{
  "type": "workflow",
  "trigger": "batch quality check required for production line B",
  "nodes": ["pause_line", "sample", "measure_cmm", "measure_roughness", "compare_spec", "quarantine", "approve", "resume_line"],
  "edges": [
    {"src": "pause_line", "dst": "sample"},
    {"src": "sample", "dst": "measure_cmm"},
    {"src": "sample", "dst": "measure_roughness"},
    {"src": "measure_cmm", "dst": "compare_spec"},
    {"src": "measure_roughness", "dst": "compare_spec"},
    {"src": "compare_spec", "dst": "quarantine", "cond": "out_of_tolerance"},
    {"src": "compare_spec", "dst": "approve", "cond": "within_tolerance"},
    {"src": "approve", "dst": "resume_line"}
  ],
  "created_at": 1768471200000,
  "namespace": "manufacturing-sops",
  "structural_tags": ["quality-control", "line-B", "batch-inspection"]
}

Note that quarantine is a terminal node — it has no outgoing edges, so the line does not resume. That is expressed purely by topology.

Automated Trading Strategies

Trading strategies are procedures triggered by market conditions, and their pre-trade checks are naturally concurrent:

{
  "type": "workflow",
  "trigger": "RSI crosses below 30 on 4-hour timeframe",
  "nodes": ["confirm_daily_rsi", "check_ma_200", "check_volume", "size_position", "place_limit_buy", "set_stop_loss", "set_take_profit", "log_setup", "stand_down"],
  "edges": [
    {"src": "confirm_daily_rsi", "dst": "check_ma_200"},
    {"src": "confirm_daily_rsi", "dst": "check_volume"},
    {"src": "check_ma_200", "dst": "size_position", "cond": "all_filters_pass"},
    {"src": "check_volume", "dst": "size_position", "cond": "all_filters_pass"},
    {"src": "check_ma_200", "dst": "stand_down", "cond": "filter_failed"},
    {"src": "check_volume", "dst": "stand_down", "cond": "filter_failed"},
    {"src": "size_position", "dst": "place_limit_buy"},
    {"src": "place_limit_buy", "dst": "set_stop_loss"},
    {"src": "set_stop_loss", "dst": "set_take_profit"},
    {"src": "set_take_profit", "dst": "log_setup"}
  ],
  "created_at": 1768471200000,
  "namespace": "trading-strategies",
  "importance": 0.9,
  "structural_tags": ["mean-reversion", "equities", "systematic"]
}

Customer Escalation Procedures

Support escalation is where bounded cycles earn their place. A "provide customer update every 30 minutes" step is a genuine loop — it repeats until the incident resolves, and it needs a bound so an agent cannot spin forever:

{
  "type": "workflow",
  "trigger": "enterprise customer reports data loss incident",
  "nodes": ["page_oncall", "open_p1_ticket", "acknowledge_customer", "assign_commander", "investigate", "update_customer", "schedule_review", "deliver_report"],
  "edges": [
    {"src": "page_oncall", "dst": "open_p1_ticket"},
    {"src": "page_oncall", "dst": "acknowledge_customer"},
    {"src": "open_p1_ticket", "dst": "assign_commander"},
    {"src": "acknowledge_customer", "dst": "assign_commander"},
    {"src": "assign_commander", "dst": "investigate"},
    {"src": "investigate", "dst": "update_customer"},
    {"src": "update_customer", "dst": "investigate", "cond": "unresolved", "max_cycles": 48},
    {"src": "update_customer", "dst": "schedule_review", "cond": "resolved"},
    {"src": "schedule_review", "dst": "deliver_report"}
  ],
  "created_at": 1768471200000,
  "namespace": "customer-support",
  "importance": 0.95,
  "structural_tags": ["escalation", "p1", "enterprise", "data-loss"]
}

The back-edge from update_customer to investigate carries max_cycles: 48 — 24 hours of half-hourly updates. Without that bound, a conforming implementation should reject the grain.

Serialization Details

When a Workflow grain is serialized following the canonical algorithm (Section 4.9):

  1. Validate required fields: type must be "workflow", nodes must be a non-empty array of unique strings, created_at must be present. Validate that every src, dst, bindings key, and retries key references a declared node, and that no cycle is unbounded.
  2. Compact field names via the field map: type becomes t, created_at becomes ca, namespace becomes ns, importance becomes im. Among the Workflow-specific fields, only bindings shortens — to bind. trigger, nodes, edges, and retries keep their full names (Section 6.4). Inside each edge map, src, dst, and cond keep their names; max_cycles compacts to mxc.
  3. Preserve array order: The nodes array maintains insertion order (Section 4.6). This is critical — the entry point is defined as the first element.
  4. NFC-normalize all strings (Section 4.4), including every node ID, condition string, and the trigger.
  5. Omit null values (Section 4.5).
  6. Sort map keys lexicographically (Section 4.1) — including the keys inside each edge map and inside bind and retries.
  7. Encode as MessagePack.
  8. Prepend the 9-byte fixed header with type byte 0x04.
  9. Hash with SHA-256.

Workflows and the Success/Failure Feedback Loop

While Workflows do not have a confidence field, they participate in a feedback loop through two core fields available on all grain types (Section 6.1):

  • success_count (int, non-negative) — number of times this workflow was executed successfully
  • failure_count (int, non-negative) — number of times execution failed

Because grains are immutable, these counts are tracked by creating new Workflow grains that supersede the current one with updated counts. Over time, the ratio provides an empirical reliability measure.

The graph model sharpens this feedback considerably. Because every execution writes Tool grains tagged mg:step_action:<node_id>, failure is attributable to a specific node rather than to the procedure as a whole. A workflow whose failures cluster on one node does not need a rewrite — it needs a retries entry, a better binding, or a conditional bypass around that node.

Sharing Workflows Across Agents

Because Workflows are self-contained grains with standard serialization, they can be shared between agents, teams, and organizations:

  • Within a team: Agents in the same namespace can discover and use each other's Workflows.
  • Cross-team: The related_to field (Section 14.2) can link a Workflow to related Workflows in other namespaces, with relation_type: "similar" or "elaborates".
  • Cross-organization: Workflows can be exported as .mg files (Section 11) and imported by other systems. The content address provides integrity verification.
  • With signing: For high-trust sharing, Workflows can be wrapped in COSE Sign1 envelopes (Section 9) with the author's DID, providing cryptographic proof of authorship.

One caveat travels with the graph model: bindings are content addresses of Tool definition grains, and a receiving store that does not hold those grains cannot resolve tier one. That is not a defect — resolution falls through to the Named and Abstract tiers, so a shared workflow degrades to a readable plan rather than breaking. But an exporter that wants full fidelity should include the bound Tool definitions in the same .mg container.

Design Considerations

Node Granularity

How detailed should each node be? There is no spec-defined rule, but practical guidance:

  • Each node should be a single, actionable step
  • Node IDs should be understandable in isolation, since they double as labels
  • If a node has real internal structure, consider a separate Workflow for the sub-procedure and reference it with related_to

Model Concurrency Honestly

The temptation is to serialize everything — a straight line is easier to reason about. Resist it where the real procedure branches. A plan that says three checks run in sequence, when they actually run in parallel, will mislead every executor and every auditor who reads it. If steps are independent, give them independent edges.

Bound Every Cycle

If you add a back-edge, add max_cycles or a retries entry in the same commit. A conforming implementation should reject an unbounded cycle, so an unbounded one is not "permissive" — it is a grain that may fail validation on import.

Namespace Organization

Use namespaces to organize Workflows by domain: "ops-runbooks", "deployment", "customer-support", "trading-strategies". This enables efficient namespace-based filtering via the header namespace hash (bytes 3-4) without payload deserialization.

Summary

Workflows encode procedural memory — the knowledge of how to do things. Since OMS v1.4 the model is a directed graph: nodes name the steps, edges connect them, bindings attach them to real tools, and retries bound repetition. Structure is inferred from topology rather than declared, so a fork, a join, a decision point, and a terminal node are all consequences of how the graph is wired — with nothing to contradict.

The real power comes from four characteristics:

  1. Expressiveness: Parallel fan-out, AND-joins, conditional branching, and bounded cycles are first-class, so a plan can say what a real procedure actually does instead of flattening it into prose.

  2. Learnability: Agents can extract Workflows from observed patterns in Tool and Event grains — and the graph target preserves the concurrency and retry structure a flat list would discard.

  3. Versioning: The immutability model and supersession chain provide automatic, auditable version history. Execution records attach by relation, so runs never mutate the plan.

  4. Shareability: As standard OMS grains, Workflows can be shared across agents, teams, and organizations with integrity verification via content addressing and optional cryptographic signing.

Whether encoding IT runbooks, manufacturing SOPs, trading strategies, or customer escalation procedures, Workflows turn informal knowledge into structured, verifiable, portable procedural memory. They are the playbooks that agents can follow, share, improve, and trust.