Skip to main content

Why?

Agentica exposes library utilities that listen to and log agent or magic function invocations, chat histories and REPL interactions. This can be incredibly useful for understanding your agent or magic function. The module has the following structure:
agentica.magic.logging
├── AgentListener
├── AgentLogger
└── loggers
    ├── StandardLogger
    ├── PrintLogger
    └── FileLogger

Agent Loggers - The “What” and “How” of Logging

AgentLogger is the pluggable base class that powers listeners by defining the logging behaviour itself.
class AgentLogger(ABC):
    local_id: int | None
    parent_local_id: int | None

    # These methods must be implemented.
    def on_spawn(self) -> None: ...
    def on_call_enter(self, user_prompt: str, parent_local_id: int = None) -> None: ...
    def on_call_exit(self, result: object) -> None: ...
    async def on_chunk(self, chunk: Chunk) -> None: ...
Abstract methods that must be implemented:
  • on_spawn - Called when an agent is spawned
  • on_call_enter - Called when an agent receives a prompt
  • on_call_exit - Called when an agent returns a result
  • on_chunk - Called when a streaming chunk is received
Built‑in loggers:
  • StandardLogger: Prints concise, colored lifecycle lines to stdout and writes full chat histories to per‑agent files under ./logs/ (e.g., agent-7.log). It auto‑creates the logs directory (with a .gitignore), assigns stable colors per agent, and allocates incrementing agent IDs based on existing files.
  • PrintLogger: Same as StandardLogger but only prints to stdout; does not write files.
  • FileLogger: Same as StandardLogger but only writes log files; does not print to stdout.

Contextual logging

Any AgentLogger can be used in context. When you enter the context, the logger is registered and any agents or magic functions spawned within that scope will automatically use this logger.
from agentica import spawn
from agentica.magic.logging.loggers import FileLogger

# All agents spawned in this block will log to file
with FileLogger():
    agent = await spawn(premise="I will be logged to file")
    result = await agent.call(int, "Calculate 2 + 2")

# Outside the context, normal logging behavior resumes
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. Multiple contextual loggers can be nested, and all will be active simultaneously. For more persistant logging behaviour, we introduce the concept of an agent listener below.

Agent Listeners - The “When” and “Where” of Logging

AgentListener is a concrete orchestration class that manages the logging lifecycle. It wraps an AgentLogger and manages the concurrent listening task whilst piping events into the logger.
class AgentListener:
    logger: AgentLogger

    def listen(self, csm, uid: str, iid: str) -> None: ...
    def close(self) -> None: ...
In short, the listener listens to an HTTP endpoint, while the logger does the printing and file logging:
  • listen() starts the async task that receives chunks
  • close() cancels the listening task
You generally do not need to subclass AgentListener yourself, or interact with any of its methods, unless you want to override the default behaviour. The following AgentListener constructors are available for convenience:
StandardListener = lambda: AgentListener(StandardLogger())
FileOnlyListener = lambda: AgentListener(FileLogger())
PrintOnlyListener = lambda: AgentListener(PrintLogger())
Examples of stdout and a .log file for the StandardListener are given below.
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)
<message role="user">
    Work out the 32nd power of 3
</message>
<message role="agent">
    ```python
    result = 3**32
    ```
</message>

Specifying agent listeners

Agentica provides three ways to specify how agents and magic 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 magic function is invoked, listener resolution follows this priority (highest to lowest):
  1. Contextual loggers (via context manager) - highest priority, temporary
  2. Per-agent/magic 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 magic functions is the StandardListener. You can change this globally:
def set_default_agent_listener(
  listener: Callable[[], AgentListener] | None
) -> None:
Parameters
listener
Callable[[], AgentListener] | None
The listener constructor for logging an agent or magic function’s activity and chat history. If None, no listener will be used by default.

Method 2: Per-agent/magic function listener

Override the default for specific agents or magic functions:
from agentica.magic.logging import (
    PrintOnlyListener,
    FileOnlyListener
)

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

# Attach listener to a specific magic function
@magic(listener=FileOnlyListener)
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 magic functions spawned within that scope:
from agentica import spawn
from agentica.magic.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:
from agentica.magic.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:
from agentica.magic.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")