Custom Agents
A custom agent is an agent you build and run yourself — your own code, in your own process — as opposed to a third-party managed or hosted agent product. Netzilo embeds directly inside that process as an SDK: every LLM prompt, model response, and tool call your agent makes can be evaluated — allowed, blocked, or redacted — against policy pulled live from your Netzilo Server. No daemon, no sidecar, nothing to stand up.
The policy is the set of Endpoint filters, under Edge > Filters, that match the SDK's peer. Enroll the agent with a setup key whose auto-group is your agents' group, and bind guardrails to that group in an endpoint filter. Agents enrolled with a setup key are listed under Agents in the AI Agent Activity report.
Watch · 1:28
Govern the agents you build, from the inside
Two lines of the Netzilo SDK put an agent under the guardrails, with its processes and files in its session.
Install
pip install netzilo
- Python package: pypi.org/project/netzilo
- Node.js package: npmjs.com/package/netzilo
Core API
One call starts the embedded client. It's non-blocking — your agent keeps running while policy syncs in the background.
import netzilo
netzilo.start(
server="https://srv.netzilo.com", # management server
pat="nzl_...", # or setup_key="..."
agent_name="my-agent",
)
server is https://srv.netzilo.com on Netzilo Cloud (the default) and https://<your domain> on a self-hosted Netzilo Server. The SDK does not support interactive SSO sign-in: use a setup key or a personal access token.
Everything on the Framework Adapters, Raw LLM Clients, and MCP & Middleware Integrations pages builds on this one call.
Platform support
Every toolkit on the following pages — framework hooks, raw LLM clients, MCP, and the third-party Guardrails AI library — works on macOS, Linux, and Windows.
Advanced governance (enable_advanced_governance in Python / enableAdvancedGovernance in TypeScript) — deep, in-process inspection of the agent's own outbound traffic, covering every prompt, response, and tool call including from subprocesses and libraries you have no hook for — is currently Linux only. On macOS and Windows, leave it off (the default) and use the framework adapters and manual gating shown on the following pages.
Minimal example with advanced governance
A complete, runnable agent with advanced governance turned on — no framework, no manual tool wrapping. Once start() returns, every LLM call, subprocess, and outbound request your agent code makes is inspected in-process.
import time
import netzilo
netzilo.start(
server="https://srv.netzilo.com",
setup_key="...", # or pat="nzl_..."
agent_name="my-agent",
enable_advanced_governance=True, # Linux only — see note above
)
while not netzilo.is_running(): # wait until policy has synced
time.sleep(0.5)
# Your agent's own code runs unmodified from here — call any LLM SDK,
# spawn subprocesses, make outbound requests. All of it is inspected.
...
netzilo.stop() # graceful stop: snapshots the behavior graph, flushes events
TypeScript also has a one-line fire-and-forget form for this exact case — govern() reads the same config from NETZILO_* environment variables and starts with advanced governance on by default. On macOS and Windows set NETZILO_ADVANCED=0, because advanced governance is Linux only:
import { govern } from "netzilo";
await govern(); // NETZILO_SERVER / NETZILO_SETUP_KEY / NETZILO_AGENT_NAME env vars; non-blocking
Advanced governance adds in-process TLS interception, with a CA at ~/.netzilo-sdk/netzilo-ca.pem, and a probe that records file and process activity of the agent and its subprocesses. Statically linked binaries and programs that ignore the proxy environment variables are not covered, Node.js fetch needs undici, and inside a container the CA can only be installed as root.
Configuration
| Option (Python) | Environment variable | Meaning |
|---|---|---|
server | NETZILO_SERVER | Netzilo Server address; default https://srv.netzilo.com |
setup_key | NETZILO_SETUP_KEY | Setup key to enroll with |
pat | NETZILO_PAT | Personal access token, instead of a setup key |
agent_name | NETZILO_AGENT_NAME | Name shown for the agent |
enable_advanced_governance | NETZILO_ADVANCED | In-process interception and probe; Linux only |
log_level, log_file | NETZILO_LOG_LEVEL | Logging |
config_path | Where the SDK keeps its state; default ~/.netzilo-sdk/config.json |
In TypeScript the options are camel case (agentName, setupKey, enableAdvancedGovernance). When a supervisor process starts the SDK for another process, call set_agent_pid(pid) so that the real workload is recorded as the agent.
In containers, also set NETZILO_PROXY_PORT, NETZILO_CONFIG_DIR, NETZILO_EXTRA_NO_PROXY and NETZILO_READY_TIMEOUT (seconds, default 45) as needed, and start the agent only after the SDK reports it is running.
Direct checks
Without a framework adapter, ask the SDK directly:
import netzilo
if netzilo.is_allowed("web_search", {"query": q}):
...
netzilo.check_prompt(prompt_text)
netzilo.check_response(answer_text)
Upgrading from SDK 4.x
SDK 5.0.0 removed the mcp_gateway_port option and function (Python) and mcpGatewayPort (TypeScript). Passing them now fails with a TypeError or a compile error. Code that passed mcp_gateway_port=0 to turn governance off must change: there is no off switch. To leave an agent ungoverned, keep it out of every filter.
Calling models through the AI Gateway
An SDK agent can also call its models through the AI Gateway: point its OpenAI client at <gateway>/v1 or its Anthropic client at <gateway>/anthropic, with the token of a service user or of the agent's owner as a bearer token. The SDK governs what the agent does; the gateway governs which models it may use, its budget and its prompts.
Watch · 1:20
Govern the agents you build, through the gateway
Point an Anthropic SDK agent at the gateway with its own service identity, and its guardrails follow.
One session per task
The gateway groups an agent's model calls into sessions, which you find under Activity > AI Sessions and in Agentic Activity. Coding agents such as Claude Code send their own session ID. A model API call from your own agent carries none, so the gateway groups calls by conversation: calls that share a system prompt and first message belong together, and a call that starts the conversation again starts a new session.
For exact sessions, give each task its own ID in the x-bf-session-id header. This also keeps apart two runs of the same task at the same time.
import uuid
import anthropic
client = anthropic.Anthropic(
base_url="https://<gateway>/anthropic",
auth_token=token,
default_headers={"x-bf-session-id": str(uuid.uuid4())},
)
A client that serves many tasks sends the ID per request instead, for example client.messages.create(..., extra_headers={"x-bf-session-id": task_id}). The OpenAI client takes the same default_headers and extra_headers.
Related Documentation
- Framework Adapters - CrewAI, LangGraph, Strands and more
- Raw LLM Clients - OpenAI, Anthropic, Bedrock, Gemini, Mistral
- MCP & Middleware Integrations - LiteLLM, MCP, Guardrails AI
- Filters - Endpoint filters that govern SDK agents

