# First Agent
You want a production-shaped `Jido.AI.Agent` with tools, request handles, and explicit runtime control.
After this guide, you will run a custom tool, submit async requests, and await specific request IDs.
## Build The Tool
```elixir
defmodule MyApp.Actions.AddNumbers do
use Jido.Action,
name: "add_numbers",
schema: Zoi.object(%{a: Zoi.integer(), b: Zoi.integer()}),
description: "Add two numbers."
@impl true
def run(%{a: a, b: b}, _context), do: {:ok, %{sum: a + b}}
end
```
## Build The Agent
```elixir
defmodule MyApp.MathAgent do
use Jido.AI.Agent,
name: "math_agent",
model: :fast,
tools: [MyApp.Actions.AddNumbers],
max_iterations: 8,
system_prompt: "Solve accurately. Use tools for arithmetic.",
llm_opts: [thinking: %{type: :enabled, budget_tokens: 2048}, reasoning_effort: :high]
end
```
## Optional: Configure Tool Effect Policy
Use `effect_policy` to bound which tool-emitted effects are allowed at runtime.
```elixir
defmodule MyApp.SafeMathAgent do
use Jido.AI.Agent,
name: "safe_math_agent",
model: :fast,
tools: [MyApp.Actions.AddNumbers],
effect_policy: %{
mode: :allow_list,
allow: [Jido.Agent.StateOp.SetState, Jido.Agent.Directive.Emit]
},
strategy_effect_policy: %{
constraints: %{
emit: %{allowed_signal_prefixes: ["app.math."]}
}
}
end
```
`strategy_effect_policy.constraints` accepts atom keys, string keys, or keyword lists (including nested `emit` and `schedule` maps). Inputs are normalized before policy enforcement.
## Run Async + Await
```elixir
{:ok, pid} = Jido.AgentServer.start(agent: MyApp.MathAgent)
{:ok, req} = MyApp.MathAgent.ask(pid, "What is 19 + 23?")
{:ok, result} = MyApp.MathAgent.await(req, timeout: 15_000)
# Per-request overrides
{:ok, req2} =
MyApp.MathAgent.ask(pid, "Explain how you solved that",
allowed_tools: ["add_numbers"],
tool_context: %{tenant_id: "acme"},
llm_opts: [reasoning_effort: :medium]
)
```
## Optional: Shape ReAct Requests Per Turn
Use `request_transformer` when the next LLM turn should depend on runtime state produced by earlier tools.
```elixir
defmodule MyApp.ClassifierTransformer do
def transform_request(request, _state, _config, runtime_context) do
seen_codes = get_in(runtime_context, [:state, :seen_codes]) || []
case seen_codes do
[] ->
{:ok, %{tools: request.tools}}
codes ->
{:ok,
%{
tools: %{},
llm_opts: [
provider_options: [
response_schema: %{
type: "object",
properties: %{code: %{enum: codes}}
}
]
]
}}
end
end
end
defmodule MyApp.ClassifierAgent do
use Jido.AI.Agent,
name: "classifier_agent",
model: :capable,
tools: [MyApp.Actions.FindSimilarCodes],
request_transformer: MyApp.ClassifierTransformer
end
```
Then let your retrieval tool write the allowlist into runtime state:
```elixir
{:ok, %{seen_codes: codes},
[
%Jido.Agent.StateOp.SetState{
attrs: %{seen_codes: codes}
}
]}
```
This pattern is the clean way to implement "show only the IDs the model has seen" flows. Prefer runtime state plus `request_transformer` over parsing thread history.
## Optional: Set Tool Context At Runtime
```elixir
signal = Jido.Signal.new!(
"ai.react.set_tool_context",
%{tool_context: %{tenant_id: "acme"}},
source: "/docs/example"
)
:ok = Jido.AgentServer.cast(pid, signal)
```
## Optional: Set System Prompt At Runtime
```elixir
{:ok, _agent} = Jido.AI.set_system_prompt(pid, "You are a concise support specialist.")
```
## Final Answer Vs Tool Outputs
`ask_sync/3` and `snapshot.result` return the final assistant answer. Tool
Actions may also return structured data that you want to inspect directly. For
that, read `snapshot.details[:tool_results]` instead of parsing conversation
messages or internal request traces.
| Surface | Contains | Use for |
|---|---|---|
| `snapshot.result` | Final assistant answer | What to show the user |
| `snapshot.details[:tool_results]` | Completed tool outputs for the current or most recent ReAct run | URLs, IDs, records, and other structured tool data |
| `snapshot.details[:conversation]` | Projected LLM messages, including serialized tool messages | Restoring conversation context |
```elixir
{:ok, status} = Jido.AgentServer.status(pid)
image_results =
status.snapshot.details[:tool_results]
|> List.wrap()
|> Enum.filter(&(&1.name == "generate_image"))
|> Enum.map(fn %{result: {:ok, output, _effects}} -> output end)
```
Use this for run inspection. If a tool produces domain data that must survive
process restarts or serve as the system of record, persist it from the tool or
return an allowed state effect.
## Optional: Restore Conversation Context
If you persist the conversation history (e.g. from `snapshot.details.conversation`),
you can restore it on restart so the agent resumes where it left off.
```elixir
saved_messages = snapshot.details.conversation
# Split out one leading system message (if present) so it does not become
# a duplicate context entry.
{saved_system_prompt, conversation_messages} =
case saved_messages do
[%{role: role, content: content} | rest]
when role in [:system, "system"] and is_binary(content) ->
{content, rest}
_ ->
{nil, saved_messages}
end
# At start time — pass the saved context via initial_state:
context =
Jido.AI.Context.new(system_prompt: saved_system_prompt)
|> Jido.AI.Context.append_messages(conversation_messages)
Jido.AgentServer.start_link(agent: MyAgent, initial_state: %{context: context})
```
When restoring with `initial_state: %{context: context}`, a nil
`context.system_prompt` is backfilled from the agent's configured prompt.
## Note: Retrieval And ReAct
If you enable the retrieval plugin, auto-enrichment does **not** run on `ai.react.query` signals.
Recall memory explicitly and prepend it to your prompt. See [Retrieval And Quota](retrieval_and_quota.md) for details.
## Failure Mode: Tool Not Registered / Not Valid
Symptom:
- Request completes with tool error
- `{:error, :not_a_tool}` when registering dynamically
Fix:
- Ensure module exports `name/0`, `schema/0`, and `run/2`
- Validate module with `Jido.AI.register_tool(pid, ToolModule)`
## Defaults You Should Know
- `request_policy` default: `:reject`
- Tool timeout default: `15_000ms`
- Tool retry defaults: `1` retry, `200ms` backoff
## When To Use / Not Use
Use this approach when:
- You need reasoning plus tool execution
- You need per-request correlation and awaiting
Do not use this approach when:
- You only need deterministic, single-pass text completion
## Next
- [Request Lifecycle And Concurrency](request_lifecycle_and_concurrency.md)
- [Tool Calling With Actions](tool_calling_with_actions.md)
- [Directives Runtime Contract](../developer/directives_runtime_contract.md)