# `ALLM.ChatResult`
[🔗](https://github.com/cykod/ALLM/blob/v0.3.0/lib/allm/chat_result.ex#L1)

The result of a full chat loop. See spec §5.9.

Layer A — pure serializable data. Carries the final `:thread`, the
`:final_response`, a list of per-turn `:steps`, and a `:halted_reason`
atom indicating why the loop stopped.

`halted_reason: :completed` means the loop terminated normally. Every
other atom represents a halt — including `:max_turns` (spec §20's
`:max_turns_exceeded`), `:halt_when`, `:ask_user`, `:tool_error`,
`:cancelled`, and any custom atom a tool returned via `{:halt, atom, _}`.
Use `halted?/1` to check.

# `halted_reason`

```elixir
@type halted_reason() ::
  :completed
  | :max_turns
  | :halt_when
  | :ask_user
  | :tool_error
  | :cancelled
  | atom()
```

Reason the chat loop halted. `:completed` is the normal-exit atom; every
other value signals early termination. The type is open (`atom()` tail)
so custom tool-halt reasons surface here without touching the enum.

# `t`

```elixir
@type t() :: %ALLM.ChatResult{
  final_response: ALLM.Response.t(),
  halted_reason: halted_reason(),
  metadata: map(),
  pending_question: String.t() | nil,
  pending_tool_call_id: String.t() | nil,
  steps: [ALLM.StepResult.t()],
  thread: ALLM.Thread.t()
}
```

# `halted?`

```elixir
@spec halted?(t()) :: boolean()
```

Return `true` when `halted_reason` is anything other than `:completed`.

## Examples

    iex> ALLM.ChatResult.halted?(ALLM.ChatResult.new(halted_reason: :completed))
    false

    iex> ALLM.ChatResult.halted?(ALLM.ChatResult.new(halted_reason: :max_turns))
    true

# `new`

```elixir
@spec new(keyword()) :: t()
```

Build a `%ChatResult{}` from keyword opts.

Every field is optional. Unknown keys raise `ArgumentError` via `struct!/2`.

## Examples

    iex> cr = ALLM.ChatResult.new(halted_reason: :completed)
    iex> cr.halted_reason
    :completed
    iex> cr.steps
    []

---

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