Agentix.Conversation.Config (Agentix v0.5.2)

Copy Markdown View Source

Per-conversation configuration: which model, the system prompt, the (fixed) tool list, and runtime knobs.

The runtime knobs mirror the install/config contract:

  • working_budget — token budget for the assembled context.

  • injection_reserve — token budget reserved for pre-hook injections; over-reserve injection is a loud Agentix.Hook.OverflowError.

  • tool_retention — global default tool-result retention (%{mode: :age | :count, value: pos_integer, never_evict: boolean}; a tool's own :retention overrides it). Reserved: the tool-result reducer is not wired into the assembly path, so this is not currently applied.

  • compaction_window — how many recent turns the sliding-window reducer keeps verbatim. Reserved: the sliding-window reducer is not wired into the assembly path, so this is not currently applied.

  • default_timeout — suspension expiry default, in milliseconds.

  • hook_timeout — per parallel pre-hook deadline, in milliseconds; a hook that exceeds it is shut down and recorded as a crashed (skipped) injector. Sequential hooks run inline and are the author's responsibility to keep bounded.

  • model_call_log — how much of each provider call to record durably:

    • :off (default) — nothing is written.
    • :records — one model_calls row per call, carrying model, usage, latency, outcome, tenant_key and feature, but not the prompt. This is the setting for cost and observability: the rows are small, bounded by call volume, and outlive the conversation's own deletion.
    • :full — the same row plus rendered_context, the entire assembled prompt, for replay and evals. Storage grows with prompt size, so this is the expensive one.

    Rows are written for every outcome — a completed call, a failed one, and a cancelled one — so a turn abandoned mid-stream still leaves evidence that tokens were spent. A failed or cancelled call carries no usage, because usage only exists once the stream assembles.

  • audit?deprecated, kept so conversations persisted before model_call_log existed still revive. true means :full. An explicit model_call_log always wins.

  • retry — transient-failure retry policy for the pre-stream provider call (%{max_attempts: pos_integer, base_ms: pos_integer, max_ms: pos_integer}, or false to disable). Exponential backoff with jitter, honoring retry-after; retried error classes are connection drops, HTTP 429, and HTTP 5xx. A failure after the first streamed chunk is never retried (no duplicate output). false and %{max_attempts: 1} are equivalent — one attempt, no retry.

  • response_format — default output schema applied to every turn that does not pass a per-turn :schema (nil = plain text; a NimbleOptions keyword or a JSON Schema map otherwise). A per-turn schema: false opts out of this default for one turn.

  • hooksAgentix.Hook structs run around each model call.

  • stream_transformer — a (chunk -> chunk) seam applied to each stream chunk (nil is the identity default).

  • api_key — the provider API key for this conversation's model calls: a string, a 0-arity resolver fun (re-evaluated on every model call, so a rotated key takes effect on the next turn), or nil to fall back to ReqLLM's own key resolution (per-provider app config / env). Passed to the provider as a per-request option; never persisted — the Ecto adapter's settings sanitizer drops it, so a conversation revived from persisted settings alone is key-less and the host must re-pass a fresh config if it relies on per-conversation keys.

  • tenant_key — optional owning-tenant key for multi-tenant hosts (nil or a non-empty string). Persisted as an indexed column on the conversation record (plus in settings), stamped on :model_call/:tool telemetry metadata, and the selector for Agentix.Persistence.delete_by_tenant/1. Write-once: it may be set while unset and re-passed with the same value, but a conflicting value makes ensure_started/2 return {:error, :tenant_key_conflict}.

  • feature — optional label for the part of the host application this conversation serves (nil or a non-empty string). Persisted on the conversation record and mirrored onto every model-call record, where it is indexed alongside tenant_key — so "what did this tenant spend, by feature" is one query with no join. Unlike tenant_key it is a label, not an isolation boundary: nothing is selected or deleted by it, so re-passing a different value simply relabels the conversation.

  • notifier / pubsub — wiring resolved at runtime; nil falls back to the application-level configuration.

Like tools, hooks/stream_transformer are functions, not JSON-serializable; they live here and are rebuilt from config on revival (verbatim for the ETS adapter). api_key is dropped on persistence even in its string form — key material never lands in a durable row.

Summary

Functions

Builds a config from attrs. Requires a non-empty :model string. Raises ArgumentError if :model is missing/blank, if working_budget, injection_reserve, default_timeout, or hook_timeout is not a positive integer, if stream_transformer is neither nil nor a 1-arity function, or on unknown keys. String keys naming a known field are accepted (so a config can be rebuilt from a persistence adapter that round-trips settings as JSON).

Types

t()

@type t() :: %Agentix.Conversation.Config{
  api_key: String.t() | (-> String.t()) | nil,
  audit?: boolean(),
  compaction_window: pos_integer(),
  default_timeout: pos_integer(),
  feature: String.t() | nil,
  hook_timeout: pos_integer(),
  hooks: [Agentix.Hook.t()],
  injection_reserve: pos_integer(),
  model: String.t(),
  model_call_log: :off | :records | :full,
  notifier: module() | nil,
  pubsub: atom() | nil,
  response_format: keyword() | map() | nil,
  retry:
    %{
      max_attempts: pos_integer(),
      base_ms: pos_integer(),
      max_ms: pos_integer()
    }
    | false,
  stream_transformer: (term() -> term()) | nil,
  system_prompt: String.t() | nil,
  tenant_key: String.t() | nil,
  tool_retention: %{
    mode: :age | :count,
    value: pos_integer(),
    never_evict: boolean()
  },
  tools: list(),
  working_budget: pos_integer()
}

Functions

new(attrs)

@spec new(keyword() | map()) :: t()

Builds a config from attrs. Requires a non-empty :model string. Raises ArgumentError if :model is missing/blank, if working_budget, injection_reserve, default_timeout, or hook_timeout is not a positive integer, if stream_transformer is neither nil nor a 1-arity function, or on unknown keys. String keys naming a known field are accepted (so a config can be rebuilt from a persistence adapter that round-trips settings as JSON).