SudoAgent is the smallest possible boundary that gives you verifiable, fail-closed control (policy/approvals/budgets) plus tamper-evident evidence and receipts—so you don’t have to re-implement and re-audit that stack in every service.
Version: 2.0.0 (v2 ledger/verification)
- Teams letting agents or automation call real systems (payments, prod data, infra) and need a fail-closed boundary.
- Engineers who want proof an action was authorized (and, if required, approved) before it executed.
- Security/ops who need tamper-evident evidence they can verify later.
- If you already have budgets, approvals, tamper-evident logging, receipts, and verification wired correctly across your services, you don’t need SudoAgent.
- It is: a synchronous authorization boundary around tool/function calls with deterministic redaction and a tamper-evident ledger.
- It is not: an agent orchestrator, scheduler, or sandbox. Policies are Python today for determinism; signed policy bundles (OPA/Rego or YAML) are a roadmap item for code-less changes.
src/sudoagent/— core SDK (engine, ledger backends, budgets, approvals, adapters).examples/— runnable demos (quickstart, workflow demo).tests/— pytest suite (engine, ledger verification, adapters).docs/— guides and references (quickstart, OSS guide, ledger spec, architecture, FAQ).gateway/— reserved for future control-plane work (not present today).
- Quick intro:
docs/quickstart.md - OSS overview:
docs/oss_guide.md - How the ledger works:
docs/v2_ledger.md - Architecture:
docs/architecture.md - Operations/FAQ:
docs/faq.md
SudoAgent wraps a function call and enforces one of three outcomes:
- allow: execute immediately
- deny: block the call, raise
ApprovalDenied - require_approval: request approval; executes only if approved
Defaults (dev-friendly / quickstart):
- Audit log:
sudo_audit.jsonl(operational record; not tamper-evident) - Ledger:
sudo_ledger.jsonl(tamper-evident but dev-only; single-writer, local) - Budgets/approvals: in-memory
Recommended default for real use or multi-process on one host:
- Evidence:
SQLiteLedger(Path("sudo_ledger.sqlite"))(WAL, fsync) - Budgets:
from sudoagent.budgets import persistent_budgetand passbudget_manager=persistent_budget("budgets.sqlite", agent_limit=..., tool_limit=...) - Approvals:
approval_store=SQLiteApprovalStore(Path("approvals.sqlite"))for durable state/timeouts
Sharding guidance: use one ledger file per domain/env (e.g., ledgers/prod-payments.sqlite, ledgers/prod-support.sqlite) to avoid a global mutex and keep verification fast.
- You create a
SudoEnginewith a required policy. - You decorate functions with
@sudo.guard()or callsudo.execute(func, ...). - The engine evaluates the policy, optionally invokes the approver, writes the decision to the ledger + audit log, and then executes (or denies).
Key behavior:
- Decision logging happens before execution and is fail-closed. If logging fails, execution is blocked and
AuditLogErroris raised. - Outcome logging happens after execution and is best-effort. Logging failures do not affect the return value.
- Approved actions produce two audit entries (decision + outcome). Denied actions produce one (decision only).
- Entries for the same call are linked by
request_id(UUID4). - Audit entries include timestamp, action, decision, reason, and safe representations of args/kwargs.
See docs/architecture.md for the full execution flow and audit semantics.
pip install sudoagentOptional extras:
- Signing / receipts:
pip install "sudoagent[crypto]" - Adapters:
pip install "sudoagent[langchain]",pip install "sudoagent[crewai]",pip install "sudoagent[autogen]" - SQLite helpers (if you prefer explicit deps): standard library only; no extra install required.
Dev install:
python -m venv .venv
source .venv/bin/activate # Linux/macOS
# PowerShell: .\.venv\Scripts\Activate.ps1
pip install -e ".[dev]"Run the demo:
python examples/quickstart.pyWhat happens:
- A low-value refund is allowed automatically.
- A high-value refund triggers an interactive approval prompt.
- Decisions/outcomes are written to
sudo_audit.jsonlandsudo_ledger.jsonl(dev-only path for the demo).
Prefer SQLite for anything real:
SUDOAGENT_LEDGER=sqlite python examples/workflow_demo.py # or instantiate SQLiteLedger in your codeFor a 5-minute walkthrough and production checklist, see docs/quickstart.md. OSS guide: docs/oss_guide.md. FAQ / gotchas: docs/faq.md (includes asyncio guidance).
Full workflow demo (approval + budgets + verify):
SUDOAGENT_AUTO_APPROVE=1 python examples/workflow_demo.py
sudoagent verify sudo_ledger.jsonlv2 ledger demo (decision_hash + verification):
python examples/v2_demo.py
sudoagent verify sudo_ledger.jsonl
# Or JSON output
sudoagent verify sudo_ledger.jsonl --json(The demo writes sudo_ledger.jsonl in the current directory; delete it between runs if you want a fresh ledger.)
CLI export/filter/search:
sudoagent export sudo_ledger.jsonl --format json
sudoagent filter sudo_ledger.jsonl --request-id <id>
sudoagent search sudo_ledger.jsonl --query refund_userSigning and receipts:
pip install "sudoagent[crypto]"
sudoagent keygen --private-key keys/private.pem --public-key keys/public.pem
sudoagent verify sudo_ledger.jsonl --public-key keys/public.pem
sudoagent receipt sudo_ledger.jsonl --request-id <id>To run non-interactively (CI/demo):
SUDOAGENT_AUTO_APPROVE=1 python examples/quickstart.pyOn Windows PowerShell:
$env:SUDOAGENT_AUTO_APPROVE="1"; python examples/quickstart.pyexamples/quickstart.py: allow + approval using JSONL defaults.examples/v2_demo.py: shows the v2 ledger hashing/verification flow.examples/workflow_demo.py: approval + budgets + verification; setSUDOAGENT_AUTO_APPROVE=1to auto-approve in CI.
from sudoagent import ApprovalDenied, Context, Decision, PolicyResult, SudoEngine
class HighValueRefundPolicy:
def evaluate(self, ctx: Context) -> PolicyResult:
refund_amount = ctx.kwargs.get("refund_amount", 0)
if refund_amount <= 500:
return PolicyResult(decision=Decision.ALLOW, reason="within limit")
return PolicyResult(decision=Decision.REQUIRE_APPROVAL, reason="over limit")
policy = HighValueRefundPolicy()
sudo = SudoEngine(policy=policy)
@sudo.guard()
def refund_user(user_id: str, refund_amount: float) -> None:
print(f"Refunding {refund_amount} to {user_id}")
refund_user("user_1", refund_amount=10.0)
try:
refund_user("user_2", refund_amount=1500.0)
except ApprovalDenied as e:
print(f"Denied: {e}")- Context: captures the function call (action name, redacted args/kwargs, metadata).
- Policy: returns
ALLOW,DENY, orREQUIRE_APPROVALwith a reason. - Approver: handles the approval step. Default is
InteractiveApprover(terminal y/n). - AuditLogger: writes audit entries. Default is
JsonlAuditLogger. - Budgets: optional rate limits via
BudgetManager(passbudget_costtoexecute/guardfor spend accounting). Usepersistent_budget(...)for durable counters. - Fail-closed: if policy, approval, or decision logging fails, execution is blocked.
- decision_hash: SHA-256 over canonical decision payload (request_id, intent, parameters, actor, policy_hash).
- policy_id: stable policy identifier (class name by default).
- policy_hash: SHA-256 over canonicalized policy identifier (class name by default).
- Ledger schema: entries include
schema_version,ledger_version,agent_id,policy_id,policy_hash, and redacted args/kwargs metadata. - Ledger verification:
sudoagent verify <ledger_path>checks schema/ledger versions, hash chain, and decision_hash references.
Stable, searchable reason codes are emitted in decision metadata and ledger entries:
POLICY_ALLOW_LOW_RISKPOLICY_DENY_HIGH_RISKPOLICY_REQUIRE_APPROVAL_HIGH_VALUEPOLICY_EVALUATION_FAILEDBUDGET_EXCEEDED_AGENT_RATEBUDGET_EXCEEDED_TOOL_RATEBUDGET_EVALUATION_FAILEDAPPROVAL_DENIEDAPPROVAL_PROCESS_FAILEDLEDGER_WRITE_FAILED_DECISION
- Threat model: see THREAT_MODEL.md.
- Security policy: see SECURITY.md.
- Rich markup in approval prompts is escaped to prevent terminal injection.
- Redaction is centralized and applied before policy evaluation, approval prompts, and ledger hashing.
- Audit logging redacts sensitive key names (
api_key,token,password, etc.) and values (JWT-like strings,sk-prefixes, PEM blocks). - Decision logging failures raise
AuditLogErrorand block execution. Outcome logging failures do not block. - Denied actions log the decision only. Approved actions log decision and outcome, linked by
request_id. - SudoAgent is not a sandbox; protect the host and secrets separately.
Example audit entries:
{"event":"decision","request_id":"...","action":"...","decision":"allow","reason":"within limit",...}
{"event":"outcome","request_id":"...","outcome":"success",...}Limitations:
- This is not a sandbox. Side effects inside the guarded function are not prevented.
- The default audit log (
sudo_audit.jsonl) is not tamper-evident and is intended for single-process use. - The default JSONL ledger (
sudo_ledger.jsonl) is single-writer; for multi-process on one host, useSQLiteLedger.
SudoAgent is designed for dependency injection:
- Implement
Policy.evaluate(ctx) -> PolicyResult. - Implement
Approver.approve(ctx, result, request_id) -> bool(Slack/UI/etc.). - Implement
Ledgerfor custom evidence stores. - Implement
AuditLoggerfor operational logging sinks.
Notes:
- Policy is required at construction. Pass
AllowAllPolicy()explicitly for permissive mode. InteractiveApproveris intended for local development. For production, implement a custom approver.- For persistence: prefer
SQLiteLedger,persistent_budget, andSQLiteApprovalStorein multi-process or long-running scenarios.
Adapters:
- LangChain:
pip install "sudoagent[langchain]"+ docs/adapters.md - CrewAI:
pip install "sudoagent[crewai]" - AutoGen:
pip install "sudoagent[autogen]"
See ROADMAP.md for a short, best-effort plan.
Big picture:
- OSS “embedded engine” stays: synchronous guard + tamper-evident ledger, local by default, SQLite recommended for multi-process.
- Future “gateway/control plane” (commercial) will layer on: multi-host ledger, richer approvals, SIEM/export integrations, hosted ops. No timelines or promises here; OSS remains usable on its own.
We support the latest minor release line. See SUPPORT.md for details.
Small PRs are welcome. See CONTRIBUTING.md and CODE_OF_CONDUCT.md.
pytest -q
ruff check .
mypy srcMIT License. See LICENSE.
