Quickstart
From pip install to your first tamper-evident audit row in under five minutes.
Current version: 0.6.0
v0.6.0 is the current release. It adds Ed25519 per-row audit signing, HMAC chain key rotation, an Article 12 evidence export endpoint, and the wrapper coverage registry on top of everything in v0.5.4. Fresh installs now run alembic upgrade head automatically from governance migrate, so one command takes a new database all the way to HEAD. v0.5.x pins still work, but new deployments should target >=0.6.0.
Install the SDK
pip install "code-atelier-governance>=0.6.0"Set your audit secret
The audit secret is a 32+ byte key used to compute the HMAC chain. Generate one and store it in your secrets manager — same place as your DB password. Never commit it to git.
export GOVERNANCE_AUDIT_SECRET=$(python -c 'import secrets; print(secrets.token_hex(32))')Apply the schema
The SDK ships DDL files inside the package. The CLI applies all schemas (audit, cost, gates, loop, presence, console) in one command. Idempotent — safe to run multiple times.
governance migrate --database-url $DATABASE_URLInitialize the SDK and log your first event
Three lines to instantiate. The SDK connects to your Postgres, starts the background flush loop, and is ready to accept audit events. Every module is opt-in via a GovernanceConfig flag — when a flag is False, the module is not constructed and accessing sdk.<module> raises AttributeError (not a silent no-op). Pass enable_loop=False or enable_presence=False for lightweight deployments.
import os
import asyncio
from codeatelier_governance import GovernanceSDK, AuditEvent
async def main():
async with GovernanceSDK(database_url=os.environ["DATABASE_URL"]) as sdk:
record = await sdk.audit.log(
AuditEvent(agent_id="my-first-agent", kind="hello.world")
)
print(f"Logged: {record.event_id} (hmac: {record.hmac[:16]}...)")
asyncio.run(main())Enforcement coverage warning
At sdk.start(), the SDK emits a structlog warning named no_wrappers_registered if no wrap_openai or wrap_anthropic has been registered. This is the only signal that enforcement is not covering your LLM calls. An unwrapped client bypasses every scope, budget, and audit gate the SDK offers.
Do NOT silence this warning with warn_on_no_wrappers=False unless you are running an intentionally wrapper-free deployment (audit-only, gate-only, or tests). Silencing it in a production deployment that expects wrappers hides a misconfiguration.
Next steps
The SDK is now logging tamper-evident audit events. These optional steps add enforcement gates and the console GUI.
Add enforcement gates (optional)
Register scope, budget, and loop policies. Now the SDK blocks disallowed tools, denies calls that exceed the budget, and kills runaway loops — before the LLM fires.
from codeatelier_governance import ScopePolicy, BudgetPolicy
from codeatelier_governance.loop import LoopPolicy
sdk.scope.register(ScopePolicy(
agent_id="my-first-agent",
allowed_tools=frozenset({"search", "summarize"}),
hidden_tools=frozenset({"admin_reset"}), # removed from LLM context
))
sdk.cost.register(BudgetPolicy(
agent_id="my-first-agent",
per_session_usd=0.50,
per_session_seconds=300, # 5-minute session time limit
per_agent_usd_daily=5.00,
))
# sdk.loop only exists if the SDK was initialized with enable_loop=True (the default).
# If you set enable_loop=False in Step 4, remove this block — sdk.loop raises AttributeError.
sdk.loop.register(LoopPolicy(
agent_id="my-first-agent",
window_seconds=60, # sliding window
max_calls=5, # max 5 calls to the same tool
action="raise", # kill the loop (or "log" to just observe)
))
# Pre-call enforcement:
await sdk.scope.check(agent_id="my-first-agent", tool="search") # passes
await sdk.cost.check_or_raise("my-first-agent", session_id) # passes if under budget
await sdk.loop.record_call("my-first-agent", session_id, "search") # records + checks
# ... make your LLM call ...
# Auto-compute USD from model name (24 models built in):
await sdk.cost.track_usage("my-first-agent", session_id,
model="gpt-4o", input_tokens=100, output_tokens=50)
# Or pass USD manually:
await sdk.cost.track("my-first-agent", session_id, tokens=150, usd=0.003)See it in the console (optional)
The governance console is a read-only dashboard that visualizes your audit chain, cost tracking, and pending approvals.
pip install "code-atelier-governance[console]"
export GOVERNANCE_DATABASE_URL=postgresql://...
export GOVERNANCE_AUDIT_SECRET=...
python -m codeatelier_governance.console
# Open http://localhost:8766/docs for the API, or connect the Next.js frontendUsing Flask or Django?
The sync wrapper runs a background event loop in a daemon thread. Every async method becomes a blocking sync call. Same API, no await.
Use the sync wrapper (optional)
GovernanceSDKSync wraps the async SDK for synchronous frameworks. All module methods (audit.log, scope.check, cost.check_or_raise) work without await.
from codeatelier_governance import GovernanceSDKSync, AuditEvent
with GovernanceSDKSync(database_url=os.environ["DATABASE_URL"]) as sdk:
sdk.audit.log(AuditEvent(agent_id="my-agent", kind="tool.call"))
sdk.scope.check("my-agent", tool="send_email") # blocking
sdk.cost.check_or_raise("my-agent", session_id) # blockingWhat just happened?
- Every
audit.log()call wrote an HMAC-chained row to your Postgres. Each row's HMAC covers its own fields plus the previous row's HMAC — tampering with any past row breaks every subsequent row's verification. - The scope check would have raised
ScopeViolationif you'd tried a tool not in the whitelist. The violation gets audit-logged automatically. - The cost check would have raised
BudgetExceededif the session USD cap, daily cap, or session time limit was hit. If the cost store is unreachable, the call is denied (fail-closed by default). - The loop check would have raised
LoopDetectedif the agent called the same tool more than 5 times in 60 seconds. Runaway loops are killed before they burn budget. - The
track_usage()call auto-computed USD from the model name using built-in pricing for 24 models. No manualusd=needed. - None of this added any infrastructure. Just your existing Postgres.