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.
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": "..."}
{"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": "..."}
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.9.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| agentlens_io-0.9.0.tar.gz | 34.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| agentlens_io-0.9.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 60.8 kB
Release files / agentlens_io-0.9.0.tar.gz
| Download URL | agentlens_io-0.9.0.tar.gz |
|---|---|
| Size | 34.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f2b6681239a7e38e3170d764f125d3c364eccd3d36002cea56b8f0f3c45b3d09
|
|
BLAKE2b-256 checksum How to use checksums |
1f190db5d968740fb59ef1505cd8792ad21cebd330312c2deb3388ac371d55ff
|
| 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.9.0-py3-none-any.whl
| Download URL | agentlens_io-0.9.0-py3-none-any.whl |
|---|---|
| Size | 26.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
75fefa3082a14f3fb8180303be26a4cc3868317bad81b90db0f07e2053f9fa52
|
|
BLAKE2b-256 checksum How to use checksums |
e55acf515370906150d3f430f869390ecff47edfa23bdcb91521bed9103e3fd3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|