# `Sagents.State`

Agent state structure for managing agent execution context.

The state holds the complete context for an agent execution including:
- Message history (list of `LangChain.Message` structs)
- TODO list
- Metadata

**Note**: Files are managed separately by `FileSystemServer` and are not part of
the agent's internal state. The FileSystemServer provides persistent storage with
ETS and optional backend persistence (disk, database, S3, etc.).

## agent_id Management (Automatic)

The `agent_id` field is a **runtime identifier** used for process registration and
coordination. You don't need to set it when creating states—the library
automatically injects it when you call `Sagents.Agent.execute/2`, `Sagents.Agent.resume/3`, or
start an `AgentServer`.

### Why it's automatic

The `agent_id` flows from the Agent struct (which is configuration) to the State
(which is data). Making you synchronize them manually could be error-prone.

### For middleware developers

When middleware receives state in hooks (`before_model`, `after_model`), the
`agent_id` will already be set. If you create new state structs in middleware,
copy the `agent_id` from the incoming state:

    def after_model(state, config) do
      updated_state = State.new!(%{
        agent_id: state.agent_id,  # Copy from incoming state
        messages: new_messages
      })
      {:ok, updated_state}
    end

## State Merging

State merging follows specific rules:

- **messages**: Appends new messages to existing list
- **todos**: Replaces with new todos (merge handled by TodoList middleware)
- **metadata**: Deep merges metadata maps
- **agent_id**: Uses right if present, otherwise left (runtime identifier, not data)

# `t`

```elixir
@type t() :: %Sagents.State{
  agent_id: term(),
  interrupt_data: term(),
  messages: term(),
  metadata: term(),
  todos: term()
}
```

# `add_message`

Add a message to the state.

Message must be a `LangChain.Message` struct.

# `add_messages`

Add multiple messages to the state.

Messages must be `LangChain.Message` structs.

# `clean_stale_interrupts`

```elixir
@spec clean_stale_interrupts(t()) :: t()
```

Replace any stale interrupt placeholder tool results with error messages.

Called after loading state from the database. Interrupted tool results
reference sub-agent processes that no longer exist, so they must be
converted to error results before the LLM sees them.

This is idempotent — if there are no stale interrupts, it's a no-op.

# `delete_todo`

Remove a TODO by ID.

# `from_serialized`

Deserializes state data from export_state/1.

This is a convenience wrapper around StateSerializer.deserialize_state/2.

**Important**: The `agent_id` is NOT serialized (it's a runtime identifier),
so you MUST provide it when deserializing. This ensures the state can properly
interact with AgentServer and middleware that rely on the agent_id.

## Examples

    # Load from database
    {:ok, state_data} = load_from_db(conversation_id)

    # Deserialize with agent_id
    {:ok, state} = State.from_serialized("my-agent-123", state_data["state"])

## Parameters

  - `agent_id` - The agent_id to use for this state (required)
  - `data` - The serialized state map (the "state" field from export_state)

## Returns

  - `{:ok, state}` - Successfully deserialized with agent_id set
  - `{:error, reason}` - Deserialization failed

# `get_metadata`

Get metadata value.

# `get_todo`

Get a TODO by ID.

# `get_todos_by_status`

Get all TODOs with a specific status.

# `merge_states`

Merge two states together.

This is used when combining state updates from tools, middleware, or subagents.

## Merge Rules

- **messages**: Concatenates lists (left + right)
- **todos**: Uses right if present, otherwise left
- **metadata**: Deep merges maps

## Examples

    left = State.new!(%{messages: [%{role: "user", content: "hi"}]})
    right = State.new!(%{messages: [%{role: "assistant", content: "hello"}]})
    merged = State.merge_states(left, right)
    # merged now has both messages

# `new`

Create a new agent state.

Note: The `agent_id` field is optional when creating a state. The library
automatically injects it when the state is passed to Agent.execute or
AgentServer. This eliminates the need for manual agent_id synchronization.

## Examples

    # Create state without agent_id (recommended)
    state = State.new!(%{messages: [message]})

    # Library injects agent_id automatically
    {:ok, result_state} = Agent.execute(agent, state)

# `new!`

Create a new agent state, raising on error.

# `put_metadata`

Set metadata value.

# `put_todo`

Add or update a TODO item.

If a TODO with the same ID exists, it will be replaced at its current position.
If the TODO ID doesn't exist, it will be appended to the end of the list.

# `replace_tool_result`

Replace a tool result in the state's messages by `tool_call_id`.

Delegates to `LangChain.Message.replace_tool_result/3`.

# `reset`

```elixir
@spec reset(t()) :: t()
```

Reset the state to a clean slate.

Clears:
- All messages
- All TODOs
- All metadata

**Note**: This function only resets the Agent's state structure. File state is managed
separately by FileSystemServer and must be reset through AgentServer.reset/1 which
coordinates the full reset process.

## Examples

    state = State.new!(%{
      messages: [msg1, msg2],
      todos: [todo1],
      metadata: %{config: "value"}
    })

    reset_state = State.reset(state)
    # reset_state has:
    # - messages: []
    # - todos: []
    # - metadata: %{} (cleared)

# `set_messages`

Replace all messages.

Useful for:
- Thread restoration (restoring persisted messages)
- Testing scenarios (setting sample messages)
- Bulk message updates

## Parameters

- `state` - The current State struct
- `messages` - List of Message structs

## Examples

    messages = [
      Message.new_user!("Hello")
    ]
    state = State.set_messages(state, messages)

# `set_todos`

Replace all TODOs.

---

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