Skip to content

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 > Orders is 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:

  1. Subprocesses declared lexically inside the invoking process.
  2. Subprocesses on the owning module (closest ancestor wins).
  3. 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 stdlib actor modules 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 X invocation 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.Interface edges 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.Interface steps.
  • 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.