Skip to content

Audit Logging

Added in: v0.15.0

AuditLogger provides a JSONL append-only audit trail for every agent action. It records tool calls, LLM responses, policy decisions, and errors — all with configurable privacy controls and daily file rotation.


Quick Start

from selectools import Agent, AgentConfig, OpenAIProvider, tool
from selectools.audit import AuditLogger, PrivacyLevel

@tool(description="Search the knowledge base")
def search(query: str) -> str:
    return f"Results for: {query}"

audit = AuditLogger(
    log_dir="./audit",
    privacy=PrivacyLevel.KEYS_ONLY,   # redact argument values
    daily_rotation=True,               # audit-2026-03-12.jsonl
)

agent = Agent(
    tools=[search],
    provider=OpenAIProvider(),
    config=AgentConfig(observers=[audit]),
)

result = agent.ask("Find articles about Python")
# Check ./audit/audit-2026-03-12.jsonl

Every event is one JSON line:

{"event":"run_start","run_id":"abc123","message_count":1,"ts":"2026-03-12T18:30:00.000000+00:00"}
{"event":"tool_start","run_id":"abc123","call_id":"xyz","tool_name":"search","tool_args":{"query":"<redacted>"},"ts":"..."}
{"event":"tool_end","run_id":"abc123","call_id":"xyz","tool_name":"search","duration_ms":42.5,"success":true,"ts":"..."}
{"event":"llm_end","run_id":"abc123","model":"gpt-4o","prompt_tokens":150,"completion_tokens":50,"cost_usd":0.001,"ts":"..."}
{"event":"run_end","run_id":"abc123","iterations":2,"tool_name":"search","ts":"..."}

Privacy Levels

Control how sensitive data appears in audit logs:

Level Behaviour Example {"query": "secret data"}
PrivacyLevel.FULL Log everything verbatim {"query": "secret data"}
PrivacyLevel.KEYS_ONLY Redact values {"query": "<redacted>"}
PrivacyLevel.HASHED SHA-256 hash (truncated) {"query": "2bb80d537b1da3..."}
PrivacyLevel.NONE Omit arguments entirely {}
# Full logging (development)
AuditLogger(privacy=PrivacyLevel.FULL)

# Keys only (production default)
AuditLogger(privacy=PrivacyLevel.KEYS_ONLY)

# Hashed (compliance — can verify without exposing)
AuditLogger(privacy=PrivacyLevel.HASHED)

# No args (strictest privacy)
AuditLogger(privacy=PrivacyLevel.NONE)

File Rotation

# Daily rotation (default) — audit-2026-03-12.jsonl, audit-2026-03-13.jsonl, ...
AuditLogger(log_dir="./audit", daily_rotation=True)

# Single file — audit.jsonl
AuditLogger(log_dir="./audit", daily_rotation=False)

Recorded Events

Event When Key Fields
run_start Agent starts processing run_id, message_count
run_end Agent finishes run_id, iterations, tool_name, total_cost_usd
tool_start Before tool execution run_id, call_id, tool_name, tool_args
tool_end After successful tool run_id, call_id, tool_name, duration_ms, success
tool_error Tool raised exception run_id, tool_name, error, error_type, duration_ms
llm_end After LLM response run_id, model, prompt_tokens, cost_usd
policy_decision Policy evaluated tool run_id, tool_name, decision, reason
error Unrecoverable error run_id, error, error_type

Include LLM Response Content

By default, response content is not logged (privacy). Opt in:

AuditLogger(include_content=True)
# llm_end events will include "response_length": 250
# tool_end events will include "result_length": 100

Combining with Other Observers

AuditLogger is just an AgentObserver — combine it with others:

from selectools.observer import LoggingObserver

agent = Agent(
    tools=[...],
    provider=provider,
    config=AgentConfig(
        observers=[
            AuditLogger(log_dir="./audit"),     # JSONL file
            LoggingObserver(),                   # Python logging
        ],
    ),
)

Thread Safety

AuditLogger uses a threading.Lock for file writes, making it safe for concurrent batch() usage.


API Reference

Class / Enum Description
AuditLogger(log_dir, privacy, daily_rotation, include_content) JSONL audit logger (implements AgentObserver)
PrivacyLevel.FULL Log all values
PrivacyLevel.KEYS_ONLY Redact values to "<redacted>"
PrivacyLevel.HASHED SHA-256 hash of values
PrivacyLevel.NONE Omit tool_args entirely