Protocols¶
Axio’s extensibility comes from a small set of runtime-checkable protocols and abstract base classes. Implement any of them to plug your own components into the framework - no subclassing the agent, no monkey-patching.
classDiagram
class CompletionTransport {
<<Protocol>>
+stream(messages, tools, system) AsyncIterator~StreamEvent~
}
class ContextStore {
<<ABC>>
+append(message)*
+get_history() list~Message~*
+session_id str
+fork() ContextStore
+clear()
+close()
+list_sessions() list~SessionInfo~
}
class PermissionGuard {
<<ABC>>
+check(handler) handler*
}
Agent --> CompletionTransport
Agent --> ContextStore
Tool --> PermissionGuard
CompletionTransport¶
The transport protocol has a single method:
from typing import runtime_checkable, Protocol
from collections.abc import AsyncIterator
from axio.messages import Message
from axio import Tool, StreamEvent
@runtime_checkable
class CompletionTransport(Protocol):
def stream(
self,
messages: list[Message],
tools: list[Tool],
system: str,
) -> AsyncIterator[StreamEvent]: ...
The agent calls stream() on every iteration, passing the full conversation
history, the available tools, and the system prompt. The transport yields
StreamEvent values as they arrive from the LLM.
Available transports (each in its own installable package):
Transport |
Package |
Notes |
|---|---|---|
|
|
Anthropic Claude models |
|
|
OpenAI and OpenAI-compatible APIs |
|
|
ChatGPT via OAuth |
The core axio package does not bundle any transport implementation - install
the appropriate package for your model provider.
See Writing Transports for a step-by-step guide.
ContextStore¶
The context store manages conversation history. It is an abstract base class
with async methods. Only append and get_history are truly abstract -
everything else has a working default implementation:
from axio.messages import Message
from axio import ContextStore
class MyContextStore(ContextStore):
def __init__(self) -> None:
self._messages: list[Message] = []
async def append(self, message: Message) -> None:
self._messages.append(message)
async def get_history(self) -> list[Message]:
return list(self._messages)
# Default implementations provided by ContextStore (override as needed):
# session_id - lazy UUID hex property
# clear() - raises NotImplementedError by default
# fork() - deep-copies history into a MemoryContextStore
# close() - no-op by default
# set_context_tokens(input, output) - no-op by default
# get_context_tokens() - returns (0, 0) by default
# add_context_tokens(input, output) - increments via get/set above
# list_sessions() - returns a single SessionInfo for the current session
store = MyContextStore()
assert store.session_id # auto-generated UUID hex
Built-in implementations:
MemoryContextStore(inaxio) - in-memory, no persistence; ideal for short-lived agents, tests, and prototypes.SQLiteContextStore(inaxio-context-sqlite) - persistent, SQLite-backed; survives process restarts and supports multiple named sessions per project.
Implement your own ContextStore to back conversations with Redis, a database,
or any other storage layer.
See Context & Messages for details on the message model.
PermissionGuard¶
Guards gate tool execution. They sit between parameter validation and handler
invocation. PermissionGuard is an abstract base class (ABC) - not a
Protocol. Subclass it and implement check():
from typing import Any
from axio import PermissionGuard, Tool
class MyGuard(PermissionGuard):
async def check(self, tool: Tool[Any], **kwargs: Any) -> dict[str, Any]:
# return kwargs to allow, raise GuardError to deny
return kwargs
A guard receives the Tool object and the raw keyword arguments. It must
either return a dict of (possibly modified) kwargs to allow execution, or
raise GuardError to deny. Guards can modify the kwargs before returning them.
Tool calls are made via await guard(tool, **kwargs), which delegates to check().
The ConcurrentGuard subclass additionally wraps check() in an
asyncio.Semaphore to control parallelism.
Axio ships three built-in guards:
AllowAllGuardAlways returns kwargs unchanged - useful as a no-op default.
DenyAllGuardAlways raises
GuardError("denied")- useful for locked-down environments.ConcurrentGuardAbstract base that serializes (or rate-limits) concurrent
check()calls via a semaphore. Set theconcurrencyclass attribute to control parallelism (default: 1).
Multiple guards compose sequentially - each guard’s output is passed to the next.
See Permission Guards for the full guard system and Writing Guards for a how-to guide.
Additional transport protocols¶
Beyond CompletionTransport, Axio defines protocols for other AI modalities.
These protocols are protocol-only - the core axio package defines the
interfaces, but does not ship with implementations. You can implement them
yourself or use third-party packages when available.
ImageGenTransport¶
Generates images from text prompts.
from typing import runtime_checkable, Protocol
@runtime_checkable
class ImageGenTransport(Protocol):
async def generate(
self,
prompt: str,
*,
size: tuple[int, int] | None = None,
n: int = 1,
) -> list[bytes]:
"""Generate images from a text prompt.
Args:
prompt: Text description of the image to generate.
size: Optional (width, height) tuple in pixels.
n: Number of images to generate (default: 1).
Returns:
List of raw image bytes (one per generated image).
"""
...
Purpose: Text-to-image generation for tasks like creating diagrams, illustrations, or visual content on demand.
Usage pattern:
async def example():
from axio.transport import ImageGenTransport
transport: ImageGenTransport = ... # your implementation
images = await transport.generate("a red moon", size=(1024, 1024), n=1)
assert len(images) == 1
assert isinstance(images[0], bytes)
Implementation status: Protocol-only. No official implementation package ships with Axio. Implement your own or use a third-party package.
TTSTransport¶
Synthesizes speech from text (text-to-speech).
from typing import runtime_checkable, Protocol
from collections.abc import AsyncIterator
@runtime_checkable
class TTSTransport(Protocol):
def synthesize(
self,
text: str,
*,
voice: str | None = None,
) -> AsyncIterator[bytes]:
"""Synthesize speech from text.
Args:
text: Text to convert to speech.
voice: Optional voice identifier (provider-specific).
Yields:
Chunks of audio data (e.g., WAV or MP3 bytes).
"""
...
Purpose: Convert agent responses to audio for voice assistants or accessibility features.
Usage pattern:
async def example():
from axio.transport import TTSTransport
transport: TTSTransport = ... # your implementation
audio_chunks = [
chunk
async for chunk in transport.synthesize("Hello world", voice="alloy")
]
audio_data = b"".join(audio_chunks)
assert isinstance(audio_data, bytes)
Implementation status: Protocol-only. No official implementation package ships with Axio. Implement your own or use a third-party package.
STTTransport¶
Transcribes audio to text (speech-to-text).
from typing import runtime_checkable, Protocol
@runtime_checkable
class STTTransport(Protocol):
async def transcribe(
self,
audio: bytes,
media_type: str = "audio/wav",
) -> str:
"""Transcribe audio to text.
Args:
audio: Raw audio bytes.
media_type: MIME type of the audio (e.g., "audio/wav", "audio/mp3").
Returns:
Transcribed text.
"""
...
Purpose: Convert user voice input to text for processing by the agent.
Usage pattern:
async def example():
from axio.transport import STTTransport
transport: STTTransport = ... # your implementation
audio_data = b"..." # raw audio bytes
text = await transport.transcribe(audio_data, media_type="audio/wav")
assert isinstance(text, str)
Implementation status: Protocol-only. No official implementation package ships with Axio. Implement your own or use a third-party package.
EmbeddingTransport¶
Generates vector embeddings from text.
from typing import runtime_checkable, Protocol
@runtime_checkable
class EmbeddingTransport(Protocol):
async def embed(
self,
texts: list[str],
) -> list[list[float]]:
"""Generate embeddings for a list of texts.
Args:
texts: List of text strings to embed.
Returns:
List of embedding vectors (one per input text).
"""
...
Purpose: Generate embeddings for RAG (retrieval-augmented generation), semantic search, or similarity comparisons.
Usage pattern:
async def example():
from axio.transport import EmbeddingTransport
transport: EmbeddingTransport = ... # your implementation
embeddings = await transport.embed(["hello", "world"])
assert len(embeddings) == 2
assert isinstance(embeddings[0], list)
assert isinstance(embeddings[0][0], float)
Implementation status: Protocol-only. No official implementation package ships with Axio. Implement your own or use a third-party package.
Note: All transport protocols follow the same design principles:
Stateless: All state is passed via arguments; no hidden state between calls.
Type-safe: Protocols are
@runtime_checkablefor isinstance checks.Composable: Multiple transports can be combined or wrapped.
To implement a custom transport, see Writing Transports.