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):
- Contextual loggers (via context manager) - highest priority, temporary
- Per-agent/magic function listener (via
listener parameter) - medium priority, per-instance
- 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")