# `A2A.Agent`
[🔗](https://github.com/actioncard/a2a-elixir/blob/main/lib/a2a/agent.ex#L1)

Behaviour for defining A2A agents.

Agents are the primary abstraction in the A2A library. Each agent declares
its identity and capabilities via `agent_card/0` and implements message
handling via `handle_message/2`.

## Usage

    defmodule MyApp.GreeterAgent do
      use A2A.Agent,
        name: "greeter",
        description: "Greets users",
        skills: [
          %{id: "greet", name: "Greet", description: "Says hello", tags: []}
        ]

      @impl A2A.Agent
      def handle_message(message, _context) do
        text = A2A.Message.text(message)
        {:reply, [A2A.Part.Text.new("Hello, #{text}!")]}
      end
    end

The `use` macro accepts shorthand options for `agent_card/0`:

- `:name` — agent name (required unless `agent_card/0` is defined manually)
- `:description` — agent description (default: `""`)
- `:version` — agent version (default: `"0.1.0"`)
- `:skills` — list of skill maps (default: `[]`)
- `:opts` — additional keyword options (default: `[]`)

## Architecture

`use A2A.Agent` generates a full GenServer. The agent author only implements
the behaviour callbacks — the runtime manages task lifecycle, state
transitions, history accumulation, and persistence.

Internally, three modules collaborate:

- **`A2A.Agent`** (this module) — the behaviour definition and the `use`
  macro that generates GenServer client API and callbacks.
- **Agent.Runtime** (internal) — pure functions for message processing.
  Creates tasks, calls `handle_message/2`, maps reply tuples to state
  transitions. Also handles task continuation (multi-turn) and stream
  wrapping.
- **Agent.State** (internal) — the internal GenServer state struct. Holds
  the task map, context index, and optional task store reference. Provides
  helpers for task storage, retrieval, and state transitions.

## Task Lifecycle

The runtime manages a task state machine so agent implementations don't
have to. Each message creates (or continues) a task — except
`{:message, parts}`, which answers without one — that progresses through:

    :submitted → :working → :completed
                          → :failed
                          → :input_required → (new message) → :working → ...
                          → :canceled

The reply from `handle_message/2` determines the transition:

- `{:reply, parts}` — creates an artifact, transitions to `:completed`
- `{:message, parts}` — answers with a bare `A2A.Message` and **no task**
  (see below)
- `{:input_required, parts}` — transitions to `:input_required`, caller
  can continue the same task by passing `task_id:` to the next call
- `{:stream, enumerable}` — stays `:working`, transitions to `:completed`
  when the caller fully consumes the stream
- `{:error, reason}` — transitions to `:failed`

## Bare Message Replies

`{:message, parts}` is the other half of the A2A spec's
`SendMessageResponse` oneof: the agent answers out-of-band and no task is
created. The task the runtime built for the turn is discarded — nothing is
persisted, `tasks/get` will not find it, and `A2A.call/3` returns
`{:ok, %A2A.Message{}}` instead of a task. On the wire the JSON-RPC result
is `{"message": …}` rather than `{"task": …}`.

The message inherits the request's `context_id` and carries no `task_id`,
since there is no task for it to reference.

Prefer it over `{:reply, parts}` for answers with nothing to track — a
lookup, an acknowledgement, a rejected request. Anything a caller might
later poll, cancel, or continue needs a task.

Returning it while continuing an existing task is rejected with
`{:error, :message_on_task}` (`-32006` on the wire): the caller is holding a
task id, so a bare Message would strand that task and drop the turn from its
history.

## Reply Parts and Status

The parts returned from `handle_message/2` serve double duty: they are
appended to the task's `history` (as an agent message) **and**, for
`{:input_required, parts}`, set as the task's `status.message`.

These two fields have different audiences:

- **`history`** — the full conversation transcript, used by agent logic
  for context on subsequent turns.
- **`status.message`** — a short prompt surfaced to the client UI,
  explaining why the task is paused and what input is needed.

Because both are populated from the same parts, keep
`{:input_required, parts}` **concise and actionable**:

    # Good — short prompt that works as both history entry and status
    {:input_required, [Part.Text.new("What size pizza?")]}

    # Avoid — long explanation duplicated into status.message
    {:input_required, [Part.Text.new("Here's our full menu... What size?")]}

For `{:reply, parts}` and `{:stream, enumerable}`, the parts only go
into `history` and `artifacts` — `status.message` is left empty.
`{:message, parts}` populates neither, since it produces no task.

## Multi-Turn Conversations

When an agent returns `{:input_required, parts}`, the task pauses. The
caller continues it by passing `task_id:` with the next message:

    {:ok, task} = A2A.call(agent, "order pizza")
    # task.status.state == :input_required
    {:ok, task} = A2A.call(agent, "large", task_id: task.id)
    # task.status.state may be :completed or :input_required again

The runtime appends each message to the task's history, so the agent
receives the full conversation in `context.history`.

## Streaming

When an agent returns `{:stream, enumerable}`, the runtime wraps the
stream so that consuming it automatically finalizes the task:

    {:ok, task, stream} = A2A.stream(agent, "count to 5")
    Enum.each(stream, &IO.inspect/1)
    # task is now :completed with an artifact containing all streamed parts

## Persistence

By default, tasks live in the GenServer's process state (in-memory map).
For external persistence, pass a task store at startup:

    MyAgent.start_link(task_store: {A2A.TaskStore.ETS, :my_table})

The runtime writes every task update to both the internal map and the
external store. See `A2A.TaskStore` for the behaviour interface.

## Starting an Agent

    {:ok, pid} = MyAgent.start_link()
    {:ok, task} = A2A.call(MyAgent, "hello")

Or with options:

    MyAgent.start_link(name: :my_agent, task_store: {A2A.TaskStore.ETS, :tasks})

# `card`

```elixir
@type card() :: %{
  name: String.t(),
  description: String.t(),
  version: String.t(),
  skills: [skill()],
  opts: keyword()
}
```

# `context`

```elixir
@type context() :: %{
  task_id: String.t(),
  context_id: String.t() | nil,
  history: [A2A.Message.t()],
  metadata: map(),
  extensions: %{optional(String.t()) =&gt; A2A.Extension.activation()}
}
```

# `reply`

```elixir
@type reply() ::
  {:reply, [A2A.Part.t()]}
  | {:message, [A2A.Part.t()]}
  | {:stream, Enumerable.t()}
  | {:input_required, [A2A.Part.t()]}
  | {:error, term()}
```

# `skill`

```elixir
@type skill() :: %{
  id: String.t(),
  name: String.t(),
  description: String.t(),
  tags: [String.t()]
}
```

# `agent_card`

```elixir
@callback agent_card() :: card()
```

Returns the agent's identity and capabilities.

# `handle_cancel`

```elixir
@callback handle_cancel(context()) :: :ok | {:error, String.t()}
```

Called when a task is canceled by the caller. Optional.

# `handle_message`

```elixir
@callback handle_message(A2A.Message.t(), context()) :: reply()
```

Handles an incoming message. This is the core agent logic.

---

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