Skip to main content

agentlens

Tamper-evident audit logging for Claude agents — Claude Code hooks and Anthropic SDK. Local-first, append-only, OSS.

Why

Anthropic logs API calls for their own safety monitoring — but that log is not yours. When your Claude-powered agent takes an action, you need your own tamper-evident record: for compliance (EU AI Act Art. 12, ISO/IEC 42001 A.6.2.8), incident response, and accountability.

agentlens captures every tool_use / tool_result event into a SHA-256 hash-chained JSONL file on your own machine — via Claude Code hooks (recommended) or as a drop-in Anthropic SDK wrapper. It can also block dangerous tool calls before they execute (deterministic rules, no LLM in the loop).

Quickstart: Claude Code / Claude Agent SDK (v0.6.0+)

pip install agentlens-io
agentlens hook install   # prints the settings.json snippet

.claude/settings.json:

{
  "hooks": {
    "PreToolUse": [
      {"matcher": "*", "hooks": [
        {"type": "command", "command": "agentlens hook pre --log ~/.agentlens/audit.jsonl --block critical"}
      ]}
    ],
    "PostToolUse": [
      {"matcher": "*", "hooks": [
        {"type": "command", "command": "agentlens hook post --log ~/.agentlens/audit.jsonl"}
      ]}
    ]
  }
}

Now every tool call in Claude Code is audit-logged, and rm -rf /-class commands are denied before execution:

agentlens view   ~/.agentlens/audit.jsonl        # colorized event viewer
agentlens summary ~/.agentlens/audit.jsonl       # per-session stats
agentlens verify ~/.agentlens/audit.jsonl        # ✅ hash-chain integrity / ❌ tamper detected
agentlens feedback ~/.agentlens/audit.jsonl --emit-code   # suggest whitelist rules from suppressed violations (v0.8.0+)

feedback reads the accumulated log — including the suppressed_violations that the whitelist keeps instead of deleting — and proposes narrowly-scoped WhitelistRules for rules with a high false-positive rate. It is suggestion-only: it never rewrites your ruleset. A ruleset that auto-tunes from its own logs can be poisoned, so a human stays in the loop. Flags: --min-occurrences N (default 3), --threshold F (default 0.9), --emit-code.

Options: --block critical|high|off (default critical), --whitelist rules.json (false-positive suppression — suppressed violations stay in the log), --standalone (post-hook logs tool_use+result when no pre-hook is registered). Hooks are fail-open: the logger can never break your agent loop.

Design principles

  • Read-only interception — requests and responses are never altered
  • Append-only writes — log entries cannot be edited after creation
  • No AI in the logger — capture logic is deterministic code, not an LLM
  • Your data stays local — FileWriter (default) writes to your own machine; no data leaves your environment

Usage: SDK wrapper

from agentlens import AuditedAnthropic

# Drop-in replacement for anthropic.Anthropic()
client = AuditedAnthropic(log_path="./audit.jsonl")

response = client.messages.create(
    model="claude-opus-4-6",
    max_tokens=1024,
    tools=[...],
    messages=[{"role": "user", "content": "..."}],
)
# Every tool_use and tool_result is now in audit.jsonl

Async (v0.7.0+)

AsyncAuditedAnthropic is the drop-in for anthropic.AsyncAnthropic — same audit logging and pre-execution blocking, awaited:

from agentlens import AsyncAuditedAnthropic

client = AsyncAuditedAnthropic(log_path="./audit.jsonl", block_on_critical=True)

response = await client.messages.create(
    model="claude-opus-4-6",
    max_tokens=1024,
    tools=[...],
    messages=[{"role": "user", "content": "..."}],
)
# Raises PreExecutionBlockedError before a critical tool call reaches you.

Streaming (v0.9.0+)

messages.stream() is wrapped too. Text passes through untouched; the audit and the pre-execution gate fire when the message completes — before your code reads the finished tool_use and acts on it. Works on the sync and async clients:

with client.messages.stream(
    model="claude-opus-4-6",
    max_tokens=1024,
    tools=[...],
    messages=[{"role": "user", "content": "..."}],
) as stream:
    for text in stream.text_stream:
        print(text, end="")
    message = stream.get_final_message()  # tool_use audited + gated here
# block_on_critical raises PreExecutionBlockedError before you touch tool_use.

Provenance — who ran the agent (v0.10.0+)

The log answers what happened. Provenance adds who caused it and under what authority — stamped onto every event so a reader can prove attribution later.

from agentlens import AuditedAnthropic, Provenance

client = AuditedAnthropic(
    log_path="./audit.jsonl",
    provenance=Provenance(
        agent_id="deploy-bot",            # which agent
        principal="alice@corp",           # on whose behalf
        authority=["repo:read", "ci:run"],# scopes it was granted
        # run_id auto-generated; pass parent_run_id to record lineage
    ),
)

Hosted/CI agents usually get their identity from the platform via env, so Provenance.from_env() reads AGENTLENS_AGENT_ID, AGENTLENS_PRINCIPAL, AGENTLENS_AUTHORITY (comma/space separated), AGENTLENS_RUN_ID, AGENTLENS_PARENT_RUN_ID. Provenance is covered by the hash chain, so tampering with who did it breaks verify too. agentlens view shows a by: line per call; summary breaks Tool Use down by agent. In v0.10 authority is recorded; AuthorityPolicy (below) turns it into enforcement.

Authority enforcement — stay inside the granted scope (v0.11.0+)

v0.10 recorded the authority an agent was granted. AuthorityPolicy enforces it: a tool whose required scopes aren't all present in the agent's granted authority raises a critical violation, blocked by the same block_on_critical gate as the danger rules. Record → enforce.

from agentlens import AuditedAnthropic, AuthorityPolicy, Provenance

client = AuditedAnthropic(
    log_path="./audit.jsonl",
    block_on_critical=True,
    provenance=Provenance(agent_id="deploy-bot", authority=["repo:read"]),
    authority_policy=AuthorityPolicy(
        requirements={"bash": ["shell:exec"], "web_search": ["web:fetch"]},
        # default_required=["*"] for deny-by-default once your scope map is complete
    ),
)
# deploy-bot was granted only repo:read → a bash tool_use is blocked before it runs.

Enforcement is opt-in (no policy → identical to v0.10, pure recording). A tool is allowed iff every scope it requires is granted (AND semantics); unmapped tools are allowed unless you set default_required. "*" in the granted authority is a super-scope that allows everything.

Log format (JSONL)

{"event_type": "tool_use", "tool_use_id": "toolu_01xxx", "tool_name": "bash", "tool_input": {"command": "ls -la"}, "model": "claude-opus-4-6", "timestamp": "2026-04-05T10:00:00+00:00", "session_id": "...", "provenance": {"agent_id": "deploy-bot", "principal": "alice@corp", "authority": ["repo:read"], "run_id": "..."}}
{"event_type": "tool_result", "tool_use_id": "toolu_01xxx", "result_content": "file1.txt\nfile2.txt", "is_error": false, "timestamp": "2026-04-05T10:00:01+00:00", "session_id": "...", "provenance": {"agent_id": "deploy-bot", "run_id": "..."}}

Custom writer

from agentlens.writers import BaseWriter

class MyWriter(BaseWriter):
    def write(self, event) -> None:
        # send to your own DB, S3, SIEM, etc.
        my_db.insert(event.to_json())

client = AuditedAnthropic(writer=MyWriter())

Run tests

pip install -e ".[dev]"
pytest tests/

License

MIT

Release files for agentlens-io 0.11.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for agentlens-io 0.11.0
File Size Uploaded
agentlens_io-0.11.0.tar.gz 41.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for agentlens-io 0.11.0
File Interpreter ABI Platform
agentlens_io-0.11.0-py3-none-any.whl Python 3 none any Details

Total release size: 71.8 kB

Release files / agentlens_io-0.11.0.tar.gz

Download URL agentlens_io-0.11.0.tar.gz
Size 41.4 kB
Tags Source
SHA-256 checksum
How to use checksums
350ef2e3b7b8c72f728f2fd05ae06adc9ff80b500cc57cbbba5ae783faac2ca3
BLAKE2b-256 checksum
How to use checksums
1216c4f117845b7c5a32249093d7155df3657e982c5cc6b6bbcef21bee1ee6bd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / agentlens_io-0.11.0-py3-none-any.whl

Download URL agentlens_io-0.11.0-py3-none-any.whl
Size 30.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ab05390beafcd84f1a3c9130e28789f02d0c1ddf947cb411992d9679e9075300
BLAKE2b-256 checksum
How to use checksums
f017ce90f93040925af4741e00a274e0198abd836b293ec84ac4dd7e738412f7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Release history Release notifications | RSS feed

This release

0.11.0 This release

2 release files

0.9.0

2 release files

0.8.0

2 release files

0.7.0

2 release files

0.6.0

2 release files

0.5.0

2 release files

0.4.0

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page