# `PhoenixKitAgent.Migrations`
[🔗](https://github.com/BeamLabEU/phoenix_kit_agent/blob/v0.1.0/lib/phoenix_kit_agent/migrations.ex#L1)

Module-owned versioned migrations for `phoenix_kit_agent` — the protocol core's
`mix phoenix_kit.update` discovers through `PhoenixKitAgent.migration_module/0`
(`current_version/0`, `migrated_version_runtime/1`, `up/1`, `down/1`). Same
shape as `PhoenixKitDashboards.Migrations`: two version readers, `up/1`
re-reading the version before it changes anything, the SQL kept as data
(`up_statements/2`, `down_statements/2`).

## Tables (V1)

  * `phoenix_kit_agent_conversations` — a user's chats; the chat list reads
    them by `(user_uuid, status, last_message_at DESC)`.
  * `phoenix_kit_agent_messages` — every message of a chat, the tool calls
    and their outcomes included; `ai_request_uuid` links the
    `phoenix_kit_ai_requests` row of a model call.
  * `phoenix_kit_agent_attachments` — files attached to a message; the bytes
    live in Storage (`file_uuid`).
  * `phoenix_kit_agent_user_settings` — one row per user, `NULL` meaning "the
    module default"; also the per-user "turn in flight" lock.
  * `phoenix_kit_agent_limit_events` — one limit notice per user, kind and
    period.

## V2 — the confirmation of a write (spec §5.2, I6)

  * `phoenix_kit_agent_messages.tool_outcome` also takes `expired`,
    `changed` and `failed` (the outcomes of a confirmation card, kept on its
    `notice`);
  * `assistant_message_uuid` — on a `tool` message, the `assistant` message
    whose tool call it answers (the arguments a confirmation is redeemed
    with). No foreign key: both rows are in the same chat and go with it;
  * `confirmation_uuid` — the confirmation core holds the write in
    (`PhoenixKit.API.Confirm`), on the `tool` message that asks for it and
    on the `notice` that says how it ended; partial index
    `phoenix_kit_agent_messages_confirmation_uuid_index`, through which the
    chat panel finds the cards still waiting. No foreign key: core deletes
    its rows on its own schedule.

`down/1` to V1 drops the two columns and the index, and clears an outcome
V1 cannot hold (the notice stays, without it).

Deleting a user deletes their rows of every table (`ON DELETE CASCADE`); a
deleted `phoenix_kit_ai_requests` row, AI endpoint or Storage file leaves the
link `NULL` and never blocks the delete. Money is in micro-dollars
(`*_micro_usd`, `$1 = 1_000_000`); the AI journal keeps dollars
(`phoenix_kit_ai_requests.cost`), turned into micro-dollars by
`PhoenixKitAgent.Config.cost_to_micro/1`.

## Version marker

`COMMENT ON TABLE phoenix_kit_agent_conversations IS 'pk_agent_schema:<N>'`.
No table, or one without a `pk_agent_schema:` comment, reads as version 0.

## Idempotency and rollback

Every statement guards itself (`IF NOT EXISTS` / `IF EXISTS`), so the chain
can be re-run. These tables belong to this module alone: `down/1` to version
0 **drops them with every chat, message, attachment link and per-user
setting in them** (the Storage files of the attachments stay). Back up the
database first.

## Ordering

The foreign keys reach core's `phoenix_kit_users`, `phoenix_kit_files`,
`phoenix_kit_ai_endpoints` and `phoenix_kit_ai_requests`, so this chain runs
after core's (`mix phoenix_kit.update` generates the module migrations after
core's own).

# `current_version`

```elixir
@spec current_version() :: pos_integer()
```

The version this code expects the schema to be at.

# `down`

```elixir
@spec down(keyword() | map()) :: :ok
```

Rolls back to `opts[:version]` (default `0`). Migration context only. To 0 it
drops every table of the module — see the moduledoc.

# `down_statements`

```elixir
@spec down_statements(String.t(), non_neg_integer()) :: [String.t()]
```

The SQL `down/1` runs, as data. To `0` it drops every table of the module;
to a version still in the chain it undoes every later version (each
statement guards itself) and re-stamps the marker.

# `migrated_version`

```elixir
@spec migrated_version(keyword() | map()) :: non_neg_integer()
```

The installed version, read inside a migration through `Ecto.Migration`'s
`repo()`. No rescue: a version that cannot be read aborts the migration.

# `migrated_version_runtime`

```elixir
@spec migrated_version_runtime(keyword() | map()) :: non_neg_integer()
```

The installed version, read outside a migration (what `mix phoenix_kit.update`
calls) through PhoenixKit's configured repo. An invalid prefix raises; a
database that cannot be reached reads as `0`, which only costs a redundant
migration file because `up/1` re-reads the version before it changes
anything.

# `up`

```elixir
@spec up(keyword() | map()) :: :ok
```

Applies the chain up to `opts[:version]` (default `current_version/0`).
Migration context only. Re-reads the installed version first: a database
already at (or past) the target is left alone.

# `up_statements`

```elixir
@spec up_statements(String.t(), non_neg_integer()) :: [String.t()]
```

The SQL `up/1` runs, as data. `target` is how far to go (default
`current_version/0`); `0` is nothing. Every version from V1 on is in it:
each statement guards itself, so the versions already applied change
nothing.

# `version_table`

```elixir
@spec version_table() :: String.t()
```

The table carrying the `pk_agent_schema:<N>` marker.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
