# `ReqManagedAgents.Providers.Local`
[🔗](https://github.com/cash-mckeeman/req_managed_agents/blob/v0.13.0/req_managed_agents/lib/req_managed_agents/providers/local.ex#L1)

`ReqManagedAgents.Provider` that runs the agent loop **in-process** — `:request_response`
mode, one model call per `poll_turn/2` through a pluggable `chat_fun`.

The chat wire contract is neutral, OpenAI-chat-completions-shaped, plain string-keyed
maps: `chat_fun.(%{model:, messages:, tools:}) :: {:ok, response} | {:error, reason}`.
The default chat_fun adapts it to `ReqLLM.generate_text/3` (optional `req_llm` dep);
pointing a chat_fun at any OpenAI-compatible endpoint is a bare `Req.post` — e.g. a
mimir lane (`/v1/chat/completions` + granted key via `model_config`) for hard
data-plane budget enforcement.

The chat_fun's response must be OpenAI-chat-completions shaped, string-keyed:
`%{"choices" => [%{"message" => message, "finish_reason" => reason} | _], "usage" => usage}`
where `message` is `%{"role" => "assistant", "content" => text_or_nil}` plus an optional
`"tool_calls" => [%{"id" => id, "type" => "function", "function" => %{"name" => n, "arguments" => json}}]`,
and `usage` is `%{"prompt_tokens" => n, "completion_tokens" => n}` (or absent).

Errors: return `{:error, reason}`. Transient reasons are retried with exponential
backoff: `%{status: 408}` / `%{status: 500..}` (HTTP) and `%{reason: atom}` /
`%{cause: atom}` where the atom is one of `:timeout | :closed | :econnrefused |
:econnreset | :connect_timeout`. Wrap transport exceptions down to their atom —
`%{reason: %Req.TransportError{...}}` will NOT match; return `%{reason: err.reason}`.

Open opts: `:spec` (the `t:ReqManagedAgents.Provider.spec/0`, also the `provision/2`
identity handle), `:model_config` (canonical keys `:model`, `:api_key`, `:base_url`,
`:metadata`; defaults from `spec.model_config`), `:chat_fun`, `:max_turns`,
`:session_id`, retry tuning (`:max_chat_retries`, `:retry_backoff_ms`, `:sleep_fun`).

Events are synthesized under the `local.*` namespace (`local.model_response`, and the
guard events added by the loop guards) so `SessionResult.events` stays raw-preserving.

# `t`

```elixir
@type t() :: %ReqManagedAgents.Providers.Local{
  chat_fun: (map() -&gt; {:ok, map()} | {:error, term()}),
  error_counts: %{optional(String.t()) =&gt; non_neg_integer()},
  history: [map()],
  max_turns: pos_integer(),
  model: term(),
  polls: non_neg_integer(),
  resume: boolean(),
  seen: MapSet.t(),
  session_id: String.t(),
  terminal_tool: String.t() | nil,
  tools: [map()]
}
```

---

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