RootSign — tamper-evident provenance logging for AI agents (powered by Providex AI)
Project description
RootSign
Tamper-evident provenance logging for production AI agents.
What is RootSign?
RootSign is a Providex AI product — the agent capture layer of the Providex AI Agent Accountability Platform.
When AI agents take actions in production — calling tools, hitting APIs, writing to databases — there is no built-in audit trail. If something goes wrong (a wrong refund, a leaked PII record, a malformed deployment), there is no way to prove what the agent did, in what order, on whose authorization, or whether the record has been tampered with after the fact.
RootSign solves this. Each agent action is captured as an Action record containing a SHA-256 hash of the previous action — a cryptographic hash chain that makes the record tamper-evident. Modify any record after the fact and rootsign verify detects it.
Compliance-grade audit trails. Zero changes to your agent code.
Status
v0.1.5. LangGraph + CrewAI integrations, a framework-agnostic MCP proxy, rootsign verify CLI, PII redaction, human-in-the-loop checkpoints, opt-in decision capture (PRD-19 / ADR-008), and opt-in SDK micro-batching are all shipping.
| Phase | Scope | Status |
|---|---|---|
| 0 | Data model + storage + ingest handler | ✅ Complete |
| 1 | Python SDK — @rootsign.trace, LangGraph + CrewAI + MCP proxy, rootsign verify CLI, redaction, HiTL checkpoint, decision capture, micro-batching |
✅ v0.1.5 |
| 2 | Hosted ingest backend + compliance dashboard | Planned |
| 3 | Policy enforcement + incident workflow | Planned |
| 4 | Cross-platform governance | Planned |
Quickstart — LangGraph
1. Install
Python 3.11 or 3.12 recommended. RootSign itself supports 3.11+, but the
[crewai]extra currently lags on 3.13/3.14 wheels. If you hitNo matching distribution found for crewai, switch to Python 3.12 and reinstall.
pip install rootsign[langgraph]
Start PostgreSQL + TimescaleDB locally and apply the schema:
rootsign-admin start-db # docker run timescale/timescaledb:latest-pg16
rootsign-admin init # alembic upgrade head
start-db wraps a single docker run so you don't need to clone the repo. If you have cloned it, docker-compose up -d db is the equivalent developer path. Both reuse the same rootsign-timescaledb container name and rootsign_pgdata volume — pick either, not both.
2. Register your agent (one-time setup)
import asyncio
from rootsign import register_agent, AgentEnvironment, AgentRiskTier, AgentFramework
agent = asyncio.run(register_agent(
name="my-invoice-agent",
owner="platform-team",
environment=AgentEnvironment.PRODUCTION,
risk_tier=AgentRiskTier.HIGH,
framework=AgentFramework.LANGGRAPH,
))
print(agent.agent_id)
3. Instrument your tools
import rootsign
from rootsign import LocalIngestClient
from rootsign.database import AsyncSessionLocal
from langchain_core.tools import tool
from langgraph.prebuilt import ToolNode
@tool
def send_invoice(customer_id: str, amount: float) -> str:
"""Send an invoice to a customer."""
return "sent"
async def run_graph(agent_id):
async with AsyncSessionLocal() as db:
client = LocalIngestClient(db=db)
async with rootsign.session(agent_id=agent_id, client=client) as ctx:
tools = rootsign.wrap_tools([send_invoice], ctx=ctx, client=client)
tool_node = ToolNode(tools)
# ...build and run your graph as normal
await db.commit()
Every tool call now produces a tamper-evident Action record on the hash chain.
4. Verify the chain
$ rootsign verify 660e8400-e29b-41d4-a716-446655440001
VALID ✓ — 3 records, chain intact
Session: 660e8400-e29b-41d4-a716-446655440001
Exit code is 0 for VALID, 1 for TAMPERED. Use --local <path.jsonl> for offline JSONL session files (no DB required).
If a record was modified, the verifier names the broken link:
$ rootsign verify 660e8400-e29b-41d4-a716-446655440001
TAMPERED ✗ — chain broken at record #2
Detail: self_hash mismatch on action <action_id>
Session: 660e8400-e29b-41d4-a716-446655440001
WARNING: This session log may have been tampered with.
See docs/framework-support.md for the version matrix and integration notes. A full runnable LangGraph example (ReAct agent, three instrumented tools, OpenAI-backed) lives in examples/langgraph-invoice-agent.
Decision capture (opt-in)
Record the why before each tool call — foundational for Phase 2 session replay. Off by default; opt in deliberately with ROOTSIGN_CAPTURE_DECISIONS=true.
import os
os.environ["ROOTSIGN_CAPTURE_DECISIONS"] = "true"
async with rootsign.session(agent_id=agent.agent_id, client=client) as ctx:
# Record what the agent decided before calling the tool.
await ctx.record_decision(
selected_action="send_invoice",
reasoning_summary="Amount within policy; recipient verified.",
confidence=0.97,
ingest_client=client,
)
tools = rootsign.wrap_tools([send_invoice], ctx=ctx, client=client)
await tools[0].ainvoke({"customer_id": "acme", "amount": 1500.0})
# The Action record now carries decision_id linking it to the reasoning above.
Depth controls how much reasoning is persisted, via ROOTSIGN_REASONING_DEPTH:
| Value | What's stored |
|---|---|
minimal |
selected_action + confidence only |
summary (default) |
+ reasoning_summary truncated to 500 chars |
full |
+ reasoning_summary truncated to 10,000 chars + alternatives_considered |
Calling ctx.record_decision() when the flag is off is a silent no-op — safe to ship in capture-on and capture-off environments without conditionals at the call site. One Decision links to one Action; the pending slot is single and cleared after the next tool call consumes it. Decisions are not in the hash chain (ADR-008) — verify_chain is unchanged.
Quickstart — CrewAI
CrewAI integration is the same shape — wrap the tool list at construction time.
pip install rootsign[crewai]
import rootsign
from crewai import Agent
from crewai.tools import tool
@tool("send_invoice")
def send_invoice(customer_id: str, amount: float) -> str:
"""Send an invoice to a customer."""
return "sent"
async def run_crew(agent_id):
async with AsyncSessionLocal() as db:
client = LocalIngestClient(db=db)
async with rootsign.session(agent_id=agent_id, client=client) as ctx:
wrapped = rootsign.wrap_crewai_tools(
[send_invoice], ctx=ctx, client=client
)
agent = Agent(
role="Invoicing assistant",
goal="Send invoices",
tools=wrapped,
)
# ...run your crew as normal
await db.commit()
Tested against CrewAI 0.28, 0.40, and 1.x (see CI matrix).
Quickstart — MCP proxy (any framework)
Instead of a per-framework adapter, RootSign can intercept at the Model Context Protocol layer. Point your agent's MCP client at the RootSign proxy and every tools/call becomes a tamper-evident ACTION_RECORD — any MCP-compatible agent is instrumented with zero framework code.
pip install rootsign[mcp]
import rootsign
import uvicorn
from rootsign.mcp.proxy import create_proxy_app
async def serve_proxy(agent_id):
async with AsyncSessionLocal() as db:
client = LocalIngestClient(db=db)
async with rootsign.session(agent_id=agent_id, client=client) as ctx:
app = create_proxy_app(
upstream_url="http://your-mcp-server:8001/mcp",
client=client,
ctx=ctx,
# require_approval=True # gate every proxied call on human approval
)
# A uvicorn-compatible ASGI app. Point the agent's MCP_SERVER_URL
# here; tools/call is recorded and forwarded, other methods
# (initialize, tools/list, …) pass through unchanged.
await uvicorn.Server(uvicorn.Config(app, host="0.0.0.0", port=8000)).serve()
require_approval=True gates every proxied tool call on a human decision — the same HiTL flow as @rootsign.trace, pausing before the call reaches the upstream server. See ADR-010.
Human-in-the-loop checkpoint
High-risk actions can be gated on a human decision. Pass require_approval=True to @rootsign.trace and the SDK blocks the tool from running until someone approves it via the CLI.
import rootsign
@rootsign.trace(
ingest_client=client,
session_context=ctx,
require_approval=True,
timeout_seconds=300, # 5 minutes
)
async def wire_transfer(account: str, amount: float) -> str:
# This runs ONLY after a human approves.
return execute_transfer(account, amount)
When wire_transfer(...) is called, the SDK inserts an ACTION_RECORD with authorization_status='pending' and waits. An operator approves (or rejects) from another terminal:
$ rootsign approve --list
Pending approvals (1):
<action-id> wire_transfer session=<session-id> submitted=<timestamp>
$ rootsign approve <action-id> --reason "Verified with customer"
✓ Action <action-id> approved.
The decorated function returns normally. Rejection (--reject) raises HiTLRejectedError; a 5-minute timeout raises HiTLTimeoutError and the action's authorization status becomes 'timed_out' (a terminal forensic state distinct from 'human_rejected').
See ADR-007 for the design rationale (poll loop, timeout semantics, race tolerance).
PII redaction
RedactionConfig runs before hashing, so stored input_hash / output_hash values carry no PII signal. Three ready-to-use configs:
from rootsign import StandardPIIConfig, FinancialPIIConfig, HealthcarePIIConfig
# Standard: email, phone, US SSN, credit card, UK NI number
redaction = StandardPIIConfig()
tools = rootsign.wrap_tools(
[send_invoice], ctx=ctx, client=client,
redaction_config=redaction,
)
FinancialPIIConfig adds account / routing / IBAN patterns; HealthcarePIIConfig adds MRN / NPI / DOB. Each accepts extra_rules={...} for domain-specific patterns without subclassing. See ADR-006.
Micro-batching (opt-in)
BufferedIngestClient wraps any ingest client and buffers ACTION_RECORDs in memory, flushing asynchronously — so a long, tool-heavy pipeline doesn't pay a per-call ingest round-trip. Enable it with ROOTSIGN_BUFFERED=true (the factory wraps the transport for you), or wrap explicitly:
from rootsign import BufferedIngestClient, LocalIngestClient
async with BufferedIngestClient(LocalIngestClient(db=db)) as client:
async with rootsign.session(agent_id=agent_id, client=client) as ctx:
... # session() flushes the buffer before SESSION_CLOSE
Only auto-authorized actions are buffered; HiTL, decision, and session records pass through synchronously, so approvals and hash-chain ordering are never deferred. See ADR-009.
Architecture
@rootsign.tracewraps a tool callable and emits anACTION_RECORDenvelope per call. LangGraphBaseTooland CrewAI tools are detected automatically.- MCP proxy —
create_proxy_appintercepts MCPtools/callat the protocol layer, so any MCP-compatible agent is instrumented without a framework adapter (ADR-010). LocalIngestClientis the in-process ingest path for v0.1.x. AHttpIngestClientfor the hosted backend lands in Phase 2.BufferedIngestClientoptionally wraps either for async micro-batching (ADR-009).- Hash chain is per-session: each
Actioncarriesprev_action_hashso reconstructing the chain detects any after-the-fact modification. HiTLCheckpointis an async poll loop that opens its own DB session per cycle — see ADR-007 for the loop-binding rationale.- Storage is PostgreSQL 16 + TimescaleDB 2.14. The
actionstable is a hypertable; the chain stays intact across chunks.
What's next
- Phase 2 cloud backend —
HttpIngestClient+ hosted compliance dashboard. Drop-in replacement forLocalIngestClient;BufferedIngestClientalready removes the per-call round-trip latency it would otherwise add. - MCP audit-log server — expose hash chains as an MCP data source (Mode B) so "auditor agents" can query them in-context.
- Web UI for HiTL — approve/reject pending actions from a browser instead of the CLI.
- AutoGen integration — same duck-typing shape as CrewAI.
Watch the GitHub Issues for the active roadmap.
Contributing
We welcome contributions. See CONTRIBUTING.md for development setup, coding standards, and the PR process. By submitting a contribution, you agree to the CLA.
Open-source community channels and Discord coming soon — for now, GitHub Issues is the canonical place to file bugs and propose features.
License
Apache License 2.0 — see LICENSE and NOTICE.
Security
To report a vulnerability, see SECURITY.md. Do not open a public GitHub issue.
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
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 rootsign-0.1.5.tar.gz.
File metadata
- Download URL: rootsign-0.1.5.tar.gz
- Upload date:
- Size: 837.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5107750509f5a4c7693dd38b6b8cf1f981962594c33cbb81c79de5c187865e8f
|
|
| MD5 |
bbaa8770c1dde6fb768842430e4e53b6
|
|
| BLAKE2b-256 |
d1bfbb748ab0ef2b17194088e96aa8c2bfac7280af42255ca3c050311934e367
|
Provenance
The following attestation bundles were made for rootsign-0.1.5.tar.gz:
Publisher:
release.yml on Providex-AI/rootsign
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
rootsign-0.1.5.tar.gz -
Subject digest:
5107750509f5a4c7693dd38b6b8cf1f981962594c33cbb81c79de5c187865e8f - Sigstore transparency entry: 2332434641
- Sigstore integration time:
-
Permalink:
Providex-AI/rootsign@086e6817b7764eebc3c4dabc9467658587246e32 -
Branch / Tag:
refs/tags/v0.1.5 - Owner: https://github.com/Providex-AI
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@086e6817b7764eebc3c4dabc9467658587246e32 -
Trigger Event:
release
-
Statement type:
File details
Details for the file rootsign-0.1.5-py3-none-any.whl.
File metadata
- Download URL: rootsign-0.1.5-py3-none-any.whl
- Upload date:
- Size: 109.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
43b7170d11d20c90a65872481deb00279c65e307fbca143a8051ddb8bbfc81a8
|
|
| MD5 |
6452cb480a74f99a21b68d4f12b9da1e
|
|
| BLAKE2b-256 |
2288415490fe5865c8355b7983984d26f58a9d0095e58b40ee42211d65f4a806
|
Provenance
The following attestation bundles were made for rootsign-0.1.5-py3-none-any.whl:
Publisher:
release.yml on Providex-AI/rootsign
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
rootsign-0.1.5-py3-none-any.whl -
Subject digest:
43b7170d11d20c90a65872481deb00279c65e307fbca143a8051ddb8bbfc81a8 - Sigstore transparency entry: 2332434787
- Sigstore integration time:
-
Permalink:
Providex-AI/rootsign@086e6817b7764eebc3c4dabc9467658587246e32 -
Branch / Tag:
refs/tags/v0.1.5 - Owner: https://github.com/Providex-AI
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@086e6817b7764eebc3c4dabc9467658587246e32 -
Trigger Event:
release
-
Statement type: