Development Guide
March 9, 2026 · View on GitHub
Dependencies
This project uses modern Python packaging with pyproject.toml. Dependencies are managed as follows:
- Main dependencies are in
[project.dependencies] - Test dependencies are in
[project.optional-dependencies.test] - Development dependencies are in
[project.optional-dependencies.dev]
Working within a virtual environment
Create your python virtual environment:
uv venv
source .venv/bin/activate
uv sync --active
Generating Requirements Files
If you need to generate requirements files (e.g., for deployment or specific environments):
Option 2 - Using uv:
# Install everything from lock file
uv sync --no-dev # Since dev are added by default
# Generate lock file with all dependencies
uv lock
Installing Dependencies
Option 2 - Using uv:
# Install main package with test dependencies
uv sync --group test
# Install main package with all optional dependencies
uv sync --group vectorstore
# Install in editable mode
uv sync --editable
Local Development
Sometimes you need to work with local changes from other Dapr repositories.
Using Local Python Dapr Package Changes
If you need to work with additional Python Dapr packages during local development, for example, those from python-sdk or durabletask-python, then you can follow the same steps above and then install your local versions. Adjust the paths as needed for your setup.
uv pip install -e ../durabletask-python \
-e ../python-sdk \
-e ../python-sdk/ext/dapr-ext-fastapi \
-e ../python-sdk/ext/dapr-ext-workflow
You can also update pyproject.toml file to point to your local repo instead. For example, instead of:
"durabletask-dapr=>0.2.0a15",
You can use:
"durabletask-dapr @ file:///Users/samcoyle/go/src/github.com/durabletask-python",
Using Local Dapr Runtime Changes
If you need to make changes relating to Dapr runtime during local development, for example, those from dapr or components-contrib, then follow these steps.
Working with local components-contrib changes:
- Make your changes in components-contrib.
- In
dapr/dapr, update the rootgo.modfile to point to your localcomponents-contribrepository. The override block is near the bottom to uncomment and adjust the path as needed.
Using the Dapr CLI with local dapr/dapr changes:
cd /cmd/daprd
go build -tags=allcomponents -v
cp daprd ~/.dapr/bin/daprd
Once copied, the binary at ~/.dapr/bin/daprd becomes the version used by dapr run.
Adjust this path as needed for your local setup.
Running dapr version should show the runtime as edge, which confirms your local build is being used.
Command Mapping
| pip/pip-tools command | uv equivalent |
|---|---|
pip-compile pyproject.toml | uv lock |
pip-compile --all-extras | uv lock (automatic) |
pip install -r requirements.txt | uv sync |
pip install -e . | uv sync --editable |
pip install -e ".[dev]" | uv sync --extra=dev |
pip install -e ".[test,dev]" | uv sync --all-extras |
Testing
The project uses pytest for testing. To run tests:
# Run all tests
uv run pytest
# Run specific test file
uv run pytest tests/test_random_orchestrator.py
# Run tests with coverage
uv run pytest --cov=dapr_agents
Integration Tests
Note: we do not use
pytest-docker-composeintentionally here because it is not compatible with Python2, and requires an old version of pyyaml < version6, but the rest of our project requires >6 for this pkg.
Requires Dapr CLI to be installed.
# Install test dependencies
uv sync --group test
# Set API key (required)
export OPENAI_API_KEY=your_key_here
# Run all integration tests
uv run pytest tests/integration/quickstarts/ -v -m integration
# Run specific test file
uv run pytest tests/integration/quickstarts/test_01_dapr_agents_fundamentals.py -v
# Run specific test func
uv run pytest -m integration -v tests/integration/quickstarts/test_01_dapr_agents_fundamentals.py::TestHelloWorldQuickstart::test_01_llm_client
# Run with coverage
uv run pytest tests/integration/quickstarts/ -v -m integration --cov=dapr_agents
Note: Parallel execution can be enabled with pytest-xdist using -n auto or -n
. Example: pytest -n auto -m integration.
To use an existing venv to speed up local development time, then you can update the quickstarts to set create_venv to True as a parameter in run_quickstart_script. Alternatively, you can set the env var setting: USE_EXISTING_VENV=true.
Integration Tests with Ollama (No API Key Needed)
You can run integration tests locally using Ollama instead of OpenAI. This is free, runs entirely on your machine, and is the same setup used in CI on every pull request.
Prerequisites
-
Install Ollama:
# macOS brew install ollama # Linux curl -fsSL https://ollama.com/install.sh | sh -
Start Ollama and pull the model:
ollama serve # Start the server (skip if already running) ollama pull qwen3:0.6b # ~523MB download, cached after first pull -
Ensure Dapr is initialized:
dapr init
Running the Tests
# Install test dependencies
uv sync --group test
# Run all Ollama-compatible E2E tests
OLLAMA_ENDPOINT=http://localhost:11434/v1 \
OLLAMA_MODEL=qwen3:0.6b \
OPENAI_API_KEY=ollama \
uv run pytest -m "integration and ollama" -v --timeout=300 \
tests/integration/quickstarts/
# Run a single test (e.g., the simplest LLM client test)
OLLAMA_ENDPOINT=http://localhost:11434/v1 \
OLLAMA_MODEL=qwen3:0.6b \
OPENAI_API_KEY=ollama \
uv run pytest -m "integration and ollama" -v -k test_01_llm_client --timeout=300 \
tests/integration/quickstarts/
How It Works
When OLLAMA_ENDPOINT is set, the test framework:
- Returns a dummy
"ollama"API key instead of requiringOPENAI_API_KEY - Swaps the Dapr
llm-provider.yamlcomponent to useconversation.openaipointing at the Ollama endpoint - Resolves
{{OLLAMA_MODEL}}and{{OLLAMA_ENDPOINT}}in the component YAML via the existing env-template resolver
Tests marked with @pytest.mark.ollama are the subset validated to work with small local models. These include basic LLM calls, agent tool calling, memory, durable agents, workflows, and hot-reload config change tests.
Environment Variables
| Variable | Required | Default | Description |
|---|---|---|---|
OLLAMA_ENDPOINT | Yes | — | Ollama OpenAI-compatible endpoint (e.g., http://localhost:11434/v1) |
OLLAMA_MODEL | No | qwen3:0.6b | Ollama model to use |
OPENAI_API_KEY | Yes | — | Set to ollama (dummy value, required by test fixtures) |
Using a Different Model
Any Ollama model with tool-calling support works. Larger models give more reliable results:
# Use a larger model for more reliable tool calling
ollama pull qwen2.5:3b
OLLAMA_ENDPOINT=http://localhost:11434/v1 \
OLLAMA_MODEL=qwen2.5:3b \
OPENAI_API_KEY=ollama \
uv run pytest -m "integration and ollama" -v --timeout=300 \
tests/integration/quickstarts/
CI Workflow
The E2E Tests (Ollama) GitHub Actions workflow (.github/workflows/ci-e2e-ollama.yaml) runs these same tests automatically on every pull request using qwen3:0.6b with --reruns 2 for flaky-tolerance.
Code Quality
The project uses several tools to maintain code quality:
# Run linting
uv run flake8 dapr_agents tests --ignore=E501,F401,W503,E203,E704
# Run code formatting
uv run ruff format
# Run type checking
uv run mypy --config-file mypy.ini
## Run all combined
uv run ruff format && uv run flake8 dapr_agents tests --ignore=E501,F401,W503,E203,E704 && uv run mypy --config-file mypy.ini && uv run pytest tests -m "not integration"
Pre-Push Hooks
This project uses pre-commit hooks to automatically run quality checks before pushing code to GitHub. These hooks catch issues in ~10 seconds instead of waiting 5-10 minutes for CI.
Installation
-
Install pre-commit framework (if not already installed):
uv pip install pre-commit # or pip install pre-commit -
Install the git hooks:
pre-commit install --hook-type pre-push -
(Optional) Run manually on all files to verify setup:
pre-commit run --all-files --hook-stage pre-push
What Gets Checked
When you run git push, the following checks run automatically:
- File hygiene - Trailing whitespace, end-of-file fixes
- YAML validation - Check component config files
- Code formatting - Ruff auto-formats code
- Linting - Flake8 checks for code issues
- Type checking - MyPy validates types
- Unit tests - Pytest runs ~256 unit tests (excluding integration tests)
These checks mirror the CI/CD pipeline, catching issues before they reach GitHub.
Running Hooks Manually
# Run all pre-push hooks without pushing
pre-commit run --all-files --hook-stage pre-push
# Or use the Makefile shortcut
make hooks-run
# Run all hooks PLUS integration tests (comprehensive check, slower)
make hooks-run-all
# Run individual checks (same commands as before)
uv run ruff format dapr_agents tests
uv run flake8 dapr_agents tests --ignore=E501,F401,W503,E203,E704
uv run mypy --config-file mypy.ini
uv run pytest tests -m "not integration"
Skipping Hooks (Emergency Only)
If you absolutely must push without running hooks:
git push --no-verify
Note: Use sparingly - CI will still catch issues, but this defeats the purpose of local validation.
Troubleshooting
"Hook failed to run"
- Ensure dependencies are installed:
uv sync --group dev --group test
"Tests are failing"
- Run tests locally to see details:
uv run pytest tests -m "not integration" -v - Fix failing tests before pushing
"First run is slow"
- First-time execution downloads pre-commit repositories (~30s)
- Subsequent runs are cached and fast (~10s)
Performance
- Expected runtime: 8-12 seconds (pre-push hooks only)
- Expected runtime: 2-5 minutes (with
make hooks-run-allincluding integration tests) - Only checks staged files where possible
- Integration tests NOT included in pre-push hooks (too slow - use
make hooks-run-allfor comprehensive local check)
Development Workflow
Option 1 - Using pip:
-
Install development dependencies:
uv sync --group test -
Run tests before making changes:
uv run pytest tests -m "not integration" -
Make your changes
-
Run code quality checks:
uv run flake8 dapr_agents tests --ignore=E501,F401,W503,E203,E704 uv run ruff format uv run mypy --config-file mypy.ini -
Run tests again:
uv run pytest tests -m "not integration" -
Submit your changes
To run pre-commit hooks
pre-commit run --all-files
To run the Metadata Schema Generator
TODO(@casperGN): to pls add in when we should run this, when it gets ran in CI, how to avoid local versions from getting committed, etc.
uv run python scripts/generate_metadata_schema.py --version X.X.X
Design/Behavioral Decisions
DurableAgent Durability
Scenarios:
- every time we run the same app instance any inflight workflow will be resumed. 1.1 caveat here is wf will continue but you will not get the result.
- every time i have a .run() or invoke new workflow via curl or pubsub then a new workflow instance id will be created. If there is an inflight workflow already then it will be resumed, and the new one will be created.
- Trace ID = workflow ID and make the tracing pick up from where it left off.
Internal class structuring/setup
When to use Pydantic vs dataclasses:
- Use Pydantic for:
- Data crossing trust boundaries or is persisted: API payloads, pub/sub messages, persisted state (workflow state, timeline messages, trigger/broadcast schemas, tool records, etc.).
- Schemas requiring coercion, validation, or versioned migrations.
- Use dataclasses for:
- Agent construction knobs you pass in code (ie agent config classes).
- Dependency injection of services/stores/policies and behavior hooks.
Mental model:
- Think “config vs data”:
- Config you wire at construction time → dataclasses.
- Data the system processes/persists at runtime → Pydantic.
Using the env-template resolver
For Dapr component files that reference environment variables (e.g., {{OPENAI_API_KEY}} or ${{OPENAI_API_KEY}}), use the helper to render a temporary resources folder and pass it to Dapr:
dapr run --resources-path $(quickstarts/resolve_env_templates.py quickstarts/01-dapr-agents-fundamentals/components) -- python 03_durable_agent.py
The helper scans only .yaml/.yml files (non-recursive), replaces placeholders with matching env var values, writes processed files to a temp directory, and prints that directory path.