# Persistence

Persistence is optional for simple use, but built into the architecture from the start.

If you are configuring persistence, you will usually also want to review `configuration.md`. If you are persisting multi-turn chat history, also see `replay.md`.

The library can persist:

- conversations
- turns
- messages
- assistant output
- tool calls
- tool results
- usage
- errors
- metadata
- assets
- provider asset bindings
- conversation memory title/summary fields
- structured conversation notes

## Persistence model

Providers should not know how persistence is stored.

Instead:

- providers work with normalized requests and responses
- runtime coordinates persistence
- persistence providers store normalized conversation and turn data
- asset stores persist core assets and provider bindings

## Current DB-backed persistence

The current DB-backed persistence layer includes support for:

- conversations
- turns
- assets
- provider asset bindings
- tool call lifecycle rows
- embeddings cache rows
- conversation memory fields and note rows

Current schema centers around:

- `ai_conversations`
- `ai_turns`
- `ai_assets`
- `ai_provider_asset_bindings`
- `ai_tool_calls`
- `ai_embeddings`
- `ai_conversation_notes`

Conversation memory is currently stored as:

- `ai_conversations.title`
- `ai_conversations.summary`
- `ai_conversations.memory_metadata`
- `ai_conversation_notes`

Structured payloads are currently stored as JSON strings in `LONGTEXT` columns for flexibility.

## Bootstrap behavior

When DB persistence is enabled, bootstrap wires both:

- `DbPersistenceProvider` for conversations and turns
- `DbAssetStore` for assets and provider bindings

Use `Bootstrap::ensurePersistenceSchema($config)` for explicit schema setup, or set `persistence.auto_schema` to `true` to let bootstrap ensure the configured tables automatically.

If you are upgrading an existing install to a version that includes conversation memory, explicit schema setup is the safest path before enabling memory features in production:

```php
Bootstrap::ensurePersistenceSchema($config);
Bootstrap::registerDefaults($config);
```

## Example config

```php
use boru\ai\Config\Config;
use boru\ai\Bootstrap;

$config = new Config();

$config->persistence(array(
    'enabled' => true,
    'driver' => 'db',
    'auto_schema' => true,
    'persist_tool_calls' => true,
    'tables' => array(
        'conversations' => 'ai_conversations',
        'turns' => 'ai_turns',
        'assets' => 'ai_assets',
        'provider_asset_bindings' => 'ai_provider_asset_bindings',
        'tool_calls' => 'ai_tool_calls',
        'embeddings' => 'ai_embeddings',
        'conversation_notes' => 'ai_conversation_notes',
    ),
));

$config->db(array(
    'use' => true,
    'dbhost' => '127.0.0.1',
    'dbport' => 3306,
    'dbname' => 'ai',
    'dbuser' => 'root',
    'dbpass' => '',
    'dbtype' => 'mysql',
));

Bootstrap::registerDefaults($config);
```

## Table overrides

If you override table names in config, your database schema must match those names.

Supported table keys:

- `conversations`
- `turns`
- `assets`
- `provider_asset_bindings`
- `tool_calls`
- `embeddings`
- `conversation_notes`

## Error handling

The DB-backed persistence layer now uses a checked execution wrapper around `dhDB`.

That means statement-level DB errors are surfaced as runtime exceptions instead of failing silently and causing confusing downstream state.

This is intended as a hardening layer until the underlying DB library is replaced.

## Design note

Persistence is a first-class subsystem, not a provider detail.

That separation keeps provider adapters focused on request/response mapping while the runtime owns orchestration and lifecycle persistence.

## See also

- `configuration.md`
- `conversation-memory.md`
- `replay.md`
- `assets.md`
- `architecture.md`
