Skip to content

Advanced: Async & Futures

Lumen provides first-class async programming with futures.

Futures

Futures represent values that will be available later:

lumen
let future = spawn(long_running_task())
# ... do other work ...
let result = await future

Creating Futures

spawn

Create a future from a callable:

lumen
let f1 = spawn(compute(42))
let f2 = spawn(fetch_data("https://api.example.com"))

spawn with closure

lumen
let future = spawn(fn() -> String
  let a = compute_step_1()
  let b = compute_step_2(a)
  return b
end)

spawn list

Create multiple futures:

lumen
let futures = spawn([
  task_a(),
  task_b(),
  task_c()
])

let results = await futures  # list[Result]

Awaiting Futures

Basic await

lumen
let result = await future

Await resolves nested futures

lumen
let nested = [spawn(a()), spawn(b()), spawn(c())]
let results = await nested  # Automatically awaits each

Future States

lumen
enum FutureState
  Pending
  Completed(value: T)
  Error(message: String)
end

Check state:

lumen
match future.state
  Pending -> "Still running"
  Completed(v) -> "Done: {v}"
  Error(msg) -> "Failed: {msg}"
end

Scheduling Modes

Eager (default)

Futures start immediately when spawned:

lumen
let f = spawn(task())  # Starts now
# ... other work ...
let r = await f        # Waits if not done

Deferred FIFO

With @deterministic true:

lumen
@deterministic true

cell example() -> list[Int]
  let f1 = spawn(task_a())  # Queued
  let f2 = spawn(task_b())  # Queued
  
  # Execute in order: f1, then f2
  return [await f1, await f2]
end

Orchestration Patterns

Parallel

Run multiple futures concurrently:

lumen
await parallel for item in items
  process(item)
end

Block form:

lumen
await parallel
  a = fetch_a()
  b = fetch_b()
  c = fetch_c()
end

# a, b, c are all available
return a ++ b ++ c

Race

First to complete wins:

lumen
let result = await race
  fetch_from_primary()
  fetch_from_backup()
end

Vote

Consensus from multiple sources:

lumen
let answer = await vote
  model_a(question)
  model_b(question)
  model_c(question)
end

Select

Wait for first available:

lumen
await select
  msg = channel.receive()
  timeout = sleep(1000)
end

match msg
  m: Message -> handle(m)
  _ -> print("timeout")
end

Timeout

Add time limit:

lumen
let result = await timeout(5000, slow_operation())

match result
  v: T -> print("Got {v}")
  null -> print("Timed out")
end

Error Handling

Futures can fail:

lumen
let future = spawn(risky_operation())

match await future
  value: T -> print("Success: {value}")
  error: String -> print("Failed: {error}")
end

With try:

lumen
cell safe_fetch() -> result[Data, String]
  let future = spawn(fetch_data())
  let result = await future
  
  match result
    data: Data -> return ok(data)
    err: String -> return err(err)
  end
end

Async Cells

Mark cells as async:

lumen
async cell fetch_user(id: String) -> User
  let data = await fetch("/users/{id}")
  return parse_user(data)
end

Async cells can use await directly.

Practical Example: Web Scraper

lumen
async cell scrape_site(base_url: String) -> list[Page]
  let index = await fetch("{base_url}/index")
  let links = extract_links(index)
  
  let pages = await parallel for link in links
    fetch("{base_url}/{link}")
  end
  
  return pages
end

Practical Example: Multi-Model Query

lumen
use tool llm.chat as Chat

async cell query_ensemble(question: String) -> Consensus / {llm}
  # Get answers from multiple models in parallel
  let answers = await parallel
    gpt4 = query_model("gpt-4o", question)
    claude = query_model("claude-3-opus", question)
    gemini = query_model("gemini-pro", question)
  end
  
  # Vote on best answer
  let consensus = await vote
    gpt4.answer
    claude.answer
    gemini.answer
  end
  
  return Consensus(
    answer: consensus,
    models: [gpt4, claude, gemini]
  )
end

cell query_model(model: String, question: String) -> Answer / {llm}
  role system: You are a helpful assistant.
  role user: {question}
  return Chat(prompt: question, model: model)
end

Best Practices

  1. Use parallel for independence — Don't serialize unnecessarily
  2. Add timeouts — Prevent hanging
  3. Handle errors — Futures can fail
  4. Test with deterministic mode — Ensure reproducibility
  5. Avoid blocking in futures — Let the scheduler manage

Next Steps

MIT Licensed