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

The module settings (`phoenix_kit_settings`, keys `agent_*`) and their
defaults. A per-user value (`PhoenixKitAgent.Schemas.UserSettings`) wins over
these; these win over the built-in defaults below.

| Key | Default |
|---|---|
| `agent_enabled` | `false` (see `PhoenixKitAgent.enabled?/0`) |
| `agent_default_endpoint_uuid` | none — the AI endpoint the assistant uses |
| `agent_vision_endpoint_uuid` | none — describes images for a model without image input |
| `agent_system_prompt` | none — the module's system prompt (JSON `{"text": …}`) |
| `agent_daily_cost_limit_micro_usd` | `1_000_000` ($1) |
| `agent_monthly_cost_limit_micro_usd` | `10_000_000` ($10) |
| `agent_daily_turn_limit` | `200` |
| `agent_max_attachment_mb` | `20` |
| `agent_max_attachments_per_message` | `5` |
| `agent_limit_watchers` | none — JSON `{"uuids": […]}` of user uuids; none = every Owner |
| `agent_max_tool_rounds` | `8` |
| `agent_turn_deadline_s` | `300` |
| `agent_history_chars` | `24_000` |
| `agent_tool_result_chars` | `12_000` |
| `agent_capture_ai_content` | `false` |

Money is in micro-dollars ($1 = 1_000_000); the AI journal keeps dollars
(`phoenix_kit_ai_requests.cost`), read through `cost_to_micro/1`. A stored
value that is not a whole number, or is outside its range (below), reads as
the default. A limit of `0` is a real limit (nothing allowed); a technical
value must be positive.

Core keeps a string setting to 1000 characters, so the two settings that can
be longer — the module prompt and the watchers — are JSON settings
(`value_json`), written by `put_system_prompt/2` and `put_limit_watchers/2`.
The two models are written by `put_default_endpoint/2` and
`put_vision_endpoint/2`, which take only an AI endpoint that chats and fits
its AI task (`agent.chat`, `agent.attachment`).

Every writer records the change with who made it (`actor_uuid:` in `opts`)
in the core Activity log: through the settings history (`setting.changed`,
with the values before and after) — except the module prompt, whose text
must not be kept there (nor in the query log that history's write goes
to): it is written without that history, and its change is recorded as
`agent.settings_updated` with the field name only.

`agent_capture_ai_content` is on only when it reads exactly `"true"`: then the
bodies of the assistant's model calls are written to the AI request journal,
where every holder of the `ai` permission reads them. Only an Owner may switch
it: `put_capture_ai_content/2` does not check that, its caller (the Settings
page, `PhoenixKitAgent.Web.SettingsLive`) does.

# `endpoint_refusal`

```elixir
@type endpoint_refusal() ::
  :invalid_endpoint | {:model_unfit | :model_unverified, [atom()]}
```

Why `put_default_endpoint/2` or `put_vision_endpoint/2` wrote nothing.

# `capture_ai_content?`

```elixir
@spec capture_ai_content?() :: boolean()
```

Whether the bodies of the assistant's model calls go to the AI journal.

# `cost_to_micro`

```elixir
@spec cost_to_micro(Decimal.t() | nil) :: non_neg_integer() | nil
```

A cost in dollars as the AI journal keeps it (`PhoenixKitAI.Request.cost`,
a `Decimal`, finer than a micro-dollar at times) as whole micro-dollars,
rounded half up: `$0.0000025` is `3`. `nil` (no cost known) stays `nil`.
A sum is rounded once, after it is added up, never row by row.

# `default_endpoint_uuid`

```elixir
@spec default_endpoint_uuid() :: String.t() | nil
```

The AI endpoint the assistant uses by default, or `nil`.

# `defaults_changeset`

```elixir
@spec defaults_changeset(map()) :: Ecto.Changeset.t()
```

Validates the defaults form; see `save_defaults/2`.

# `defaults_form_values`

```elixir
@spec defaults_form_values() :: %{required(String.t()) =&gt; String.t()}
```

The current defaults as the form shows them: string values, money in dollars.

# `history_chars`

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

How many characters of earlier messages go into the model's context.

# `limit_defaults`

```elixir
@spec limit_defaults() :: %{required(atom()) =&gt; non_neg_integer()}
```

The module's default per-user limits.

# `limit_watchers`

```elixir
@spec limit_watchers() :: [String.t()]
```

The users told when someone hits a limit (`agent_limit_watchers`). Empty means
every Owner; who those are is decided where the notice is sent.

# `max_tool_rounds`

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

Most model ↔ tool rounds in one turn.

# `micro_to_usd`

```elixir
@spec micro_to_usd(non_neg_integer()) :: String.t()
```

Micro-dollars as dollars for a form field: `1_500_000` is `"1.5"`.

# `put_capture_ai_content`

```elixir
@spec put_capture_ai_content(boolean(), keyword()) ::
  {:ok, term()} | {:error, Ecto.Changeset.t()}
```

Switches `agent_capture_ai_content`. `opts` go to `PhoenixKit.Settings`
(`actor_uuid:`, `source:`). The caller checks that the person is an Owner.

# `put_default_endpoint`

```elixir
@spec put_default_endpoint(String.t() | nil, keyword()) ::
  {:ok, term()} | {:error, endpoint_refusal() | Ecto.Changeset.t()}
```

Sets the AI endpoint the assistant uses by default
(`agent_default_endpoint_uuid`), or none (`nil`). Only an existing endpoint
that chats is taken; anything else is `{:error, :invalid_endpoint}` and
nothing is written. A newly chosen endpoint must fit the `agent.chat` AI
task (`PhoenixKitAI.Tasks.check_selection/2`): one whose model is known not
to take tools is `{:error, {:model_unfit, missing}}`, one nobody knows
takes them `{:error, {:model_unverified, unclear}}`, and nothing is
written; the endpoint already set is not checked again. `opts` go to
`PhoenixKit.Settings` (`actor_uuid:`, `source:`). The caller checks
`agent.manage`.

# `put_limit_watchers`

```elixir
@spec put_limit_watchers([String.t()], keyword()) ::
  {:ok, term()} | {:error, :invalid_uuid | Ecto.Changeset.t()}
```

Sets who is told when someone hits a limit (`agent_limit_watchers`): user
uuids, repeats dropped; an empty list means every Owner. Anything that is
not a uuid is `{:error, :invalid_uuid}` and nothing is written. The caller
checks `agent.manage`.

# `put_system_prompt`

```elixir
@spec put_system_prompt(String.t() | nil, keyword()) ::
  {:ok, term()} | {:error, Ecto.Changeset.t()}
```

Sets the module's system prompt (`agent_system_prompt`); a blank one means
none. NUL characters are cut (the database refuses them); a prompt over
16000 characters is `{:error, changeset}` and nothing is written —
it goes into every request. A change is recorded in the Activity log as
`agent.settings_updated` by `actor_uuid:` (in `opts`), with the field name
and never the text (see the moduledoc). The caller checks `agent.manage`.

# `put_vision_endpoint`

```elixir
@spec put_vision_endpoint(String.t() | nil, keyword()) ::
  {:ok, term()} | {:error, endpoint_refusal() | Ecto.Changeset.t()}
```

Sets the AI endpoint that describes pictures for a model that does not see
them (`agent_vision_endpoint_uuid`), or none (`nil`); as
`put_default_endpoint/2`, against the `agent.attachment` AI task (its model
must take pictures).

# `remove_limit_watcher`

```elixir
@spec remove_limit_watcher(String.t()) :: {:ok, term()} | {:error, Ecto.Changeset.t()}
```

Takes `user_uuid` out of `agent_limit_watchers`, under the setting's row lock
(a concurrent change of the list is not lost). `{:ok, :unchanged}` when it was
not in the list.

# `save_defaults`

```elixir
@spec save_defaults(map(), keyword()) ::
  {:ok, [String.t()]} | {:error, Ecto.Changeset.t() | :not_saved}
```

Saves the defaults form: dollars become micro-dollars; every value is checked
against its range and nothing is written unless all are valid. Only values
that differ from what is in effect are written, so an untouched built-in
default stays unstored (and follows a later change of the default). They are
written in one transaction (`PhoenixKit.Settings.update_settings_batch/2`):
a write the database refuses leaves every setting as it was and is
`{:error, :not_saved}`. `opts` go to `PhoenixKit.Settings` (`actor_uuid:`,
`source:`) so the change is in the settings history with who made it.

# `system_prompt`

```elixir
@spec system_prompt() :: String.t() | nil
```

The module's system prompt, or `nil`.

# `tool_result_chars`

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

How many characters of one tool result go to the model (and are kept).

# `turn_deadline_s`

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

Deadline of a whole turn, in seconds.

# `usd_to_micro`

```elixir
@spec usd_to_micro(term()) :: {:ok, non_neg_integer()} | {:error, atom()}
```

Dollars typed by a person (`"2,5"`, `"0.05"`) as micro-dollars: `{:ok,
integer}`, `{:error, :fraction}` when finer than a millionth of a dollar,
`{:error, reason}` for anything that is not a non-negative amount.

# `vision_endpoint_uuid`

```elixir
@spec vision_endpoint_uuid() :: String.t() | nil
```

The AI endpoint that describes images for a model without image input, or `nil`.

---

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