# `ClaudeWrapper.Structured`
[🔗](https://github.com/genagent/claude_wrapper_ex/blob/main/lib/claude_wrapper/structured.ex#L1)

Run a Claude call as a typed function.

A `Structured` task turns **structured input** into a rendered prompt,
constrains the model's output to a **JSON Schema**, and parses the
validated object into a **domain value** -- returning the raw
`t:ClaudeWrapper.Result.t/0` alongside as an audit log. The model call is
the only stochastic step; prompt construction, output shape, and parsing
are deterministic and typed -- a deterministic envelope around a
stochastic core.

A task module implements:

  * `render/1` -- input -> a `t:ClaudeWrapper.Prompt.t/0` or a string
  * `schema/0` -- the JSON Schema the output must conform to (claude's
    `--json-schema`); the validated object arrives as the result's
    `structured_output`
  * `parse/1` -- *optional*; map the validated object into a domain
    value. Defaults to returning the object unchanged.

`run/3` wires them together.

## Example

    defmodule ExtractName do
      @behaviour ClaudeWrapper.Structured

      @impl true
      def render(text), do: "Extract the person's name from: " <> text

      @impl true
      def schema do
        %{
          "type" => "object",
          "properties" => %{"name" => %{"type" => "string"}},
          "required" => ["name"]
        }
      end
    end

    {:ok, %{"name" => name}, %ClaudeWrapper.Result{}} =
      ClaudeWrapper.Structured.run(ExtractName, "Hi, I'm Ada Lovelace.")

## Parsing into a domain value

An optional `parse/1` maps the validated object into whatever shape the
caller wants -- a struct, an Ecto schema, a normalized tuple. The model
still only returns the schema-validated object; `parse/1` is the
deterministic last step.

    defmodule ExtractPerson do
      @behaviour ClaudeWrapper.Structured

      @impl true
      def render(text), do: "Extract the person's full name from: " <> text

      @impl true
      def schema, do: ExtractName.schema()

      @impl true
      def parse(%{"name" => name}), do: {:ok, %Person{name: name}}
      def parse(other), do: {:error, {:unexpected_shape, other}}
    end

    {:ok, %Person{name: "Ada Lovelace"}, %ClaudeWrapper.Result{}} =
      ClaudeWrapper.Structured.run(ExtractPerson, "Hi, I'm Ada Lovelace.")

## Reasoning room

A hard schema gives the model no space to think before answering. If a
task needs reasoning, add a field for it to the schema (e.g. a
`"reasoning"` string beside the `"answer"` object) rather than dropping
the schema -- you keep a guaranteed-parseable object *and* thinking room
in one deterministic call.

# `parse`
*optional* 

```elixir
@callback parse(structured_output :: map() | list()) :: {:ok, term()} | {:error, term()}
```

Map the schema-validated object into a domain value. Optional; the
default returns the object unchanged.

    def parse(%{"name" => name}), do: {:ok, %Person{name: name}}

# `render`

```elixir
@callback render(input :: term()) :: ClaudeWrapper.Prompt.t() | String.t()
```

Turn the task input into a prompt (a `Prompt` struct or a string).

# `schema`

```elixir
@callback schema() :: map()
```

The JSON Schema the model output must conform to.

# `run`

```elixir
@spec run(module(), term(), keyword()) ::
  {:ok, term(), ClaudeWrapper.Result.t()} | {:error, term()}
```

Run `module` over `input`.

Returns `{:ok, parsed, result}` -- the parsed domain value plus the raw
`t:ClaudeWrapper.Result.t/0` (the audit log) -- or `{:error, reason}`.

`opts` are forwarded to `ClaudeWrapper.query/2` (e.g. `:model`,
`:working_dir`, `:timeout`); `:json_schema` is set from `schema/0`.

Error reasons:

  * a `ClaudeWrapper.Error` -- `render/1` returned a non-prompt
    (`:invalid_render`), the prompt failed to render, the CLI call
    failed, or the result carried no `structured_output`
    (`:no_structured_output`)
  * a `Jason` encode error -- `schema/0` was not JSON-encodable
  * whatever `parse/1` returned on `{:error, _}`

---

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