Concurrency

Introduction

Overview

llm.rb supports six concurrency strategies for tool execution: :sequential, :thread, :fiber, :async, :fork, and :ractor. Each implements the same interface (spawn, wait, alive?, interrupt!), so the caller never has to care which strategy is behind a given task.

How it works

Each strategy creates a task object that wraps a function call. spawn starts execution, wait collects the result, alive? checks whether the task is still running, and interrupt! signals the task to stop.

Interruption is reliable across all six. LLM::Interrupt reaches the tool regardless of whether it runs on a thread, fiber, process, or ractor. The tool can rescue, clean up, and either re-raise to cancel the turn or return a value to continue.

agent = LLM::Agent.new(llm, concurrency: :fork, tools: [...])

fn = FetchStocks.function
fn.task(:thread).wait

Why would I use it?

Different tools have different execution requirements. IO-bound tools benefit from thread or async concurrency. CPU-bound tools benefit from ractors or forks. Tools that might crash the process should run in a fork for isolation.

The common interface means you can change strategies without changing your tool code.

Notes

sequential: Tools run one at a time on the calling thread. No overhead. Best for simple agents or when tool order matters.

thread: Each tool runs in its own Thread. Releases the GVL during blocking IO. Best for IO-bound tools like HTTP calls and database queries.

fiber: Each tool runs in a scheduler-backed Fiber. Requires Fiber.scheduler. Best for IO-bound tools inside an async framework. Much lighter than threads.

async: Each tool runs as an Async::Task inside a managed background reactor. Best for IO-bound tools when you want Async’s structured concurrency model without running your whole application inside a reactor. Requires the async gem.

fork: Each tool runs in a forked child process. True parallelism and process isolation. Best for shell commands, native extensions, or anything you do not want touching the parent’s memory. Requires the xchan gem.

ractor: Each class-based tool runs in a Ruby Ractor. True parallelism without the overhead of forking full processes. Only class-based tools are supported. Arguments must be ractor-shareable.

Strategy Backing Parallel? Isolation? Requires
:sequential direct call No No None
:thread Thread IO only (GVL) No None
:fiber Fiber.schedule Cooperative No Fiber.scheduler
:async Async::Reactor Cooperative No async gem
:fork Kernel.fork Yes (process) Yes (memory) xchan gem
:ractor Ractor Yes (CPU) Limited None