Imported from timurci/atk (
AGENTS.md). Install upstream withnpx skills add timurci/atk. Copyright stays with the author.
AGENTS.md — atk
Project Overview
atk (Agent Toolkit) is a Python library that provides a unified, vendor-agnostic interface for building AI agent systems. The core package (atk.core) defines abstract protocols for language models, message schemas, and tool definitions — designed to be importable by anyone building their own agent harness. Provider integrations (e.g. atk.providers) are secondary modules that implement the core protocol using third-party SDKs. The project is structured as an extensible toolkit, with planned future modules for tools (atk.tools) and a proper agent harness.
Tech Stack
- Language: Python
3.14+ - Package manager / runner:
uv - Dependencies declared in:
pyproject.toml - Linter + formatter:
ruff(config inruff.toml) - Static type checker:
ty - Pre-commit hooks:
prek(config inprek.toml) - Testing:
pytest - Key libraries:
pydantic— data models and validation (core dependency)any-llm-sdk— unified LLM provider interface (optional)
Environment
Install dependencies
uv sync
Install with optional provider support
uv sync --extra providers
Run tests
uv run pytest
Quality Gates
Every change must pass all of the following before it is considered complete:
# 1. Fix lint issues
uv run ruff check --fix
# 2. Format code
uv run ruff format
# 3. Type check
uv run ty check
# 4. Run tests
uv run pytest
Run these in order. Fix all errors and warnings before finishing. Do not submit a change that fails any of these.
Pre-commit Hooks
Install once after cloning:
prek install
Run manually against all files:
prek run --all-files
Hooks enforce: conventional commit messages, ruff check/format, ty type checking, and safety checks (private keys, large files, whitespace, TOML/YAML/JSON validity).
Project Structure
atk/
├── pyproject.toml # Project metadata and dependencies
├── ruff.toml # Ruff linter/formatter config
├── prek.toml # Pre-commit hook configuration
├── AGENTS.md # This file
│
├── src/
│ └── atk/
│ ├── __init__.py
│ ├── core/ # Abstract protocol — vendor-agnostic
│ │ ├── __init__.py
│ │ ├── model.py # LanguageModel protocol (async interface)
│ │ ├── message.py # Message schemas (User/Assistant/Tool messages)
│ │ ├── tool.py # Tool schema and parameter types
│ │ └── toolset.py # CallableToolset — invoke tools from Tool definitions
│ └── providers/ # any-llm integration (3rd-party adapter)
│ ├── __init__.py
│ ├── model.py # AnyLanguageModel implements LanguageModel
│ ├── message.py # MessageMapper — bidirectional mapping
│ └── tool.py # ToolMapper — internal → any-llm tool schema
│
├── tests/
│ ├── __init__.py
│ └── unit/
│ ├── __init__.py
│ ├── core/
│ │ ├── __init__.py
│ │ ├── conftest.py # Shared fixtures
│ │ ├── test_tool_from_callable.py
│ │ └── tool.py # Test fixtures / helpers
│ └── providers/
│ ├── __init__.py
│ ├── test_message_mapper.py
│ ├── test_model.py
│ ├── test_stream_accumulator.py
│ └── test_tool_mapper.py
│
├── examples/
│ ├── __init__.py
│ ├── structured.py # Structured output example
│ └── chat/ # Interactive chat example
│ ├── __init__.py
│ ├── main.py
│ ├── chat_loop.py
│ ├── display.py
│ └── tools.py
│
└── docs/ # Documentation
Architecture & Design
atk has two explicit layers. Maintain this boundary strictly:
Provider integrations (atk.providers)
│ implement
▼
Core protocol (atk.core)
│ defines
▼
Pydantic models & types (message.py, tool.py)
- Core layer (
atk.core/): Defines theLanguageModelprotocol, message types (UserMessage,AssistantMessage,ToolMessage), and tool parameter schemas. This is the library surface — anyone should be able to importatk.coreand build their own agent system without any provider dependency. Keep it clean, abstract, and free of provider-specific code. - Provider layer (
atk.providers/): A third-party integration adapter that implements the core protocol using theany-llm-sdklibrary. Contains mappers that translate between internal types and any-llm SDK types. This module is independent and should not be imported by other vendor modules except throughatk.core. Because it depends onany-llm-sdk, it delegates provider selection to any-llm (which uses official provider SDKs under the hood).
Design principles:
- KISS: Prefer the simplest implementation that satisfies the current requirement. Prefer plain functions and Pydantic models over class hierarchies. Avoid premature abstraction, unnecessary indirection, and abstractions that do not remove real duplication.
- YAGNI: Do not add plugin systems, registries, config hooks, extensibility points, or abstract base classes unless a concrete second implementation exists or is explicitly required.
- Core-first:
atk.coreis the primary artifact. Provider modules are implementations of its contracts, not peers. - Let errors propagate: Do not catch exceptions only to return a silent fallback (e.g., empty bytes,
None, or an empty list). That masks the real failure and makes debugging harder. Only catch exceptions if the code can meaningfully recover or add context; otherwise, let the exception propagate.
Code Conventions
Naming
- Modules and packages:
snake_case - Classes:
PascalCase - Functions, variables, parameters:
snake_case - Constants:
UPPER_SNAKE_CASE
Typing
- All public function signatures must have complete type annotations.
- Prefer
X | NoneoverOptional[X]. - Do not use
Anyunless interfacing with an untyped third-party library, and always add a comment explaining why. - Do not use
cast. Use type narrowing, validation, overloads, or clearer data modeling instead.
Error handling
- Raise
NotImplementedErrorwith a descriptive message for unimplemented methods or unsupported cases. - Never raise bare
ExceptionorValueError— use specific exception types orNotImplementedError. - Custom project error classes must inherit directly from
Exception, not from built-in exception subclasses. - Provider modules should raise
NotImplementedErrorfor unsupported features (e.g. audio output, custom tool calls) with a clear message.
Imports
- Import order: stdlib → third-party → internal (ruff enforces this).
- Never use wildcard imports.
Async design
- The
LanguageModelprotocol is async-only (async def generate_response). All provider implementations must follow this pattern. Do not add synchronous implementations.
What NOT to Do
- Do not add dependencies without
uv add [package](updatespyproject.tomland lockfile). - Do not use
pip installdirectly. - Do not put provider-specific code in
atk.core/— it must remain provider-agnostic. - Do not import from one provider module into another.
- Do not add synchronous implementations of
LanguageModel— the protocol is async-only. - Do not suppress type errors with
# type: ignoreor# ty: ignorewithout an explanatory comment. - Do not use
cast; model or narrow the type properly. - Do not edit
ruff.tomlor add# noqasuppressions without a comment explaining the exception. - Do not introduce plugin systems or registries unless a concrete second provider implementation exists.
Known Gotchas
- Python
3.14+is required — do not use syntax or features unavailable in this version. - The
any-llm-sdkdependency is optional. Code inatk.providers/will fail to import without it — this is intentional. - Ruff is configured with
select = ["ALL"]and only ignoresCOM812globally. Per-file ignores fortests/**disable annotation requirements, assert bans, and docstring requirements. atk.providersusesany-llm-sdkwhich re-exports OpenAI SDK types. Theopenaipackage is a transitive dependency — do not import it directly inatk.providers; always useany_llm.types.completionor construct dicts for message format.