Advanced & self-hosting
The Python SDK and CLI, streaming to your SIEM, performance characteristics, and running the whole stack on your own infrastructure.
Python SDK & CLI
The SDK ships in the repository (sdk/, installable with pip install -e sdk) and wraps the two calls most integrations need:
python
from sentnelops import AIRClient
client = AIRClient(api_key="snops_key_…",
base_url="https://api.sentnelops.com")
agent = client.register(name="ci-release-bot", owner="platform-team",
environment="dev", created_by="ada@acme.dev",
allowed_mcp=["github-mcp"])
decision = client.is_permitted(agent["id"], "github-mcp", tool="delete_repo")
# Decision(permitted=False, reason="tool delete_repo is denied on github-mcp")shell
export SNOPS_API_KEY=snops_key_…
export SNOPS_BASE_URL=https://api.sentnelops.com
sentnelops register --name prod-billing --owner finance-team \
--env production --allowed-mcp stripe-mcp --risk-level high
# ✓ Agent registered — prints agent_id + token (shown once)SIEM & automation
| Integration | How |
|---|---|
| Splunk / Datadog / Sentinel | Schedule GET /mcp-calls/export.csv?days=1 with the api key — the CSV is import-ready |
| Real-time pipeline | Consume wss://…/ws/calls?key=<api-key> — JSON events under a second after each call |
| Alerting | Slack webhook + SMTP settings; unset = zero egress, events logged locally |
| Compliance evidence | /agents/export.csv and /reports/governance on a cron |
Performance characteristics
| Property | Detail |
|---|---|
| Proxy overhead | Measured p99 under 2 ms added latency — call logging is fully off the response path |
| Identity cache | The proxy caches agent identity + upstream resolution for a few seconds (configurable). Org policy is cached per org with the same TTL — a save lands immediately on the writing replica and within one TTL elsewhere; the platform-default files hot-reload on mtime change |
| Rotation latency | Token rotation invalidates old tokens within the identity-cache TTL (default 5 s) |
| Fail-open invariant | In observe mode, no failure inside SentnelOps ever drops a customer call |
Self-hosting
The stack is a single container image (FastAPI + asyncpg) plus Postgres 16 — VPC native, zero data egress by design:
cp .env.example .env # set SNOPS_JWT_SECRET at minimum docker compose up --build # Postgres 16 + API :8000 + Caddy :80/:443
| Variable | Default | Purpose |
|---|---|---|
SNOPS_DATABASE_URL | local postgres | asyncpg DSN |
SNOPS_JWT_SECRET | change it | Signs agent + user JWTs; rotating it invalidates every token at once |
SNOPS_RATE_LIMIT_PER_MINUTE | 600 | Per-credential fixed window (per minute) |
SNOPS_POLICIES_PATH | policies | Platform-default policy YAML directory (hot-reloaded) |
SNOPS_PROXY_IDENTITY_TTL_SECONDS | 5.0 | Proxy identity + org policy cache; 0 disables |
SNOPS_SLACK_WEBHOOK_URL | unset | Slack alerts + digest |
SNOPS_ALERT_EMAIL_TO / _FROM, SNOPS_SMTP_* | unset | Email alerts (STARTTLS) |
SNOPS_ENFORCEMENT_FAIL_MODE_DEFAULT | open | Last resort when the policy store itself is unreadable |
SNOPS_CORS_ORIGINS | localhost:3000 | Browser origins allowed to call the API directly |
Ops rhythm: run
make sweep (TTL + review checks) on a daily cron, keep policy YAML in version control, and rotate agent tokens on schedule. Migrations are idempotent (make migrate).CIMD document shape
GET /cimd/{agent_id}.json
{
"client_id": "/cimd/<agent_id>.json",
"client_name": "<agent name>",
"sentnelops": {
"agent_id": "<uuid>",
"owner": "<team>",
"environment": "dev | staging | production",
"risk_level": "low | medium | high",
"status": "active | suspended | …",
"allowed_mcp": ["…"]
}
}Unknown or malformed ids return 404. The document never contains secrets — it is safe to resolve from anywhere.