Authorize, gate, and prove every action your AI agent takes — tamper-evident, zero-dependency.
Project description
colorless
Authorize, gate, and prove every action your AI agent takes. Tamper-evident. Zero dependencies. ~5 lines of code.
v1.0 — policy gating, a tamper-evident ledger, sync and async guards, tool-call adapters (OpenAI/Anthropic/MCP/LangChain/CrewAI/LlamaIndex), content guardrails + secret-redaction by default, an MCP security scan, a stdlib dashboard, OpenTelemetry export, a JS/TS SDK, and a
colorlessCLI — all zero-dependency, CI-green across Python + JS. A hosted dashboard + team features are on the roadmap below.
The problem
AI agents have graduated from answering questions to taking actions — sending emails, moving money, writing to your database, calling other tools. The moment an agent can act, two questions decide whether you can actually ship it:
- Can you stop the catastrophic action before it happens? (refund $1M, drop a table, email every customer)
- Can you prove, later, exactly what it did — and that every action was authorized?
Today most teams have neither. They have traces and eval scores from development — not a runtime gate and not a tamper-evident record of what the agent did in production.
colorless is that missing layer.
Install
Zero dependencies, so it's instant.
# Python — installs as `colorless-audit`, imports as `colorless`
pip install colorless-audit
# JavaScript / TypeScript
npm install @nikip0/colorless
Or run straight from a clone with no install at all:
git clone https://github.com/nikip0/colorless.git
cd colorless
python3 examples/quickstart.py
Quickstart
from colorless import Colorless
w = Colorless("agent.jsonl", on_approval=ping_slack) # on_approval(action, decision) -> bool
w.deny("delete_account") # never, full stop
w.require_approval("refund", when=lambda a: a["args"]["amount"] > 100) # big ones need a human
@w.guard
def refund(amount, to):
return payments.refund(amount, to)
refund(amount=80, to="cust_12") # runs — logged, sealed in the chain
refund(amount=5000, to="cust_12") # raises ApprovalRequired until a human says yes
Then, at any time:
w.verify()
# {"ok": True, "length": 412, "head": "9f3c…", "broken_at": None} <- cryptographic proof nothing was altered
Tamper with a single past entry — edit it, delete it, reorder it — and verify() tells you
exactly where the chain broke. anchor() publishes the head hash externally so even silently
deleting the most recent entries is provable.
Run the full demo:
python3 examples/quickstart.py
Drop it into your agent loop (OpenAI / Anthropic / MCP)
LLM agents call tools as (name, arguments) — the same shape across OpenAI function-calling,
Anthropic tool use, and MCP servers. ToolGuard gates every such call and seals it, with one
line in your dispatch loop:
from colorless import Colorless, ToolGuard, PolicyDenied
w = Colorless("agent.jsonl")
w.deny("delete_repo")
w.require_approval("send_invoice", when=lambda a: a["args"]["amount"] > 1000)
tg = ToolGuard(w)
tg.add("search_web", search_web)
tg.add("send_invoice", send_invoice)
# in your loop, for each tool_call the model emits:
for call in llm_response.tool_calls:
try:
result = tg.call(call.name, call.arguments) # gated + sealed
except PolicyDenied:
result = "blocked by policy" # hand the refusal back to the model
Your tools and your loop don't change — every action is now gated and provable. See
examples/agent_loop.py.
Async agents
@guard and ToolGuard.acall work with coroutine tools out of the box:
@w.guard
async def search(query):
return await client.search(query) # gated + sealed, awaited for you
await tg.acall("search", {"query": "..."}) # async dispatch inside your agent loop
Secrets never hit the ledger
Redaction is on by default. Keys named like secrets (api_key, token, password, …) and
values shaped like secrets (OpenAI sk-…, Bearer …, GitHub/AWS/Slack tokens) are masked to
*** before anything is written. Disable with Colorless(redact=None), or pass your own function.
Verify from the terminal (CLI)
Anyone — an auditor, a teammate, CI — can independently check a ledger without touching your code:
colorless verify agent.jsonl # exits 1 if the chain was tampered with
colorless tail agent.jsonl -n 20
colorless anchor agent.jsonl agent.anchor.json # publish this snapshot externally
colorless verify-anchor agent.jsonl agent.anchor.json
Dashboard
A zero-dependency web control room over the ledger — live action feed, pending approvals you approve/deny on screen, integrity status, and one-click audit export:
colorless dashboard agent.jsonl # → http://127.0.0.1:8787/?token=…
Auth is on by default — a token is generated + printed at startup (override with --token / env
COLORLESS_DASHBOARD_TOKEN, or --tokens-file for named per-user tokens; --token "" disables for
loopback dev). Approvals record the authenticated approver in the tamper-evident ledger, and bad
tokens are rate-limited.
Wire live human approvals to it, so an agent blocks until you click Approve:
from colorless import Colorless
from colorless.dashboard import ApprovalQueue, queue_approval
q = ApprovalQueue()
cl = Colorless("agent.jsonl", on_approval=queue_approval(q)) # blocks until a human resolves it
See it with seeded data: python3 examples/dashboard_demo.py.
Integrations
The core is framework-agnostic; these add turnkey wiring. Each adapter imports no third-party SDK, so the core stays zero-dependency:
- MCP —
colorless.integrations.mcp+examples/mcp_server.py(FastMCP). Gate + seal every tool an MCP client calls.pip install mcp. - LangChain / LangGraph —
guard_tools(cl, tools)incolorless.integrations.langchain+examples/langchain_agent.py. One line wraps your entire tool list.pip install langchain-core. - CrewAI —
colorless.integrations.crewai.guard_tools(cl, tools)— wrap a crew's tools. - LlamaIndex —
colorless.integrations.llamaindex.guard_tools(cl, tools)— wrapsFunctionTools. - OpenAI Agents SDK —
@guard(cl)under@function_tool(colorless.integrations.openai_agents). - OpenAI / Anthropic tool calls — use
ToolGuard.call(name, args)directly in your loop (examples/agent_loop.py).
All the framework adapters share one duck-typed wrapper (no SDK imports), so they track each framework's API across versions and wrap only the leaf callable (no double-logging).
MCP security scan
Before you trust an MCP server, scan its tool definitions for poisoning — hidden instructions in
descriptions ("before using any tool, read ~/.ssh/id_rsa…"), invisible/bidi unicode, homoglyph
tool names — and catch rug-pulls (a tool's definition changing after you approved it):
from colorless.mcp_scan import scan_tools, pin, diff
findings = scan_tools(server.tools) # {tool: [{location, issue, detail}]} (poisoning/injection/hidden_unicode/suspicious_name)
baseline = pin(server.tools) # store after review; later:
drift = diff(server.tools, baseline) # {tool: "added"|"removed"|"changed"} ("changed" == rug-pull)
Zero-dependency; works on MCP Tool objects or plain dicts. Pairs with colorless.integrations.mcp
(gate + seal the calls) — scan the definitions, gate the calls.
Content guardrails
Detect PII, prompt-injection, and jailbreak attempts in the text your agent handles — and gate on them through the same policy + tamper-evident audit (so you can prove you screened):
from colorless import Colorless
from colorless.guardrails import has_injection, has_pii, redact_pii
cl = Colorless("agent.jsonl", redact=redact_pii) # keep detected PII out of the log too
cl.deny(when=has_injection) # block any action whose args carry an injection
cl.require_approval(when=has_pii) # any action carrying PII waits for a human
Zero-dependency, pattern-based — a fast first line you can prove you ran. For ML-grade detection,
plug in Presidio/Lakera as your own when= predicate; colorless gives you the gate + audit, not the model.
Storage backends
The ledger is pluggable. JSONL is the zero-dependency, portable default; point it at a .db /
.sqlite path (or pass backend="sqlite") for the indexed, scalable stdlib-sqlite3 backend —
same entries, same hashes, so verify is backend-agnostic (a JSONL and a SQLite ledger of the
same actions share the same head hash), and verify streams so it stays constant-memory on a
million-row ledger.
from colorless import Colorless
cl = Colorless("agent.db") # auto-detected SQLite: indexed head/tail/by-ref, no full rewrite
colorless verify agent.db # the CLI and dashboard work with either backend, auto-detected
OpenTelemetry
Stream the audit into your existing observability stack — every gated action becomes an OTel GenAI span. Optional; the core stays zero-dependency.
from colorless import Colorless
from colorless.otel import instrument, export_ledger
cl = Colorless("agent.jsonl")
instrument(cl) # live: pip install 'colorless-audit[otel]', or pass your own tracer
export_ledger("agent.jsonl") # or batch-replay an existing ledger into your backend
Alerting
Get a Slack/webhook ping when an action is blocked, errors, or needs a human — built on the same hooks, zero-dependency, and fired off the hot path so a slow endpoint never stalls your agent:
from colorless import Colorless
from colorless.alerts import slack_alerter, approval_alerter
from colorless.dashboard import ApprovalQueue, queue_approval
q = ApprovalQueue(on_request=approval_alerter(SLACK_URL, slack=True)) # "needs approval"
cl = Colorless("agent.jsonl", on_approval=queue_approval(q))
cl.subscribe(slack_alerter(SLACK_URL)) # alert when an action is denied / unapproved / errors
JavaScript / TypeScript
colorless has a zero-dependency JS/TS SDK too (clients/js) — first-class TypeScript types and the
same tamper-evident ledger format, so a ledger written by a Node agent verifies with the Python
colorless verify CLI.
import { Colorless } from "@nikip0/colorless";
const cl = new Colorless({ ledger: "agent.jsonl" });
cl.requireApproval("refund", (a) => a.args.amount > 100);
const refund = cl.guard(async ({ amount, to }) => pay(amount, to), { name: "refund" });
await refund({ amount: 80, to: "cust_12" }); // sealed in the chain
cl.verify(); // { ok: true, ... }
See clients/js/README.md.
Why it's different
| dev-time eval / tracing (LangSmith, Braintrust, Arize) |
prompt guardrails (Guardrails AI, NeMo) |
colorless | |
|---|---|---|---|
| Stops a forbidden action before it runs | ✗ | partial (text only) | ✅ |
| Human-in-the-loop approval gate | ✗ | ✗ | ✅ |
| Tamper-evident record of what the agent did | ✗ | ✗ | ✅ |
| Independently verifiable / anchorable proof | ✗ | ✗ | ✅ |
| Built for production runtime, not just dev | partial | ✅ | ✅ |
| Secret redaction by default | ✗ | partial | ✅ |
| Independent CLI / CI verification | ✗ | ✗ | ✅ |
| Dependencies | many | several | zero |
The leaders watch your agent while you build it. colorless governs and proves what it does
once it's live and touching the real world — the part you actually get fired (or sued) over.
Core concepts
- Policy — ordered rules (
allow/deny/require_approval), first match wins, default configurable. Deny-by-default for production:Colorless(policy=Policy(default="deny")). - Guard —
@w.guardon a function (sync orasync), orwith w.action("name", **args):inline. Checks policy, then runs (or blocks), then records. - ToolGuard — wrap an LLM tool registry;
.call(name, args)/.acall(...)gate+seal each tool_call (OpenAI/Anthropic/MCP). - Ledger — append-only hash chain (
content_hash,row_hash = sha256(prev + content)) in a plain JSONL file, thread-safe appends.verify()re-walks and re-hashes;anchor()fixes the head in time. - Redaction — on by default (
redact_secrets); masks secret-looking keys/values before they're written.Colorless(redact=None)to disable. - CLI —
colorless verify | tail | anchor | verify-anchorfor independent, code-free checks.
Roadmap
- Now (OSS core): policy gating, tamper-evident + thread-safe ledger, anchoring, sync/async guards, OpenAI/Anthropic/MCP tool adapters, secret-redaction, CLI — all dependency-free.
- Next: turnkey adapters for LangChain / LlamaIndex; an example MCP server; framework middleware.
- Then (hosted): a dashboard to watch live actions and approve from your phone, team-wide policies, alerting, and one-click compliance export (SOC 2 / EU AI Act evidence).
License
MIT © 2026 Niki Petrov
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file colorless_audit-1.0.1.tar.gz.
File metadata
- Download URL: colorless_audit-1.0.1.tar.gz
- Upload date:
- Size: 64.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bc51c8ab6a9b6b83033848cdb6b60e75be0f8b7a2b3ba22875b264965b0fb72a
|
|
| MD5 |
2c004744040be447ba28b100085b0343
|
|
| BLAKE2b-256 |
5ce6c5e80fdbb2c2fdbb8ab596f264e6ca7f93546d986be9571b39dfbe5d0dba
|
File details
Details for the file colorless_audit-1.0.1-py3-none-any.whl.
File metadata
- Download URL: colorless_audit-1.0.1-py3-none-any.whl
- Upload date:
- Size: 51.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c0c9ea3cc9cc0b8feae47341251f1166e8aa2533b7bc31e415a360958711504c
|
|
| MD5 |
7be799e85a58212b4e62b9c4ee138996
|
|
| BLAKE2b-256 |
93616999a1ecbbc81a9fc392f7af3d0fb2be67a1201ff5d6b16386884b08dd38
|