# `Jido.AI.Request`
[🔗](https://github.com/agentjido/jido_ai/blob/v2.4.0/lib/jido_ai/request.ex#L1)

Request tracking for AI agents with per-request isolation and correlation.

This module provides a standardized way to track requests and their results
in AI agents, solving the "single-slot overwrite" problem where concurrent
requests can overwrite each other's results.

## Pattern

Follows the Elixir `Task.async/await` pattern:

    # Async (returns handle for later awaiting)
    {:ok, request} = MyAgent.ask(pid, "What is 2+2?")

    # Await specific request
    {:ok, result} = MyAgent.await(request, timeout: 30_000)

    # Or use sync convenience wrapper
    {:ok, result} = MyAgent.ask_sync(pid, "What is 2+2?")

## Request Struct

The `Request` struct contains:
- `id` - Unique request identifier (UUID)
- `server` - The agent server (pid or via tuple)
- `query` - The original query/prompt
- `status` - Current status (`:pending`, `:completed`, `:failed`)
- `result` - The result when completed
- `error` - Error details if failed
- `inserted_at` - When the request was created

## State Schema

Agents using request tracking should include in their state:

    requests: %{request_id => Handle.t()}

This module provides helpers for managing this map.

## Usage in Agent Macros

```elixir
defmodule MyAgent do
  use Jido.AI.Agent, ...

  # ask/2 now returns {:ok, Handle.t()}
  def ask(pid, query, opts \\ []) do
    Jido.AI.Request.create_and_send(pid, query, opts,
      signal_type: "ai.react.query",
      source: "/ai/react/agent"
    )
  end

  # await/2 waits for specific request
  def await(request, opts \\ []) do
    Jido.AI.Request.await(request, opts)
  end
end
```

# `server`

```elixir
@type server() :: pid() | atom() | {:via, module(), term()}
```

# `status`

```elixir
@type status() :: :pending | :completed | :failed | :timeout
```

# `await`

```elixir
@spec await(Jido.AI.Request.Handle.t(), keyword()) :: {:ok, any()} | {:error, term()}
```

Awaits completion of a specific request.

Similar to `Task.await/2`, this blocks until the request completes,
fails, or times out.

## Options

- `:timeout` - How long to wait (default: 30_000ms)

`Request.await/2` always uses the internal request paths under
`state.requests[request_id]` for status, result, and error tracking.

## Returns

- `{:ok, result}` - Request completed successfully
- `{:error, :timeout}` - Request didn't complete in time
- `{:error, reason}` - Request failed

## Examples

    {:ok, request} = MyAgent.ask(pid, "question")
    {:ok, result} = Request.await(request, timeout: 10_000)

# `await_many`

```elixir
@spec await_many([Jido.AI.Request.Handle.t()], keyword()) :: [
  ok: any(),
  error: term()
]
```

Awaits multiple requests, returning results in the same order.

Similar to `Task.await_many/2`.

## Options

- `:timeout` - How long to wait for all requests (default: 30_000ms)

## Returns

A list of results in the same order as the input requests.
Each element is either `{:ok, result}` or `{:error, reason}`.

# `complete_request`

```elixir
@spec complete_request(struct(), String.t(), any(), keyword()) :: struct()
```

Marks a request as completed with a result.

Called in `on_after_cmd/3` when a request finishes successfully.

## Examples

    def on_after_cmd(agent, {:ai_react_start, %{request_id: req_id}}, directives) do
      snap = strategy_snapshot(agent)
      if snap.done? do
        agent = Request.complete_request(agent, req_id, snap.result)
      end
      {:ok, agent, directives}
    end

# `create_and_send`

```elixir
@spec create_and_send(server(), Jido.AI.Query.t(), keyword()) ::
  {:ok, Jido.AI.Request.Handle.t()} | {:error, term()}
```

Creates a request, sends the signal, and returns the request handle.

This is the primary entry point for agents implementing `ask/2`.

## Options

- `:tool_context` - Additional context merged with agent's tool_context
- `:tools` - ReAct-only request-scoped tool registry override for this run
- `:allowed_tools` - ReAct-only request-scoped allowlist of tool names
- `:request_transformer` - ReAct-only module implementing per-turn request shaping
- `:max_iterations` - ReAct-only request-scoped maximum reasoning iterations
- `:stream_timeout_ms` - ReAct-only request-scoped runtime inactivity timeout.
  `:stream_receive_timeout_ms` is accepted as a compatibility alias.
- `:tool_heartbeat_ms` - ReAct-only request-scoped tool-execution keepalive
  interval in ms (0 = off). Emits a `:keepalive` event while tools run.
- `:req_http_options` - Per-request Req HTTP options forwarded to ReAct runtime
- `:llm_opts` - Per-request ReqLLM generation options forwarded to ReAct runtime
- `:file_id` / `:file_ids` / `:file_reference` / `:file_references` - Uploaded
  file references appended to the user query as ReqLLM content parts when supported by the ReqLLM version
- `:output` - `:raw` to bypass agent-level structured output for this request,
  or a request-scoped structured output config
- `:extra_refs` - Map of additional refs to attach to the user message thread entry
- `:stream_to` - Optional request-scoped runtime event sink, currently `{:pid, pid}`
- `:request_id` - Custom request ID (auto-generated if not provided)

## Signal Options (required)

- `:signal_type` - The signal type to create (e.g., "ai.react.query")
- `:source` - The signal source (e.g., "/ai/react/agent")

## Examples

    {:ok, request} = Request.create_and_send(pid, "What is 2+2?",
      tool_context: %{actor: user},
      signal_type: "ai.react.query",
      source: "/ai/react/agent"
    )

# `ensure_request_id`

```elixir
@spec ensure_request_id(map()) :: {String.t(), map()}
```

Extracts request_id from action params, generating one if not present.

Use this in signal routing or action preparation.

# `fail_request`

```elixir
@spec fail_request(struct(), String.t(), any()) :: struct()
```

Marks a request as failed with an error.

Called when a request encounters an error. Cancellation is recorded through
this function with a `{:cancelled, reason}` error, so completed, failed, and
cancelled requests all drop their stream sink here.

# `get_request`

```elixir
@spec get_request(struct(), String.t()) :: map() | nil
```

Gets a request by ID from agent state.

Returns `nil` if not found.

# `get_result`

```elixir
@spec get_result(struct(), String.t()) ::
  {:ok, any()} | {:error, any()} | {:pending, map()} | nil
```

Gets the result of a request if completed.

Returns `{:ok, result}` if completed, `{:error, error}` if failed,
or `{:pending, request}` if still in progress.

# `init_state`

```elixir
@spec init_state(map(), keyword()) :: map()
```

Initializes request tracking state fields.

Call this when setting up agent state to add the `requests` map.

## Options

- `:max_requests` - Maximum requests to keep (default: 100)

## Examples

    state = Request.init_state(%{})
    # => %{requests: %{}, __request_tracking__: %{max_requests: 100}}

# `schema_fields`

```elixir
@spec schema_fields() :: map()
```

Returns the Zoi schema fields for request tracking.

Include this in your agent macro's schema definition.

## Example

    base_schema_ast = quote do
      Zoi.object(Map.merge(
        %{
          model: Zoi.string() |> Zoi.default("..."),
          # ... other fields
        },
        Jido.AI.Request.schema_fields()
      ))
    end

# `send_and_await`

```elixir
@spec send_and_await(server(), Jido.AI.Query.t(), keyword()) ::
  {:ok, any()} | {:error, term()}
```

Synchronously sends a request and waits for the result.

Convenience wrapper that combines `create_and_send/3` and `await/2`.

## Options

All options from `create_and_send/3` plus:
- `:timeout` - How long to wait (default: 30_000ms)

## Examples

    {:ok, result} = Request.send_and_await(pid, "What is 2+2?",
      timeout: 10_000,
      signal_type: "ai.react.query",
      source: "/ai/react/agent"
    )

# `start_request`

```elixir
@spec start_request(struct(), String.t(), Jido.AI.Query.t(), keyword()) :: struct()
```

Records a new request in agent state.

Called in `on_before_cmd/2` when a request starts.

## Stream sinks

When `:stream_to` is given, the sink is stored on the request record so the
strategy can route runtime events to it. The sink is a live pid, so it is
dropped again as soon as the request reaches a terminal state, and it is never
written to a checkpoint — see `Jido.AI.Checkpoint`.

## Examples

    def on_before_cmd(agent, {:ai_react_start, %{query: query, request_id: req_id}} = action) do
      agent = Request.start_request(agent, req_id, query)
      {:ok, agent, action}
    end

---

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