# `Sagents.Mode.Steps`

Sagents-specific pipeline steps for custom execution modes.

These steps handle concerns that belong at the agent layer, not the
LLMChain layer: HITL interrupts and state propagation from tool results.

# `check_pre_tool_hitl`

Check if the LLM response contains tool calls that need human approval.

Reads middleware list from opts `:middleware`. Inspects `chain.exchanged_messages`
for tool calls that match the HITL policy.

Returns `{:interrupt, chain, interrupt_data}` if approval is needed.

# `continue_or_done_safe`

Decide whether to loop or return, with until_tool contract enforcement.

This is a safer variant of LangChain's `continue_or_done/3` that adds
enforcement of the "until_tool" contract: if the LLM stops (needs_response
becomes false) without ever calling the target tool, it returns an error
instead of `{:ok, chain}`.

If the target tool WAS called, `check_until_tool/2` would have already
converted the pipeline to `{:ok, chain, tool_result}`, which passes
through here as a terminal.

## When `until_tool_active` is false or absent in opts

- `{:continue, chain}` with `needs_response: true` -> calls `run_fn.(chain, opts)` (loop)
- `{:continue, chain}` with `needs_response: false` -> `{:ok, chain}` (normal completion)
- Any terminal tuple -> pass through unchanged

## When `until_tool_active` is true in opts

- `{:continue, chain}` with `needs_response: true` -> calls `run_fn.(chain, opts)` (loop)
- `{:continue, chain}` with `needs_response: false` -> `{:error, chain, %LangChainError{...}}`
- Any terminal tuple -> pass through unchanged

# `propagate_state`

Propagate state updates from tool results into the chain's custom_context.

After tool execution, tools may have returned State structs as
`processed_content`. This step extracts those deltas and merges them
into `custom_context.state`.

---

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