Skip to main content

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.

CI python dependencies license

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 colorless CLI — 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:

  1. Can you stop the catastrophic action before it happens? (refund $1M, drop a table, email every customer)
  2. 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:

  • MCPcolorless.integrations.mcp + examples/mcp_server.py (FastMCP). Gate + seal every tool an MCP client calls. pip install mcp.
  • LangChain / LangGraphguard_tools(cl, tools) in colorless.integrations.langchain + examples/langchain_agent.py. One line wraps your entire tool list. pip install langchain-core.
  • CrewAIcolorless.integrations.crewai.guard_tools(cl, tools) — wrap a crew's tools.
  • LlamaIndexcolorless.integrations.llamaindex.guard_tools(cl, tools) — wraps FunctionTools.
  • 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.guard on a function (sync or async), or with 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.
  • CLIcolorless verify | tail | anchor | verify-anchor for 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

colorless_audit-1.0.1.tar.gz (64.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

colorless_audit-1.0.1-py3-none-any.whl (51.1 kB view details)

Uploaded Python 3

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

Hashes for colorless_audit-1.0.1.tar.gz
Algorithm Hash digest
SHA256 bc51c8ab6a9b6b83033848cdb6b60e75be0f8b7a2b3ba22875b264965b0fb72a
MD5 2c004744040be447ba28b100085b0343
BLAKE2b-256 5ce6c5e80fdbb2c2fdbb8ab596f264e6ca7f93546d986be9571b39dfbe5d0dba

See more details on using hashes here.

File details

Details for the file colorless_audit-1.0.1-py3-none-any.whl.

File metadata

File hashes

Hashes for colorless_audit-1.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 c0c9ea3cc9cc0b8feae47341251f1166e8aa2533b7bc31e415a360958711504c
MD5 7be799e85a58212b4e62b9c4ee138996
BLAKE2b-256 93616999a1ecbbc81a9fc392f7af3d0fb2be67a1201ff5d6b16386884b08dd38

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page