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

The assistant settings of one user (spec §5.4, §7) and how they combine with
the module's.

## What is in effect

`effective/1` is the one place the turn loop reads a user's settings: the
user's own value (`PhoenixKitAgent.Schemas.UserSettings`), else the module
default (`PhoenixKitAgent.Config`). A `nil` field means "the default"; `0` is
a real limit. A blank prompt is no prompt. The model (`endpoint_uuid`) is read
when a turn starts, so a change takes effect with the next turn of any chat
(spec §14, В6).

`enabled?/1` is the user's own switch: `false` only when an admin switched the
assistant off for them (it never grants the `agent` permission).

## Who writes what

  * The user — `put_personal_instructions/3` and `get_personal_instructions/2`,
    for **their own** `personal_instructions` only. Like
    `PhoenixKitAgent.Conversations` they act for a live browser session and
    need `impersonated: boolean` in the options; an impersonated session, a
    revoked one, a user without `agent` or one switched off is refused. The
    write replaces that one column, so nothing an admin set can change
    through it.
  * An admin — `admin_update/4`, with the scope of a person who holds
    `agent.manage` (checked again here, so a caller that forgets is refused).
    It writes the switch, the model, the admin's prompt, the limits and the
    notice flags (`UserSettings.admin_changeset/2`) and records who changed
    which fields for whom in the core Activity log — field names only, never
    a prompt text. An Owner's settings are changed by an Owner only, and a
    form opened before someone else's save does not overwrite it
    (`expected:`).

# `effective`

```elixir
@type effective() :: %{
  user_uuid: String.t(),
  enabled?: boolean(),
  endpoint_uuid: String.t() | nil,
  system_prompt: String.t() | nil,
  personal_instructions: String.t() | nil,
  daily_cost_limit_micro_usd: non_neg_integer(),
  monthly_cost_limit_micro_usd: non_neg_integer(),
  daily_turn_limit: non_neg_integer(),
  max_attachment_mb: non_neg_integer(),
  max_attachments_per_message: non_neg_integer(),
  notify_on_limit: boolean(),
  own: [atom()]
}
```

The settings in force for a user.

# `admin_update`

```elixir
@spec admin_update(PhoenixKit.Users.Auth.Scope.t() | nil, term(), map(), keyword()) ::
  {:ok, PhoenixKitAgent.Schemas.UserSettings.t(), [atom()]}
  | {:error,
     :forbidden
     | :not_found
     | :stale
     | {:model_unfit | :model_unverified, [atom()]}
     | Ecto.Changeset.t()}
```

Saves what an admin sets for `target_user_uuid` (`attrs` as
`UserSettings.admin_changeset/2` casts them) on behalf of `actor_scope`,
which must hold `agent.manage` — and be an Owner when the target is one: an
Owner's assistant is not set by the people an Owner manages. The row is
created if the user has none, and locked while it is changed.

`expected: %{field => value}` in `opts` is what the caller's form showed
when it was opened: when the stored row no longer holds those values (an
admin saved in between), nothing is written and the answer is `{:error,
:stale}`, so a stale form never overwrites that change unseen. Fields not
named there do not count.

Returns `{:ok, settings, changed_fields}`; when fields changed, the actor,
the user (as the resource of the entry) and the field names go to the core
Activity log (action `agent.user_settings_updated`) — no values, and no
notification for the user. `{:error, :forbidden}` without the permission
(or for an Owner's settings, to a non-Owner), `{:error, :not_found}` for an
unknown user, `{:error, changeset}` for an invalid value (nothing is
written). A newly chosen model must fit the `agent.chat` AI task
(`PhoenixKitAI.Tasks.check_selection/2`): one known not to take tools is
`{:error, {:model_unfit, missing}}`, one nobody knows takes them `{:error,
{:model_unverified, unclear}}`, and nothing is written; none (the module
default) and the model already saved are not checked.

# `agent_user?`

```elixir
@spec agent_user?(term()) :: boolean()
```

Whether `user_uuid` is one of `agent_users/0`.

# `agent_users`

```elixir
@spec agent_users() :: [PhoenixKit.Users.Auth.User.t()]
```

The people the assistant is for: active users who hold `agent` through a role
(and the Owners, who hold every permission), by e-mail. The admin screen lists
these.

# `effective`

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

The settings in force for `user_uuid`: their own value, else the module
default (see the moduledoc). `own` lists the fields (model, prompt, limits)
the user's row sets itself.

# `effective_many`

```elixir
@spec effective_many([String.t()]) :: %{required(String.t()) =&gt; effective()}
```

`effective/1` for several users with one query and one read of the module
defaults: `%{user_uuid => effective}`.

# `enabled?`

```elixir
@spec enabled?(String.t()) :: boolean()
```

Whether the assistant is not switched off for the user (see the moduledoc).

# `endpoint_uuids`

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

The endpoints the assistant's turns use now: the module default and
every user's own model. It backs the `endpoints` of the `agent.chat` AI
task (`PhoenixKitAgent.ai_tasks/0`).

# `get`

```elixir
@spec get(term()) :: PhoenixKitAgent.Schemas.UserSettings.t() | nil
```

The user's settings row as it is stored, for the admin's form: an unsaved
`%UserSettings{}` when they have none. `nil` for a malformed uuid.

# `get_personal_instructions`

```elixir
@spec get_personal_instructions(
  PhoenixKitAgent.Conversations.session_ref(),
  keyword()
) ::
  {:ok, String.t() | nil} | {:error, PhoenixKitAgent.Conversations.refusal()}
```

The session user's own personal instructions (`nil` when none). Refusals are
those of `PhoenixKitAgent.Conversations`.

# `may_configure?`

```elixir
@spec may_configure?(PhoenixKit.Users.Auth.Scope.t() | nil, term()) :: boolean()
```

Whether `actor_scope` may set the assistant of `target_user_uuid` with
`admin_update/4`: it holds `agent.manage`, and is an Owner if the target is.

# `put_personal_instructions`

```elixir
@spec put_personal_instructions(
  PhoenixKitAgent.Conversations.session_ref(),
  term(),
  keyword()
) ::
  {:ok, String.t() | nil}
  | {:error, PhoenixKitAgent.Conversations.refusal() | Ecto.Changeset.t()}
```

Sets the session user's own personal instructions (a blank text clears them).
They are the user's, ranked below the module's and the admin's prompt (spec
§7.1). Only this one column is written, whatever else the row holds; a text
over 4000 characters, or one that is not text, is `{:error, changeset}` and
nothing is written.

# `watcher_candidates`

```elixir
@spec watcher_candidates() :: [PhoenixKit.Users.Auth.User.t()]
```

The people who may be chosen to be told when someone reaches a limit
(`agent_limit_watchers`, spec §7.4): active users who hold `agent.manage`
through a role, the Owners, and whoever is a watcher now, by e-mail.

---

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