Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

sealog — sealed, immutable audit trail for AI systems

License Python GitHub

Log every model call. Hash every input. Chain every event. Prove what your AI did.


Contents

The problem

Someone will ask what your AI did. A customer, a regulator, your security team, or the incident reviewer at 2am.

If the answer is "give us a few days to dig through logs," you have a problem. If the answer is a query that returns a structured record in seconds, you have an audit trail.

Most teams don't have one. sealog fixes that.

What sealog does

  • Structured audit events for every AI action — who triggered it, which model, what went in, what came out, whether it was allowed
  • Input hashing — never log raw prompts; hash the input, store the output, link to the source
  • Hash-chained events — each event includes the hash of the previous event, making tampering evident (the useful part of a blockchain, without the rest)
  • Policy engine — allow/deny rules evaluated before every action, with the decision logged
  • Queryable — filter by agent, user, time range, cost, latency, decision
  • Two storage backends — append-only JSONL files or SQLite (bring your own by subclassing StorageBackend)
  • Zero dependencies beyond Pydantic v2

Quickstart

pip install -e .
from sealog import AuditLogger, Actor, SQLiteStorage

storage = SQLiteStorage("audit.db")
logger = AuditLogger(storage=storage)

event = logger.log(
    action="model.call",
    actor=Actor(user_id="user_441", agent_id="agent_threat_enrich"),
    model="claude-opus-4",
    model_version="20250514",
    input_data="Summarize the threat report for IOC 192.168.1.1",
    output={"summary": "3 matching IOCs found", "severity": "high"},
    latency_ms=342.0,
    cost_usd=0.0087,
)

print(event.input_hash)      # sha256:a3f8c1... (input is hashed, not stored)
print(event.decision)        # "allow"
print(event.event_hash)      # tamper-evident hash of this event

Three lines to start logging. The input is hashed automatically. The event is chained to the previous one. The output is stored as-is because it's what the user saw.

With a policy engine

from sealog import AuditLogger, Actor, PolicyEngine, PolicyRule, SQLiteStorage

policy = PolicyEngine()

policy.add_rule(PolicyRule(
    name="block_database_delete",
    description="Agents cannot delete from the database",
    action="deny",
    condition=lambda r: r.get("action") == "database.delete",
))

policy.add_rule(PolicyRule(
    name="block_expensive_models",
    description="Deny calls to expensive models without approval",
    action="deny",
    condition=lambda r: r.get("model") in ["o3", "o4-mini"] and not r.get("approved"),
))

storage = SQLiteStorage("audit.db")
logger = AuditLogger(storage=storage, policy=policy)

# This gets denied — and the denial is logged
event = logger.log(
    action="database.delete",
    actor=Actor(user_id="user_441", agent_id="agent_cleanup"),
    model="claude-opus-4",
    model_version="20250514",
    input_data="DELETE FROM customers WHERE inactive = true",
    output="blocked by policy",
    latency_ms=2.0,
)

print(event.decision)         # "deny"
print(event.decision_reason)  # "Agents cannot delete from the database"

Denied events are still logged. The audit trail records what happened, not just what succeeded.

Storage backends

JSONL (FileStorage)

from sealog import FileStorage

storage = FileStorage("audit.jsonl")

Append-only. One JSON object per line. Human-readable. Easy to ship to S3, stream to a SIEM, or grep in an emergency.

SQLite (SQLiteStorage)

from sealog import SQLiteStorage

storage = SQLiteStorage("audit.db")       # persistent
storage = SQLiteStorage(":memory:")       # in-memory (for tests)

INSERT-only — no UPDATE, no DELETE. Queryable with filters pushed down to SQL.

Bring your own

Subclass StorageBackend and implement append, query, and count:

from sealog.storage.base import StorageBackend

class S3Storage(StorageBackend):
    def append(self, event): ...
    def query(self, filters=None): ...
    def count(self, filters=None): ...

Querying the audit trail

from sealog import QueryFilters

# What did agent X do between 2pm and 3pm?
events = logger.query(QueryFilters(
    agent_id="agent_threat_enrich",
    start_time=datetime(2026, 6, 16, 14, 0, tzinfo=timezone.utc),
    end_time=datetime(2026, 6, 16, 15, 0, tzinfo=timezone.utc),
))

# Which actions were denied?
denied = logger.query(QueryFilters(decision="deny"))

# How much did this user's AI usage cost?
expensive = logger.query(QueryFilters(
    user_id="user_441",
    min_cost=0.01,
))

# How many events total?
total = logger.count()

Filters: agent_id, user_id, action, model, decision, start_time, end_time, min_cost, max_cost, min_latency, max_latency.

Hash chaining (tamper evidence)

Every event includes:

  • event_hash — a SHA-256 hash of the event's contents
  • prev_hash — the event_hash of the previous event
Event 1: prev_hash="" , event_hash="a3f8..."
Event 2: prev_hash="a3f8...", event_hash="7b2c..."
Event 3: prev_hash="7b2c...", event_hash="e91d..."

If someone modifies event 2, its hash changes, and event 3's prev_hash no longer matches. Tampering is detectable without complex infrastructure.

This is the useful part of a blockchain — hash chaining for integrity — without the rest.

API reference

Class Purpose
AuditLogger(storage, policy=None) Main entry point. .log(), .query(), .count()
AuditEvent Pydantic model for one audit record
Actor(user_id, agent_id, session_id) Who triggered the action
PolicyEngine(default_action="allow") Evaluates rules against requests
PolicyRule(name, action, condition) One allow/deny rule
PolicyDecision Result of policy evaluation
QueryFilters Filter criteria for querying events
FileStorage(path) JSONL append-only backend
SQLiteStorage(path) SQLite INSERT-only backend
hash_input(data) Hash a string or dict

The principle

If you can edit the audit log, it is not an audit log.

Append-only. Immutable. Hash-chained. If someone asks what your AI did, the answer should take seconds, not days.


Blog post: The AI audit trail: what to log and why it matters

License

MIT — Shubham Katta, 2026.

About

Sealed, immutable audit trail for AI systems. Log every model call, hash inputs, chain events, prove what your AI did.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages