# `Nous.Agent`
[🔗](https://github.com/nyo16/nous/blob/v0.17.1/lib/nous/agent.ex#L1)

Primary interface for AI agents.

An Agent is a stateless configuration object that defines how to interact
with an AI model. It specifies the model, instructions, tools, and output format.

## Example

    # Simple agent
    agent = Agent.new("openai:gpt-4",
      instructions: "Be helpful and concise"
    )

    {:ok, result} = Agent.run(agent, "What is 2+2?")
    IO.puts(result.output) # "4"

    # Agent with tools
    agent = Agent.new("groq:llama-3.1-70b-versatile",
      instructions: "Help users find information",
      tools: [&MyTools.search/2]
    )

    {:ok, result} = Agent.run(agent, "Search for Elixir tutorials",
      deps: %{database: MyApp.DB}
    )

# `t`

```elixir
@type t() :: %Nous.Agent{
  behaviour_module: module() | nil,
  code_mode: Nous.CodeMode.mode() | nil,
  deps_type: module() | nil,
  enable_todos: boolean(),
  end_strategy: :early | :exhaustive,
  fallback: [Nous.Model.t()],
  hooks: [Nous.Hook.t()],
  instructions: String.t() | function() | nil,
  model: Nous.Model.t(),
  model_settings: map(),
  name: String.t(),
  output_type: Nous.Types.output_type(),
  parallel_tool_calls: boolean(),
  permissions: Nous.Permissions.Policy.t() | nil,
  plugins: [module()],
  retries: non_neg_integer(),
  sandbox: Nous.Sandbox.Policy.t() | nil,
  skills: [module() | String.t() | Nous.Skill.t() | {:group, atom()}],
  structured_output: keyword(),
  system_prompt: String.t() | function() | nil,
  tools: [Nous.Tool.t()]
}
```

# `new`

```elixir
@spec new(
  String.t(),
  keyword()
) :: t()
```

Create a new agent.

## Parameters
  * `model_string` - Model in format "provider:model-name"
  * `opts` - Configuration options

## Options
  * `:output_type` - Expected output type (`:string`, Ecto schema, schemaless map, JSON schema, or guided mode tuple)
  * `:structured_output` - Structured output options (`mode:`, `max_retries:`)
  * `:instructions` - Static instructions or function returning instructions
  * `:system_prompt` - Static system prompt or function
  * `:deps_type` - Module defining dependency structure
  * `:name` - Agent name for logging
  * `:model_settings` - Model settings (temperature, max_tokens, etc.)
  * `:retries` - Default retry count for tools
  * `:enable_todos` - Enable automatic todo tracking (default: false)
  * `:tools` - List of tool functions or Tool structs
  * `:plugins` - List of plugin modules implementing `Nous.Plugin` behaviour
  * `:hooks` - List of `Nous.Hook` structs for lifecycle interception
  * `:skills` - List of skill modules, directory paths, `Nous.Skill` structs, or `{:group, atom()}`
  * `:skill_dirs` - List of directory paths to scan for `.md` skill files (convenience for `:skills`)
  * `:end_strategy` - How to handle tool calls (`:early` or `:exhaustive`)
  * `:behaviour_module` - Custom agent behaviour module (default: BasicAgent)
  * `:fallback` - Ordered list of fallback model strings or `Model` structs to try
    when the primary model fails with a provider/model error
  * `:permissions` - Optional `Nous.Permissions.Policy` enforced at runtime:
    blocked tools are removed from the model's tool list and approval-required
    tools must pass the approval handler (see `Nous.Permissions`)
  * `:sandbox` - Optional OS-level confinement for subprocesses that tools
    spawn (`Nous.Tools.Bash`). Accepts a mode atom (`:read_only`,
    `:workspace_write`, `:danger_full_access`), a keyword list
    (`[mode: :workspace_write, workspace_root: "/srv/ws"]`), or a
    `Nous.Sandbox.Policy` struct; anything non-nil is normalized through
    `Nous.Sandbox.Policy.new/1`. `nil` (the default) falls back to the
    `config :nous, :sandbox_mode` application setting.

    This is a **sibling** knob to `:permissions`, not a replacement for it:
    permissions decide whether a tool may run at all, the sandbox confines
    what the subprocess that tool spawns may touch once it is running. A
    blocked tool never executes; a sandboxed tool executes with the OS
    refusing its writes. Use both.
  * `:code_mode` - How the model reaches tools: `:native` (direct tool calls,
    the classic behaviour), `:code` (the model sees only `run_code` and calls
    tools from inside a program), or `:both` (it picks per turn). Accepts the
    atom or its string form. `nil` (the default) falls back to the
    `config :nous, :code_mode` application setting, and then to `:both` —
    which behaves as `:native` unless a `Nous.CodeRuntime` provider is
    configured, so this option changes nothing until you configure one. Like
    `:sandbox` and `:permissions` this is a runtime knob, not a prompt: see
    `Nous.CodeMode`.
  * `:parallel_tool_calls` - Execute multiple tool calls from one model
    response concurrently (default: `false`). Hooks, approval checks, and
    post-processing (callbacks, `merge_deps`) stay sequential in call order;
    only the tool executions themselves fan out, and result messages keep
    the original call order. Tools already cannot observe each other's
    context updates within a turn (the run context is snapshotted before the
    tool loop), but external side effects (HTTP calls, DB writes) will
    interleave — leave this off if your tools depend on sequential
    side-effect ordering within a single response

## Examples

    # OpenAI GPT-4
    agent = Agent.new("openai:gpt-4")

    # Groq Llama with settings
    agent = Agent.new("groq:llama-3.1-70b-versatile",
      instructions: "Be concise",
      model_settings: %{temperature: 0.7, max_tokens: 1000}
    )

    # Local LM Studio
    agent = Agent.new("lmstudio:qwen3-vl-4b-thinking-mlx",
      instructions: "Always answer in rhymes"
    )

    # With tools
    agent = Agent.new("openai:gpt-4",
      tools: [&MyTools.search/2, &MyTools.calculate/2]
    )

# `run`

```elixir
@spec run(t(), String.t() | keyword(), keyword()) :: {:ok, map()} | {:error, term()}
```

Run agent synchronously.

## Input Formats

The second argument accepts multiple formats:

- **String prompt**: Simple string message from user
- **Keyword list**: Use `:messages` for custom message list, `:context` to continue from previous run

## Options
  * `:deps` - Dependencies to pass to tools and prompts
  * `:message_history` - Previous messages to continue conversation
  * `:usage_limits` - Usage limits for this run
  * `:model_settings` - Override model settings for this run
  * `:callbacks` - Map of callback functions for events
  * `:notify_pid` - PID to receive event messages
  * `:context` - Existing context to continue from
  * `:output_type` - Override the agent's `output_type` for this run
  * `:structured_output` - Override the agent's `structured_output` options for this run
  * `:sandbox` - Override the agent's `sandbox` policy for this run (same
    shapes as the `:sandbox` option to `new/2`)

## Examples

    # String prompt
    {:ok, result} = Agent.run(agent, "What is the capital of France?")
    IO.puts(result.output) # "Paris"

    # With dependencies
    {:ok, result} = Agent.run(agent, "Search for users",
      deps: %{database: MyApp.DB}
    )

    # Message list directly
    {:ok, result} = Agent.run(agent,
      messages: [
        Message.system("Be concise"),
        Message.user("What is 2+2?")
      ]
    )

    # Continue from previous context
    {:ok, result1} = Agent.run(agent, "First question")
    {:ok, result2} = Agent.run(agent, "Follow up",
      context: result1.context
    )

    # Continue conversation with message history
    {:ok, result2} = Agent.run(agent, "Tell me more",
      message_history: result1.new_messages
    )

    # With callbacks
    {:ok, result} = Agent.run(agent, "Hello",
      callbacks: %{
        on_llm_new_delta: fn _, text -> IO.write(text) end
      }
    )

    # Streaming + tool execution (works across providers)
    {:ok, result} = Agent.run(agent, "Search and summarize",
      stream: true,
      callbacks: %{
        on_llm_new_delta: fn _, text -> IO.write(text) end,
        on_llm_new_thinking_delta: fn _, text -> IO.write(["[thinking] ", text]) end,
        on_tool_call: fn _, call -> IO.inspect(call, label: "tool") end
      }
    )

See `Nous.AgentRunner.run/3` for the full option list. The `:stream` option
combines per-token deltas with the regular tool-call loop.

## Returns

    {:ok, %{
      output: "Result text or structured output",
      usage: %Usage{...},
      all_messages: [...],
      new_messages: [...],
      context: %Context{...}  # Can be used for continuation
    }}

    {:error, reason}

# `run_stream`

```elixir
@spec run_stream(t(), String.t(), keyword()) ::
  {:ok, Enumerable.t()} | {:error, term()}
```

Run agent with streaming.

Returns a stream that yields events as they occur.

## Events
  * `{:text_delta, text}` - Incremental text update
  * `{:tool_call, call}` - Tool is being called
  * `{:tool_result, result}` - Tool execution completed
  * `{:complete, result}` - Final result

## Example

    {:ok, stream} = Agent.run_stream(agent, "Tell me a story")

    stream
    |> Stream.each(fn
      {:text_delta, text} -> IO.write(text)
      {:complete, result} -> IO.puts("\nDone!")
    end)
    |> Stream.run()

# `tool`

> This function is deprecated. Prefer Agent.new/2 with the :tools option, or build %Nous.Tool{} directly.

```elixir
@spec tool(t(), function(), keyword()) :: t()
```

Register a tool with the agent.

Returns a new agent with the tool added.

## Options
  * `:name` - Custom tool name (default: function name)
  * `:description` - Custom description
  * `:retries` - Retry count for this tool
  * `:requires_approval` - Whether tool needs human approval

## Example

    agent = Agent.new("openai:gpt-4")
    agent = Agent.tool(agent, &MyTools.search/2,
      description: "Search the database"
    )

---

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