PhoenixKitAI.GuardEvents (PhoenixKitAI v0.26.1)

Copy Markdown View Source

What the AI task guard noticed (PhoenixKitAI.GuardEvent) and who is told.

Once per window

An event that is signalled once per window carries a dedupe_key naming the window; the key is unique, the row is inserted with ON CONFLICT DO NOTHING, and only the insert that went through signals:

eventkey
a limit at its warn percentlimit_warning:<task>:<user or ->:<unit>:<period key>
a limit reachedlimit_reached:<task>:<user or ->:<unit>:<period key>
a PhoenixKitAI.Budget refusallimit_reached:budget:<scope>:<user, endpoint or ->:<local date>
a model that no longer fits a taskmodel_unfit:<task>:<endpoint>:<local date>
an off-purpose flagoff_purpose:<task>:<user or ->:<turn_ref or request_uuid>
the watchers told of a person's flagsoff_purpose_signal:<task>:<user or ->:<local date>
an anomaly rule brokenanomaly:<task>:<user or ->:<rule>:<local date>

The period key is the local date (2026-10-22) or month (2026-10) of the site time zone. A key that was inserted (here or, seen as a conflict, on another node) is also kept on this node until its window ends, so a limit that stays past its threshold costs no further write. A forgiving (limit_forgiven), a block (blocked) and a lift (unblocked) have no key: every one is kept. After forgiving, the new count's start is part of the key, so its warning and reached signals can fire again in the same window.

Who is told

The watchers (watchers/2): the setting ai_guard_watchers (a JSON list of user uuids, changed with set_watchers/2; empty — every Owner) and the people the task's watchers callback names for the user, without the user themself. They get a notification of type "ai_guard" (PhoenixKit.Notifications.create_many/2, which leaves out a watcher who switched the type off), in the site's content language. A user's own limit is told to the user in the app (PhoenixKit.Notifications.upsert_inapp/3): at the warn percent the user only, when reached the user and the watchers.

Each signalled event is written to the Activity log as guard.<kind> with the event as its resource (resource_uuid; never target_uuid, which would notify the user a second time). A text names the task, the user by e-mail and the limit with its numbers — never a reason or anything of a call's content. The reason of an off-purpose flag stays in its event row (flags/2 shows it to those allowed); a block's reason and a lift's note stay in the block row.

A signal never fails the call that caused it: a failure is logged as its class only.

Summary

Functions

An anomaly rule of spec broken by the call of request (rule: "source", "calls_per_hour" or "tokens_per_call", with the value seen and the task's limit): an event and the watchers told, once a day per task, person and rule.

A block set or lifted (kind: "blocked" or "unblocked") by actor_uuid: the event, the Activity entry, the watchers (but the actor) told and the person told in the app — none of them with the reason or the note.

A PhoenixKitAI.Budget cap refused a call: a limit_reached event (scope budget:<scope>) and the watchers told, once per local day.

The configured watchers (ai_guard_watchers); empty means every Owner.

The off-purpose flags of a task in the last days (default 30), for the Tasks page as viewer (a scope) may see them: the count per person (most first) and the latest limit flags (default 20). A flag's reason and its journal row are left out (hidden: true) of a private task's flags unless the viewer may manage the guard (PhoenixKitAI.Guard.reasons_visible?/2).

An amount of a unit for display: dollars for money, a count otherwise.

Dollars with at least two decimals: Decimal.new("1.5") is "1.50".

A limit forgiven: the count of scope and period starts again at now. Written to the Activity log with who forgave it; nobody is notified.

A limit in words, e.g. "daily request limit".

A limit of spec reached (see the moduledoc).

A limit of spec at its warn percent (see the moduledoc).

The model of endpoint no longer has what spec needs (missing): an event and the watchers told, once a day per task and endpoint.

The notification type of the guard's signals.

An off-purpose flag of spec for user_uuid (nil for none): one event per turn or call (ref: "turn_ref" or "request_uuid"; the request uuid is linked when its row exists), and on the person's first flag in the task that day, an off_purpose_signal and the watchers told — without the reason. {:ok, event}, or :duplicate when the turn or call was flagged.

The limit events of the current windows of a task (for the Tasks page).

Inserts an event. {:ok, event} when it was written, :duplicate when its dedupe_key was already taken — known on this node (kept until :until, default the end of the local day) or found taken in the database.

An anomaly rule in words.

Sets the watchers (ai_guard_watchers); an empty list means every Owner. A uuid of nobody is left out, with a warning, and the rest are saved; the answer lists who was saved. Needs ai.manage; written to the Activity log with who changed it.

Who is told about spec (nil for no task) and user_uuid: the configured watchers (or every Owner) and the task's own, without the user.

The setting holding the watchers.

Functions

anomaly(spec, request, rule, value, limit, now \\ DateTime.utc_now())

@spec anomaly(
  PhoenixKitAI.TaskSpec.t(),
  PhoenixKitAI.Request.t(),
  String.t(),
  term(),
  term(),
  DateTime.t()
) :: :ok

An anomaly rule of spec broken by the call of request (rule: "source", "calls_per_hour" or "tokens_per_call", with the value seen and the task's limit): an event and the watchers told, once a day per task, person and rule.

block_event(kind, block, actor_uuid)

@spec block_event(String.t(), PhoenixKitAI.GuardBlock.t(), String.t() | nil) :: :ok

A block set or lifted (kind: "blocked" or "unblocked") by actor_uuid: the event, the Activity entry, the watchers (but the actor) told and the person told in the app — none of them with the reason or the note.

budget_refused(scope, endpoint, opts, now \\ DateTime.utc_now())

@spec budget_refused(
  PhoenixKitAI.Budget.scope(),
  PhoenixKitAI.Endpoint.t(),
  keyword(),
  DateTime.t()
) ::
  :ok

A PhoenixKitAI.Budget cap refused a call: a limit_reached event (scope budget:<scope>) and the watchers told, once per local day.

configured_watchers()

@spec configured_watchers() :: [String.t()]

The configured watchers (ai_guard_watchers); empty means every Owner.

flags(spec, viewer, opts \\ [])

@spec flags(PhoenixKitAI.TaskSpec.t(), term(), keyword()) :: %{
  counts: [{String.t() | nil, pos_integer()}],
  flags: [map()]
}

The off-purpose flags of a task in the last days (default 30), for the Tasks page as viewer (a scope) may see them: the count per person (most first) and the latest limit flags (default 20). A flag's reason and its journal row are left out (hidden: true) of a private task's flags unless the viewer may manage the guard (PhoenixKitAI.Guard.reasons_visible?/2).

format_amount(arg1, dollars)

@spec format_amount(
  PhoenixKitAI.TaskLimits.unit(),
  PhoenixKitAI.TaskLimits.amount() | :unmigrated
) ::
  String.t()

An amount of a unit for display: dollars for money, a count otherwise.

format_usd(dollars)

@spec format_usd(Decimal.t()) :: String.t()

Dollars with at least two decimals: Decimal.new("1.5") is "1.50".

limit_forgiven(task_key, user_uuid, scope, period, actor_uuid, now)

@spec limit_forgiven(
  String.t(),
  String.t() | nil,
  PhoenixKitAI.TaskLimits.scope(),
  PhoenixKitAI.TaskLimits.period(),
  String.t() | nil,
  DateTime.t()
) :: {:ok, PhoenixKitAI.GuardEvent.t()} | :duplicate

A limit forgiven: the count of scope and period starts again at now. Written to the Activity log with who forgave it; nobody is notified.

limit_name(atom1, atom2)

A limit in words, e.g. "daily request limit".

limit_reached(spec, user_uuid, status)

@spec limit_reached(
  PhoenixKitAI.TaskSpec.t(),
  String.t() | nil,
  PhoenixKitAI.TaskLimits.status()
) ::
  :ok

A limit of spec reached (see the moduledoc).

limit_warning(spec, user_uuid, status)

@spec limit_warning(
  PhoenixKitAI.TaskSpec.t(),
  String.t() | nil,
  PhoenixKitAI.TaskLimits.status()
) ::
  :ok

A limit of spec at its warn percent (see the moduledoc).

model_unfit(spec, endpoint, missing, now \\ DateTime.utc_now())

The model of endpoint no longer has what spec needs (missing): an event and the watchers told, once a day per task and endpoint.

notice_type()

@spec notice_type() :: String.t()

The notification type of the guard's signals.

off_purpose(spec, user_uuid, reason, ref, now \\ DateTime.utc_now())

@spec off_purpose(
  PhoenixKitAI.TaskSpec.t(),
  String.t() | nil,
  String.t() | nil,
  map(),
  DateTime.t()
) ::
  {:ok, PhoenixKitAI.GuardEvent.t()} | :duplicate

An off-purpose flag of spec for user_uuid (nil for none): one event per turn or call (ref: "turn_ref" or "request_uuid"; the request uuid is linked when its row exists), and on the person's first flag in the task that day, an off_purpose_signal and the watchers told — without the reason. {:ok, event}, or :duplicate when the turn or call was flagged.

reached_users(task_key, now \\ DateTime.utc_now())

@spec reached_users(String.t(), DateTime.t()) :: [PhoenixKitAI.GuardEvent.t()]

The limit events of the current windows of a task (for the Tasks page).

record(attrs)

@spec record(map()) :: {:ok, PhoenixKitAI.GuardEvent.t()} | :duplicate

Inserts an event. {:ok, event} when it was written, :duplicate when its dedupe_key was already taken — known on this node (kept until :until, default the end of the local day) or found taken in the database.

rule_text(rule, limit)

@spec rule_text(String.t(), term()) :: String.t()

An anomaly rule in words.

set_watchers(scope, uuids)

@spec set_watchers(term(), [String.t()]) ::
  {:ok, [String.t()]}
  | {:error, :forbidden | :invalid_uuid | :too_long | term()}

Sets the watchers (ai_guard_watchers); an empty list means every Owner. A uuid of nobody is left out, with a warning, and the rest are saved; the answer lists who was saved. Needs ai.manage; written to the Activity log with who changed it.

watchers(spec, user_uuid)

@spec watchers(PhoenixKitAI.TaskSpec.t() | nil, String.t() | nil) :: [String.t()]

Who is told about spec (nil for no task) and user_uuid: the configured watchers (or every Owner) and the task's own, without the user.

watchers_setting()

@spec watchers_setting() :: String.t()

The setting holding the watchers.