> ## Documentation Index
> Fetch the complete documentation index at: https://docs.symbolica.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Python

> Learn how to log and stream agents and agentic functions in Python.

The Agentica Python SDK exposes **utilities that listen to and log all agent and agentic function invocations and interactions**. This includes both chat histories in and out of the REPL, as well as outputs of code execution in the REPL. This is useful for debugging, monitoring, and understanding your agent's behavior.

In Python, the `logging` module has the following structure:

```
agentica.logging
├── AgentListener
├── PrintOnlyListener
├── FileOnlyListener
├── StandardListener
├── AgentLogger
├── NoLogging
└── loggers
    ├── StreamLogger
    ├── PrintLogger
    ├── FileLogger
    └── StandardLogger
```

In short, an `AgentListener` listens to an HTTP endpoint, while an `AgentLogger` defines the logging behaviour itself e.g. printing and file logging. Think of listeners as the "when" and "where" of logging, while loggers are the "what" and the "how".

## Specifying logging behaviour

The Agentica Python SDK provides **three ways** to specify how agents and agentic functions are logged, each with **different scopes and priorities**. Understanding the hierarchy helps you control logging behavior precisely for your use case.

### Listener priority hierarchy

When an agent or agentic function is invoked, listener resolution follows this priority (highest to lowest):

1. **Contextual loggers** (via context manager) - highest priority, temporary
2. **Per-agent/agentic function listener** (via `listener` parameter) - medium priority, per-instance
3. **Default agent listener** (via `set_default_agent_listener`) - lowest priority, global

If contextual loggers are active, they are **added to** (not replace) any configured listener, creating a `CompositeLogger` that routes events to all loggers simultaneously.

### Method 1: Default listener (global)

The **default agent listener** for all agents and agentic functions is the `StandardListener`. You can change this globally with `set_default_agent_listener`.

### Method 2: Per-agent/agentic function listener

Override the default for specific agents or agentic functions:

```python wrap theme={null}
from agentica.logging import (
    PrintOnlyListener,
    FileOnlyListener
)

# Attach listener to a specific agent
agent = await spawn(
    premise="Agent's task",
    listener=PrintOnlyListener
)

# Attach listener to a specific agentic function
@agentic(listener=FileOnlyListener)
async def my_func(a: int) -> str:
    ...
```

This is useful when you want different logging behavior for different agents or functions in your application.

### Method 3: Contextual loggers (scoped)

Use any `AgentLogger` to temporarily control logging for all agents and agentic functions spawned and invoked within that scope:
This is particularly useful for **temporarily changing logging behavior** for a specific section of code - for example, to debug a particular workflow or to separate logs for different operations.

```python wrap theme={null}
from agentica import spawn
from agentica.logging.loggers import (
    FileLogger,
    StandardLogger
)

agent = await spawn(premise="Helpful agent.")

# Temporarily log to file only
with FileLogger():
    await agent.call(int, "Calculate 2 + 2")
    # FileLogger is ADDED to any configured listener

# Outside context, normal behavior resumes
await agent.call(float, "Calculate the fifth root of 93")
```

**Multiple contextual loggers** can be nested - all will be active:

```python wrap theme={null}
from agentica.logging.loggers import PrintLogger, FileLogger

with PrintLogger():
    with FileLogger():
        # Both PrintLogger AND FileLogger are active
        agent = await spawn(premise="Dual logging")
        await agent.call(str, "Hello")
```

**Disable logging temporarily** with `NoLogging`:

```python wrap theme={null}
from agentica.logging.agent_logger import NoLogging

with NoLogging():
    # No logging occurs for agents spawned here
    agent = await spawn(premise="Silent agent")
    await agent.call(int, "Calculate something")
```

## Built-in listeners and loggers

The Agentica Python SDK offers built-in listeners and loggers with various combinations of behaviour, notably printing to `stdout` and writing to `.log` files.

|      Logger      |       Listener      |        `stdout`       |         `.log`        |
| :--------------: | :-----------------: | :-------------------: | :-------------------: |
| `StandardLogger` |  `StandardListener` | <Icon icon="check" /> | <Icon icon="check" /> |
|   `PrintLogger`  | `PrintOnlyListener` | <Icon icon="check" /> |                       |
|   `FileLogger`   |  `FileOnlyListener` |                       | <Icon icon="check" /> |
|    `NoLogging`   |                     |                       |                       |
|  `CaptionLogger` |                     |                       |                       |
|  `StreamLogger`  |                     |                       |                       |

The `.log` files include

* writing full chat histories to per‑agent files under `./logs/` (e.g., `agent-7.log`),
* allocates incrementing agent IDs based on existing files, and
* auto‑creating the logs directory (with a `.gitignore`).

````xml wrap theme={null}
<message role="user">
    Work out the 32nd power of 3
</message>
<message role="agent">
    ```python
    result = 3**32
    ```
</message>
````

Printing to `stdout` includes

* assigning stable colors per agent,
* printing on spawning an agent, and
* printing the result of an invocation an agent or an agentic function.

```shell wrap theme={null}
Spawned Agent 25 (./logs/agent-25.log)
► Agent 25: Get a subagent to work out the 32nd power of 3, then another subagent to work out the 34th power, then return both results.
Spawned Agent 26 (./logs/agent-26.log)
► Agent 26: Work out the 32nd power of 3
◄ Agent 26: 1853020188851841
Spawned Agent 27 (./logs/agent-27.log)
► Agent 27: Work out the 34th power of 3
◄ Agent 27: 16677181699666569
◄ Agent 25: (1853020188851841, 16677181699666569)
```

`StandardLogger` also prints reasoning content inside `<reasoning>` tags for both OpenAI and Anthropic models.

## Streaming

### `Chunk`

Each streamed piece of content is a `Chunk` with an optional `type` field for distinguishing different kinds of streamed content:

```python theme={null}
@dataclass
class Chunk:
    role: Role          # 'agent', 'user', or 'system'
    content: str        # The text content of the chunk
    type: str | None    # 'reasoning', 'output_text', 'code', 'usage', 'invocation_exit', or None
```

**Handling typed chunks:**

```python theme={null}
async def on_chunk(chunk: Chunk):
    if chunk.type == "reasoning":
        print(f"[thinking] {chunk.content}")
    elif chunk.type == "output_text":
        print(chunk.content, end="")
    elif chunk.type == "usage":
        print(f"[usage] {chunk.content}")
```

### `StreamLogger`

`StreamLogger` exposes an async iterator over the agent's text-generation stream. It also accepts an `on_chunk` callback and an `include_usage` flag:

```python theme={null}
stream = StreamLogger(
    on_chunk=my_async_handler,  # Optional: async callback for each chunk
    include_usage=True,         # Optional: include usage chunks (default False)
)
```

**Using `on_chunk` for real-time forwarding:**

```python theme={null}
async def forward(chunk: Chunk):
    await websocket.send(chunk.content)

stream = StreamLogger(on_chunk=forward, include_usage=True)
```

By default, usage chunks (`chunk.type == 'usage'`) are filtered out to avoid breaking existing consumers. Set `include_usage=True` to receive them.
