# 4. The Agent Executor

The core logic of how an A2A agent processes requests and generates responses is handled by an **Agent Executor**. The `a2a-python-sdk` provides an abstract base class `a2a.server.AgentExecutor` that you implement.

## `AgentExecutor` Interface

The `AgentExecutor` class defines methods that correspond to different A2A RPC calls:

- `async def on_message_send(...)`: Handles standard request/response messages (`message/send`).
- `async def on_message_stream(...)`: Handles requests that expect a streaming response (`message/sendStream`).
- `async def on_cancel(...)`: Handles requests to cancel a task (`tasks/cancel`).
- `async def on_resubscribe(...)`: Handles requests to resubscribe to a task's stream (`tasks/resubscribe`).

## Helloworld Agent Executor

Let's look at `examples/helloworld/agent_executor.py`. It defines `HelloWorldAgentExecutor`.

1. **The Agent (`HelloWorldAgent`)**:
    This is a simple helper class that encapsulates the actual "business logic".

    ```python { .no-copy }
    # examples/helloworld/agent_executor.py
    class HelloWorldAgent:
        async def invoke(self):
            return 'Hello World'

        async def stream(self) -> AsyncGenerator[dict[str, Any], None]:
            yield {'content': 'Hello ', 'done': False}
            await asyncio.sleep(2) # Simulate work
            yield {'content': 'World', 'done': True}
    ```

    It has an `invoke` method for single responses and a `stream` method for generating multiple chunks.

2. **The Executor (`HelloWorldAgentExecutor`)**:
    This class implements the `AgentExecutor` interface.

    - **`__init__`**:

        ```python { .no-copy }
        # examples/helloworld/agent_executor.py
        class HelloWorldAgentExecutor(AgentExecutor):
            def __init__(self):
                self.agent = HelloWorldAgent()
        ```

        It instantiates the `HelloWorldAgent`.

    - **`on_message_send`**:

        ```python { .no-copy }
        # examples/helloworld/agent_executor.py
        async def on_message_send(
            self, request: SendMessageRequest, task: Task | None
        ) -> SendMessageResponse:
            result = await self.agent.invoke() # Calls the agent's logic

            message: Message = Message( # Constructs the A2A Message
                role=Role.agent,
                parts=[Part(root=TextPart(text=result))],
                messageId=str(uuid4()),
            )
            # Wraps it in a success response
            return SendMessageResponse(
                root=SendMessageSuccessResponse(id=request.id, result=message)
            )
        ```

        When a non-streaming `message/send` request comes in:

        1. It calls `self.agent.invoke()` to get the "Hello World" string.
        2. It constructs an A2A `Message` object with the agent's role and the result as a `TextPart`.
        3. It wraps this `Message` in a `SendMessageSuccessResponse`.

    - **`on_message_stream`**:

        ```python { .no-copy }
        # examples/helloworld/agent_executor.py
        async def on_message_stream( # type: ignore
            self, request: SendMessageStreamingRequest, task: Task | None
        ) -> AsyncGenerator[SendMessageStreamingResponse, None]:
            async for chunk in self.agent.stream(): # Iterates over agent's stream
                message: Message = Message(
                    role=Role.agent,
                    parts=[Part(root=TextPart(text=chunk['content']))],
                    messageId=str(uuid4()),
                    final=chunk['done'], # Indicates if this is the last chunk
                )
                # Yields each chunk as a streaming success response
                yield SendMessageStreamingResponse(
                    root=SendMessageStreamingSuccessResponse(
                        id=request.id, result=message
                    )
                )
        ```

        When a streaming `message/sendStream` request is received:

        1. It iterates through the chunks produced by `self.agent.stream()`.
        2. For each chunk, it creates an A2A `Message`. The `final` attribute of the message is important for streaming; it tells the client if more chunks are coming.
        3. Each message is `yield`ed, wrapped in a `SendMessageStreamingSuccessResponse`. This is how SSE (Server-Sent Events) are generated by the SDK.

    - `on_cancel` and `on_resubscribe`:
        The Helloworld example marks these as `UnsupportedOperationError` because it doesn't implement task cancellation or resubscription.

        ```python { .no-copy }
        # examples/helloworld/agent_executor.py
        # ...
        async def on_cancel(
            self, request: CancelTaskRequest, task: Task
        ) -> CancelTaskResponse:
            return CancelTaskResponse(
                root=JSONRPCErrorResponse(
                    id=request.id, error=UnsupportedOperationError()
                )
            )
        # ... similar for on_resubscribe
        ```

The `AgentExecutor` acts as the bridge between the raw A2A protocol requests/responses and your agent's specific logic.
