This is the LlamaIndex Workflows library - an event-driven, async-first framework for orchestrating complex AI applications and multi-step processes.
- Python 3.9+
- AsyncIO (async/await)
- Pydantic for data models
- Starlette for web server
- Uvicorn for ASGI serving
Use the dev CLI to run tests:
# Run all package tests
uv run dev
# Filter by substring match
uv run dev -p workflows
uv run dev -p server -p client
# Pass pytest args after --
uv run dev -- -k test_nameFor more advanced scenarios, you can always cd packages/some-package and use pytest directly. The dev tool just provides additional package level test parallelism, and more curated cross package test output to avoid context bloat.
Several packages have Docker integration tests (requires Docker running) marked with @pytest.mark.docker. Run them with:
cd packages/<package> && uv run pytest -m docker -s -n0uv run pre-commit run -apackages/llama-index-workflows/src/workflows/- Main library codepackages/llama-index-workflows/src/workflows/server/- Web server implementationpackages/llama-index-workflows/tests/- Test suiteexamples/- Usage examples
See architecture-docs/ for high-level architectural overviews:
core-overview.md— Workflow, Context, Runtime, and event flowcontrol-loop.md— The reducer-based execution engineserver-architecture.md— HTTP server, persistence, and runtime decorators
The DBOS package has its own architecture doc explaining the distributed model (process boundaries, adapter rules, idle release):
- Workflow - Main orchestration class
- Context - State management across workflow steps
- Events - Event-driven communication between steps
- WorkflowServer - HTTP server for serving workflows as web services
- Always run tests after making changes:
uv run dev - Never use classes for tests, only use pytest functions
- Always annotate with types function arguments and return values
- The project uses async/await extensively
- Context serialization requires specific JSON format for globals
The following rules apply if you are running in an isolated sandbox environment and have tools to commit and push changes to git
Make sure to install uv as the package manager. Development commands rely on it.
curl -fsSL https://astral.sh/uv/install.sh | shAlways run tests and pre-commit before committing:
uv run dev
uv run pre-commit run -aWe use pytest with idiomatic pytest patterns. Follow these guidelines:
- No Test Classes: Do not use test classes to organize tests. Write tests as standalone functions. Achieve organization through descriptive function names (e.g.,
test_create_job_with_invalid_input_raises_error) or by splitting into separate test files. - Pytest Fixtures: Use fixtures for setup/teardown and shared test dependencies. Prefer fixtures over manual setup code repeated across tests.
- Prefer Real Objects Over Mocks: Use simple dataclasses and real objects directly when available rather than mocking them. Only mock external dependencies or things that are truly difficult to instantiate.
- DRY Test Setup: Do not repeat patches or setup code. Create reusable abstractions—fixtures, helper functions, or module-level constants—that can be shared across tests. Tests can easily be overwhelmed with setup; start from a rich suite of testing utilities to enable many small, expressive tests.
- Simple Testing Utilities: Testing utilities should be basic—just functions, fixtures, and global variables. Avoid over-engineering test infrastructure.
- Always use
from __future__ import annotationsat the top of each test file. Never use string annotations. - Include the standard SPDX license header at the top of each file:
# SPDX-License-Identifier: MIT # Copyright (c) 2026 LlamaIndex Inc.
- Comments are useful, but avoid fluff.
- Import etiquette
- Never use inline imports
- Never use
if TYPE_CHECKINGimports - Exceptions to these rules are made only when there are A) Acceptable circular imports or B) real startup performance issues
- Only add
__init__.py__all__exports when a file is legitimately needed for public library consumption. Module level imports should not be used internally. For the most part you should never do this unless explicitly requested to do so