Context & Messages¶
The context store holds conversation history. Messages contain typed content blocks that represent text, images, tool calls, and tool results.
Content blocks¶
classDiagram
class ContentBlock {
<<base>>
}
class TextBlock {
+text: str
}
class ImageBlock {
+media_type: str
+data: bytes
}
class ToolUseBlock {
+id: ToolCallID
+name: ToolName
+input: dict
}
class ToolResultBlock {
+tool_use_id: ToolCallID
+content: str | list
+is_error: bool
}
ContentBlock <|-- TextBlock
ContentBlock <|-- ImageBlock
ContentBlock <|-- ToolUseBlock
ContentBlock <|-- ToolResultBlock
All content blocks are frozen dataclasses:
TextBlock(text)Plain text content.
ImageBlock(media_type, data)Binary image data with MIME type (jpeg, png, gif, webp).
ToolUseBlock(id, name, input)A tool call issued by the model, with its ID, tool name, and input dict.
ToolResultBlock(tool_use_id, content, is_error)The result of a tool call.
contentcan be a string or a list ofTextBlock/ImageBlockvalues.
Serialization¶
Every block can be serialized to and from a dict:
from axio.blocks import to_dict, from_dict
d = to_dict(TextBlock(text="hello")) # {"type": "text", "text": "hello"}
block = from_dict(d) # TextBlock(text="hello")
Message¶
A Message pairs a role with a list of content blocks:
@dataclass(slots=True)
class Message:
role: Literal["user", "assistant"]
content: list[ContentBlock]
User messages typically contain TextBlock values. Assistant messages may
contain TextBlock and ToolUseBlock values. Tool results go into a
separate user message with ToolResultBlock values.
ContextStore¶
ContextStore is an abstract base class — implement it to store conversations
anywhere.
class ContextStore(ABC):
@property
@abstractmethod
def session_id(self) -> str: ...
@abstractmethod
async def append(self, message: Message) -> None: ...
@abstractmethod
async def get_history(self) -> list[Message]: ...
@abstractmethod
async def clear(self) -> None: ...
@abstractmethod
async def fork(self) -> ContextStore: ...
async def set_context_tokens(
self, input_tokens: int, output_tokens: int,
) -> None: ...
async def get_context_tokens(self) -> tuple[int, int]: ...
Built-in implementations¶
MemoryContextStoreIn-memory list of messages.
fork()returns a deep copy. No persistence.
The axio-tui package includes a SQLite-backed context store for
persistence across sessions.
Extension point¶
Implement ContextStore to use any backend:
Redis for shared state across processes
PostgreSQL for durable, queryable history
A vector database for retrieval-augmented context
Factory methods¶
ContextStore provides two class-method factories:
# Create from existing messages
ctx = await MemoryContextStore.from_history(messages)
# Clone another context store
ctx2 = await MemoryContextStore.from_context(ctx)
Context compaction¶
Long conversations can exceed the model’s context window. The
compact_context() function summarizes older messages while keeping recent
ones verbatim:
async def compact_context(
context: ContextStore,
transport: CompletionTransport,
*,
max_messages: int = 20,
keep_recent: int = 6,
system_prompt: str | None = None,
) -> list[Message] | None:
It uses a separate one-shot agent call to generate the summary. The function
returns the compacted message list if the history exceeded max_messages, or
None if no compaction was needed.
The helper _find_safe_boundary() ensures that ToolUseBlock /
ToolResultBlock pairs are never split across the boundary.