Python monorepo managed with uv workspaces.
The core framework lives in agents-core/ and plugins live in plugins/ (37+ packages).
Python >= 3.10, 3.12 recommended.
All commands use uv. Never use python -m. If you run into dependency issues, stop and ask.
# Full check (ruff + mypy + unit tests)
uv run --no-sync dev.py check
# Unit tests only (--no-sync avoids uv panic in sandboxed environments)
uv run --no-sync pytest -m "not integration"
# Integration tests (needs .env secrets)
uv run --no-sync pytest -m "integration"
# Lint & format
uv run --no-sync ruff check .
uv run --no-sync ruff format .
# Type check
uv run --no-sync mypy- Framework: pytest. Never mock.
@pytest.mark.asynciois not needed (asyncio_mode = auto).- Integration tests use
@pytest.mark.integration. - Never adjust
sys.path. - Keep unit-tests for the class under the same test class. Do not spread them around different test classes. For example, tests for
Agentmust be insideTestAgent, etc.
- Never use
from __future__ import annotations. - Never write
except Exception as e. Catch specific exceptions. - Avoid
getattr,hasattr,delattr,setattr; prefer normal attribute access. - Docstrings: Google style, keep them short.
- Do not use section comments like
# -- some section -- - Prefer
logger.exception()when logging an error with a traceback instead oflogger.error("Error: {exc}") - Do not use local imports, import at the top of the module
- Avoid
# type: ignorecomments. - Avoid using
Anytype. - When adding code to an existing file, follow the patterns already established in that file (e.g. error handling style, import guards, naming).
Imports:
- ordered as: stdlib, third-party, local package, relative. Use
TYPE_CHECKINGguard for imports only needed by type annotations. - Never import from private modules (
_foo) outside of the package's own__init__.py. Use the public re-export (e.g.from vision_agents.testing import TestResponse, notfrom vision_agents.testing._run_result import TestResponse).
Naming:
- private attributes and methods use a leading underscore (
_sessions,_warmup_agent). Public API is plain snake_case.
Type annotations:
- use them everywhere. Modern syntax:
X | Yunions,dict[str, T]generics, fullCallablesignatures,Optionalfor nullable params.
Logging:
module-level logger = logging.getLogger(__name__). Use debug for lifecycle, info for notable events, error for failures without a traceback,
exception for errors with traceback.
- In hot paths (audio processing, event handling), guard debug logging behind
if logger.isEnabledFor(logging.DEBUG):to avoid formatting overhead when debug is disabled.
Constructor validation:
- raise
ValueErrorwith a descriptive message for invalid args. Prefer custom domain exceptions over generic ones.
Async patterns:
- async-first lifecycle methods (
start/stop). Support__aenter__/__aexit__for context manager usage. - Use
asyncio.Lock,asyncio.Task,asyncio.gatherfor concurrency. - Clean up resources in
finallyblocks.
Method order:
__init__, public lifecycle methods, properties, public feature methods, private helpers, dunder methods.
- When making multiple related changes to the same file, combine them into fewer Edit calls with enough surrounding context, rather than one edit per change.
- Run tests with Bash directly. Only use subagents for test runs when you need to do other work in parallel.
- Only use TodoWrite for tasks with 5+ steps. Don't update it after every individual edit.
- Lives in
CHANGELOG.mdat the repo root. - Organised by version heading (
# v0.4.0), then sections: Breaking Changes, New Features, Bug Fixes. - Only include user-facing changes (public API breaks, features, fixes). Skip docs-only and CI-only commits.
- Reference PR numbers inline, e.g.
(#374). - To generate:
git log <last-tag>..HEAD --oneline --no-merges, then classify each commit.