7. Processes
A process is an ordered, possibly branched sequence of interface invocations. It captures business and technical flows: a checkout, a webhook handler, a daily batch job, a user onboarding sequence.
Processes are the primary source of dependency information in Archlang. The arrows between modules in your diagrams are derived from process steps. Delete a process, the arrows it implied disappear. There is no separate “draw a dependency” operation.
process Checkout { Customer > Orders.CreateOrder Orders > Inventory.Reserve Orders > Payments.Authorize Payments > Ledger.Record Orders > Notifications.SendEmail}Five steps; four modules involved; four dependency arrows derived (Customer → Orders, Orders → Inventory, Orders → Payments, Payments → Ledger, Orders → Notifications).
Step shape
Every step has the form:
Caller > Callee.Interface- Caller — a module (the entity issuing the call). Left of
>. - Callee — an interface (the handler that runs). Right of
>.
The arrow > reads “calls.” Whether the underlying transport is synchronous, async, or anything else is determined by the interface kind on the callee. With the bare interface kind, the language treats every call uniformly; stdlib kinds (command, event, …, Chapter 11) add sync/async distinctions and edge styling.
Mistake to avoid. Putting a module on the right side.
Payments > Ordersis a parse error; the right side must resolve to an interface, not a module. The validator catches this and tells you what’s there instead.
Placement
Processes can live in three places, all semantically equivalent:
Top-level. A free-standing orchestration:
process CheckoutFlow { Customer > Orders.CreateOrder Orders > Payments.Authorize}Inside a module body. Owned by that module; the fully-qualified name becomes Owner.Process:
module Orders { process Checkout { User > Orders.OrdersResource.Add }}Top-level with in. Declared flat, attached to a module’s body. Mirrors module in syntax:
process Fulfillment in Orders { Orders > Shipping.Schedule}The in form lets each team declare processes against another team’s module without editing that team’s file.
Branches: if / switch
Two forms of conditional. Use if for binary or short-chain branches; use switch when one head value picks a case from a known set.
process Checkout { Customer > Orders.CreateOrder Orders > Payments.Authorize
if "stripe-customer" { Payments > Stripe.Charge } else { Payments > PayPal.CreateOrder PayPal > PayPal.CaptureOrder }
Payments > Ledger.Record}if cond Body (else if cond Body)* (else Body)?. The condition is an identifier or quoted string; the body is a brace block.
process Checkout { Customer > Orders.CreateOrder Orders > Payments.Authorize
switch "payment processor" { stripe { Payments > Stripe.Charge } paypal { Payments > PayPal.CreateOrder PayPal > PayPal.CaptureOrder } }
Payments > Ledger.Record}switch [head] { case-label Body case-label Body ... }. No case keyword — each block starts with its label. The validator treats every case as reachable; the dependency graph includes every interface mentioned in any case.
Parallel: parallel
Concurrent steps use parallel:
process OrderFulfillment { Orders > Shipping.CreateShipment
parallel { { Shipping > Notifications.SendEmail } { Shipping > Notifications.SendSMS } }
Shipping > Orders.UpdateOrder}Branches inside parallel execute concurrently. Each branch is an explicit brace block (or a : oneliner for single steps). The renderer can lay them out side-by-side instead of sequentially.
Iteration: each
each Item in Collection { ... } iterates over a collection of items. Used for fan-out over an unknown-cardinality set.
process NotifyAll { each subscriber in NotificationList { Notifications > subscriber.SendUpdate }}Error paths: try / catch
try Body (catch [Label] Body)* for explicit error paths. Multiple catches allowed; each label optional.
process Checkout { Customer > Orders.CreateOrder try { Orders > Payments.Authorize } catch "declined" { Orders > Notifications.SendEmail }}Subprocesses
Repeated step sequences become subprocesses — reusable helpers invoked via do:
subprocess RecordEvent(name) { Orders > Ledger.Record}
process Checkout { Customer > Orders.CreateOrder do RecordEvent("checkout_started") Orders > Payments.Authorize do RecordEvent("payment_authorized")}Subprocesses can be top-level (visible everywhere), declared inside a module body (scoped to that module’s processes), or declared inside a type body (stamped onto every instance — see Chapter 18).
Lookup precedence when do X(...) runs:
- Subprocesses declared lexically inside the invoking process.
- Subprocesses on the owning module (closest ancestor wins).
- Free-standing top-level subprocesses.
Stable IDs
Processes carry stable IDs the same way modules do:
process #flow42 Checkout { Customer > Orders.CreateOrder}The formatter mints #flow42 on save. The ID lets diffs recognize a renamed process. See Chapter 13.
Validation
The parser and validator enforce process invariants:
- The caller (left of
>) must resolve to a module (including stdlibactormodules once the stdlib is imported — see Chapter 11). - The callee (right of
>) must resolve to an interface leaf (not a facet, not a module). - A
subscribes:referenced on a handler must point at an existing event. - A
do Xinvocation must resolve through the subprocess lookup precedence.
Failures appear as LSP diagnostics in your editor and as exit-1 errors from archlang validate.
What processes give you
Once you have processes, several things fall out automatically:
- Service call graph. Modules connected by the
Caller > Callee.Interfaceedges across all your processes. - Critical paths. A request flow that takes ten steps is visible at a glance.
- Blast radius. When one service goes down, which processes break? Trivially derivable.
- Change impact. When you remove an interface, the validator tells you every process step that depended on it.
None of this requires you to maintain a separate dependency catalog. The catalog is the union of every process step in the workspace.
Summary
- A process is a sequence of
Caller > Callee.Interfacesteps. - Callers are modules or actors; callees are interface leaves.
- Branches (
if/switch), parallelism (parallel), iteration (each), and error paths (try/catch) are first-class. - Subprocesses are reusable helpers invoked with
do. - Processes can live top-level, inside a module body, or attached via
in. - The dependency graph is derived from processes; you don’t draw arrows separately.
What’s next
Chapter 8: Views → — curated projections of the model for specific audiences.