# Architecture

`boru/ai` is designed as a provider-agnostic AI runtime library.

Its architecture is organized around a few stable subsystem boundaries.

If you are looking for practical usage first, start with `getting-started.md` or `chat.md`. This document focuses on boundaries and design intent.

## Core principles

These design commitments should remain stable as the library evolves:

1. The runtime owns orchestration.
2. Persistence is a first-class subsystem.
3. Tool execution is runtime-owned, not provider-owned.
4. Providers are adapters, not the core model.
5. Normalized `boru/ai` schema objects are the source of truth.
6. Provider signals and runtime-native events are distinct layers.
7. Core assets are provider-agnostic and provider-specific file identifiers live in separate bindings.
8. Configuration should be explicit, modular, and optional.
9. Native provider implementations are preferred over wrapping legacy internals.

## Main subsystem boundaries

### Runtime

Owns the high-level orchestration flow.

Responsibilities:

- resolve the AI provider
- load or initialize conversation state
- execute turns
- own the tool call loop
- coordinate persistence hooks
- emit normalized runtime lifecycle events
- remain provider agnostic

The runtime is the main coordinator of the library.

### Provider

Owns AI vendor integrations.

Responsibilities:

- convert normalized requests into vendor requests
- convert vendor responses into normalized responses
- convert vendor stream payloads into provider-layer signals or normalized runtime-facing events
- surface tool calls to the runtime
- continue provider execution after the runtime resolves tool calls

Providers must not own persistence or application behavior.

### Persistence

Owns durable storage of conversations, turns, assets, and provider bindings.

Responsibilities:

- create, load, and save conversations
- append turns
- persist core assets and provider-specific asset bindings
- store normalized messages, responses, usage, tool activity, and metadata
- support multiple persistence providers

For practical persistence details, see `persistence.md`.

### Conversation

Owns conversation and turn domain objects.

Examples:

- `Conversation`
- `Turn`
- `ConversationReference`

### Schema

Owns normalized execution-time request and response objects.

Examples:

- `Request`
- `Response`
- `Message`
- `ContentPart`
- `ToolCall`
- `ToolResult`

### Stream

Owns normalized event and listener primitives.

Responsibilities:

- text, tool, and turn event names
- event dispatching
- listener contracts
- buffering helpers
- reusable built-in listeners

For practical event consumption, see `chat.md`.

### Asset

Owns provider-agnostic core assets plus provider-specific asset bindings.

A single core asset may have many provider-specific bindings.

For practical asset usage, see `assets.md`.

## Tool loop boundary

Tool execution is a first-class runtime concern.

The runtime should:

1. send a normalized request through a provider
2. receive a normalized response that may contain tool calls
3. execute those tool calls through the runtime tool system
4. build normalized tool results
5. hand those tool results back to the provider
6. let the provider continue generation
7. persist the turn lifecycle as needed

This keeps tool behavior consistent across providers.

For practical tool usage, see `tools.md`.

## Event model

A key design distinction is the separation between provider signals and runtime-native lifecycle events.

### Provider layer

Providers surface low-level facts about what the provider observed.

### Runtime layer

The runtime emits public lifecycle events such as:

- `text.delta`
- `text.completed`
- `tool.execution.started`
- `tool.execution.completed`
- `tool.execution.failed`
- `turn.started`
- `turn.completed`
- `turn.failed`
- `turn.error`

This keeps public semantics centered on library/runtime behavior rather than provider quirks.

## Chat application boundary

`boru/ai` should support chat applications well, but it should not become application-specific chat orchestration.

Good library-level responsibilities include:

- conversation continuity primitives
- normalized stream events
- reusable listeners and collectors
- fluent callback helpers for common chat lifecycle hooks

Application-specific responsibilities should stay outside the library, such as:

- UI event contracts
- domain workflows
- business rules
- app-specific persistence side effects
- tool exposure policy

## See also

- `getting-started.md`
- `chat.md`
- `tools.md`
- `assets.md`
- `persistence.md`
