ablative docs
↑↓ move↵ openesc close
Stack Notebook ablative.com.au

AWL parallel work and loops

Fork/join, multi-step regions, tolerant collection and bounded iteration.

Open .md

Forks and regions

fork awl 8

Runs work concurrently inside one step: `fork item in items` one branch per item, or a bare `fork` for named branches.

  • form statement
  • positions step-body
//! One branch per repo, checked in parallel, joined in input order.
workflow sweep_repos
  input repos: [String]
  outcome done: type [Verdict], route success

type Verdict { ok: Bool }

worker ci
  action check(repo: String) -> Verdict

step sweep
  fork repo in repos
    check(repo: repo)
  join -> verdicts
  route done(verdicts)
join awl 8

Closes a `fork`, binding a collection fork's results in input order.

  • form statement
  • positions step-body
//! `join` closes the fork, binding the branches' results in input order.
workflow gather_reports
  input repos: [String]
  outcome done: type [Report], route success

type Report { summary: String }

worker inspectors
  action inspect(repo: String) -> Report

step gather
  fork repo in repos
    inspect(repo: repo)
  join -> reports
  route done(reports)
sequential awl 8

Runs a collection `fork` one item at a time in input order instead of in parallel.

  • form statement
  • positions step-body
//! `sequential` publishes one target at a time, in input order.
workflow publish_targets
  input targets: [String]
  outcome done: type [Receipt], route success

type Receipt { target: String }

worker publisher
  action publish(target: String) -> Receipt

step publish_all
  fork target in targets sequential
    publish(target: target)
  join -> receipts
  route done(receipts)
distribute awl 8

Opens a multi-step region that runs one item track per element in parallel.

  • form statement
  • positions step-body
//! Every host gets its own step track; the tracks run in parallel.
workflow inspect_fleet
  input hosts: [String]
  outcome done: type [Report], route success

type Report { host: String }

worker ops
  action inspect(host: String) -> Report

step open
  distribute host in hosts

step work
  inspect(host: host) -> report

step close
  collect report -> reports
  route done(reports)
sequence awl 8

Opens a multi-step region that runs one item track at a time in collection order.

  • form statement
  • positions step-body
//! One host at a time, in collection order, each through its own track.
workflow roll_out
  input hosts: [String]
  outcome done: type [Attempt], route success

type Attempt { host: String, ok: Bool }

worker ops
  action deploy(host: String) -> Attempt

step ordered
  sequence host in hosts

step push
  deploy(host: host) -> attempt

step settle
  collect attempt -> attempts
  route done(attempts)
collect awl 8

Closes the nearest region as the first statement of its close step, binding results in input order.

  • form statement
  • positions step-body
//! `collect` opens the close step, folding the region's results back in.
workflow check_services
  input services: [String]
  outcome done: type Int, route success

type Health { ok: Bool }

worker probes
  action probe(service: String) -> Health

step open
  distribute service in services

step probe_one
  probe(service: service) -> health

step close
  collect health -> results
  results |> count -> total
  route done(total)
in awl 8

Names the per-item binding of a fork or a region opener, as in `fork item in items`.

  • form statement
  • positions step-body
//! `in` names the per-item binding: each branch sees one `name`.
workflow greet_everyone
  input names: [String]
  outcome done: type [Greeting], route success

type Greeting { text: String }

worker greeter
  action greet(name: String) -> Greeting

step greet_each
  fork name in names
    greet(name: name)
  join -> greetings
  route done(greetings)

A collection fork body compiles as one unbound action or child call. Each named fork branch compiles as one action call. Join collects the declared results.

Per-item distribute tracks are concurrent; sequence tracks run in order. A collected optional result preserves a failed track as an absent value rather than pretending all tracks succeeded.

Bounded iteration

loop awl 9

Runs a bounded value-threading loop whose body runs at least once and must rebind the threaded value.

  • form statement
  • positions step-body
//! Improve the draft until it is ready, at most three passes.
workflow refine_draft
  input topic: String
  outcome done:  type Draft,  route success
  outcome stuck: type String, route failure

type Draft { body: String, ready: Bool }

worker writers
  action improve(topic: String, prior: Draft) -> Draft

step refine
  loop draft = Draft(body: "", ready: false)
    improve(topic: topic, prior: draft) -> draft
    until draft.ready
    max 3
  outcome ready: if draft.ready, route done(draft)
  outcome exhausted: else, route stuck("the draft never settled")
counting awl 9

Binds the number of completed iterations of a `loop`.

  • form statement
  • positions step-body
//! `counting` binds how many iterations the loop completed.
workflow poll_job
  input job: String
  outcome done:  type Int,    route success
  outcome stuck: type String, route failure

type Status { ready: Bool }

worker jobs
  action poll(job: String, prior: Status) -> Status

step watch
  loop status = Status(ready: false) counting polls
    poll(job: job, prior: status) -> status
    until status.ready
    max 10
  outcome ready: if status.ready, route done(polls)
  outcome gave_up: else, route stuck(job)
until awl 9

States the condition that ends a `loop`, tested after the body has run.

  • form statement
  • positions step-body
//! `until` ends the loop, tested after each pass of the body.
workflow stabilize_build
  input tag: String
  outcome done:  type Build,  route success
  outcome stuck: type String, route failure

type Build { green: Bool }

worker ci
  action rebuild(tag: String, prior: Build) -> Build

step converge
  loop build = Build(green: false)
    rebuild(tag: tag, prior: build) -> build
    until build.green
    max 5
  outcome green: if build.green, route done(build)
  outcome exhausted: else, route stuck(tag)
max awl 9

Bounds iteration: the mandatory ceiling on a `loop`, or `max N visits` on a step inside a flow loop.

  • form statement
  • positions step-body
//! `max` is the mandatory ceiling; exhaustion is a distinct outcome.
workflow negotiate_quote
  input request: String
  outcome done:  type Offer,  route success
  outcome stuck: type String, route failure

type Offer { accepted: Bool }

worker brokers
  action haggle(request: String, prior: Offer) -> Offer

step negotiate
  loop offer = Offer(accepted: false)
    haggle(request: request, prior: offer) -> offer
    until offer.accepted
    max 4
  outcome agreed: if offer.accepted, route done(offer)
  outcome exhausted: else, route stuck(request)
visits awl 9

Completes a step's `max N visits` bound, and reads as the visit count inside that step's guards.

  • form statement
  • positions step-body
//! `max 3 visits` bounds the flow loop; `visits` reads the count in guards.
workflow recheck_repo
  input repo: String
  outcome done: type Verdict, route success

type Verdict { ok: Bool }

worker ci
  action check(repo: String) -> Verdict

step attempt
  check(repo: repo) -> verdict

step judge
  outcome again: if not verdict.ok and visits < 3, route attempt
  outcome settled: else, route done(verdict)
  max 3 visits

A value-threading loop runs its body at least once and must rebind its threaded value. Counting optionally binds completed iterations; until tests after the body, and max is mandatory. A visit bound limits re-entry to a step. Bindings and result routes must remain valid on every path.

Parallel bodies have stricter bytecode compilation rules than sequential bodies. In particular, literal indexing is refused in a parallel branch or per-item track. See compiler boundaries.