Skip to main content

Installation

pip install provenlog
Requirements: Python 3.8+

Basic usage

from provenlog import ProvenLogClient

client = ProvenLogClient(agent_id="my-agent")

client.log_event(
    action_type="TOOL_CALL",
    action_name="search_database",
    action_input={"query": "revenue Q4"},
    action_status="success",
    action_output={"results": 42}
)

client.close()

Client configuration

client = ProvenLogClient(
    base_url=None,              # Server URL; None for embedded mode
    agent_id="my-agent",        # Default agent ID for all events
    session_id="session-123",   # Default session ID
    batch_size=100,             # Events per batch
    flush_interval=1.0,         # Seconds between flushes
    timeout=30.0,               # HTTP timeout
    metadata={"env": "prod"},   # Default metadata merged into every event
    labels={"dept": "finance"}, # Default labels merged into every event
    context=None,               # Initial label context for dashboard enrichment
    db_path=None,               # SQLite path (embedded mode)
    api_key=None,               # Bearer token (server mode)
    on_error=None,              # Error callback
)

Environment variables

VariableDescription
PROVENLOG_SERVER_URLServer URL (enables server mode, used by ProvenLogClient)
PROVENLOG_URLServer URL (used by provenlog.auto / plog run)
PROVENLOG_DB_PATHSQLite path for embedded mode
PROVENLOG_API_KEYBearer token for server auth

Embedded vs Server mode

# Embedded mode (default) — writes to local SQLite
client = ProvenLogClient(agent_id="my-agent")
client = ProvenLogClient(agent_id="my-agent", db_path="/tmp/audit.db")

# Server mode — sends to plog serve
client = ProvenLogClient("http://localhost:7600", agent_id="my-agent")
client = ProvenLogClient("http://localhost:7600", agent_id="my-agent", api_key="your-key")

Logging events

# Using keyword arguments
client.log_event(
    action_type="TOOL_CALL",
    action_name="search_database",
    action_input={"query": "revenue Q4"},
    action_output={"results": 42},
    action_status="success",
    duration_ms=150,
    metadata={"model": "claude-sonnet-4-5-20250929"},
    labels={"env": "prod"},
)

# Using a ProvenLogEvent object
from provenlog.event import ProvenLogEvent

event = ProvenLogEvent(
    action_type="LLM_CALL",
    action_name="chat_completion",
    action_input={"prompt": "Hello"},
    action_status="success",
)
client.log_event(event)

Labels and context

Default labels

Labels passed to the constructor are merged into every event. Event-level labels override defaults for matching keys.
client = ProvenLogClient(
    agent_id="loan-processor",
    labels={"env": "prod", "dept": "lending"},
)

# This event will have labels {"env": "prod", "dept": "lending", "loan_id": "LN-2024-4821"}
client.log_event(
    action_type="TOOL_CALL",
    action_name="pull_credit_report",
    action_status="success",
    labels={"loan_id": "LN-2024-4821"},
)

Querying by labels

Events can be filtered by label values using ?label.<key>=<value> query parameters. Multiple label filters are ANDed together.
GET /v1/events?label.env=prod
GET /v1/events?label.env=prod&label.dept=lending
GET /v1/events?agent_id=loan-processor&label.loan_id=LN-2024-4821&limit=50

Label context

Labels are short identifiers, but dashboards often need richer information. set_context() attaches business data to a label value for dashboard enrichment — for example, attaching a borrower name and loan amount to a loan_id.
client = ProvenLogClient(
    agent_id="loan-processor",
    labels={"loan_id": "LN-2024-4821"},
)

client.set_context("loan_id", {
    "borrower": "Alice Rivera",
    "amount": 420000,
    "stage": "underwriting",
})
The label key must exist in the client’s default labels — otherwise a ValueError is raised. The data is sent as a fire-and-forget PUT /v1/context/{key}/{value} request (or written to SQLite in embedded mode). Transport errors are logged as warnings and never raise. Context is keyed by (label_key, label_value) and upserted — calling set_context again replaces the previous data. You can also push context at construction time with the context parameter:
client = ProvenLogClient(
    agent_id="loan-processor",
    labels={"loan_id": "LN-2024-4821", "env": "prod"},
    context={
        "loan_id": {"borrower": "Alice Rivera", "amount": 420000},
        "env": {"display_name": "Production", "region": "us-east-1"},
    },
)

Querying events

# List events with filters
events = client.get_events(
    agent_id="my-agent",
    action_type="TOOL_CALL",
    limit=50,
    offset=0,
)

# Get a single event by ID
event = client.get_event("550e8400-e29b-41d4-a716-446655440000")

Verifying integrity

result = client.verify("my-agent")
# {"valid": True}
# or {"valid": False, "error": "hash mismatch at sequence 42"}

Batching and async

Events are ingested non-blocking via a background thread:
  • Batched every 100 events or 1 second (whichever comes first)
  • Never blocks your agent’s execution path
  • Flush explicitly with client.flush()
  • Clean shutdown with client.close()

Context manager

with ProvenLogClient(agent_id="my-agent") as client:
    client.log_event(
        action_type="TOOL_CALL",
        action_name="search",
        action_status="success",
    )
# Automatically flushed and closed

PII redaction

In embedded mode, redaction is automatic — the client loads redaction rules from your config file on startup. In server mode, the server handles redaction. To add custom redaction rules, create a provenlog.yaml config file:
redaction:
  rules:
    - name: mrn
      pattern: "MRN-\\d{10}"
      replace: "[REDACTED:mrn]"
Config file discovery order:
  1. PROVENLOG_CONFIG environment variable
  2. ./provenlog.yaml
  3. ~/.provenlog/config.yaml
You can also use the Redactor class directly for standalone redaction:
from provenlog import Redactor

redactor = Redactor.default()
clean, count = redactor.redact_string("Contact john@example.com")
# clean = "Contact [REDACTED:email]", count = 1
See PII Redaction for the full list of built-in patterns.