The Production-Grade Autonomous Agent Engine & SDK for Python
English β’ PortuguΓͺs (Brasil)
Built for high-reliability software engineering workflows. Consumed by any host process β CLI, IDE extension, backend service β via a zero-drift NDJSON protocol over stdio.
Overview β’ Features β’ Architecture β’ Quick Start β’ Protocol & Daemon β’ Security β’ Development
Nullain Agent SDK (nullain) is a production-grade agentic engine that reasons, executes code, self-corrects, and learns from experience across software development tasks.
It is built on Hexagonal Architecture (ports & adapters) and Event Sourcing β every interaction is a frozen, append-only event, state is derived by deterministic fold, and the boundary between "what the LLM decided" and "what actually ran" is explicit and auditable at every step.
The SDK ships as three packages in one workspace:
| Package | What it is |
|---|---|
nullain-sdk |
The core engine β model routing, event sourcing, context management, memory, MCP client, subagent authority, plugins, sandboxing, and the workflow orchestrator. |
nullain-tools |
Built-in developer tools β paged file reads, safe edits, ripgrep search, shell execution, git, checkpoints/undo. |
nullain-agentd |
A stdio NDJSON daemon that exposes the SDK to non-Python hosts (CLIs, IDE extensions, other services) with a versioned, schema-exported protocol. |
Nullain Agent is the reference product built on this SDK β a general-purpose chat assistant (web UI, live streaming, payments, image generation, RAG, a live-VNC code sandbox) that consumes nullain-sdk as a plain PyPI dependency.
It is worth reading as a worked example of the extension points this SDK is designed around: registering domain tools alongside the built-ins, overriding the agent's identity through SOUL.md/AGENTS.md, gating side-effecting tools with a permission_callback, and running a shared PostgresEventStore across replicas. See its architecture section for how those pieces fit together in a real deployment.
- π§ Tiered Model Routing (
ModelRouter) Task classification routes requests tofast,balanced, ordeepmodel tiers on Ollama Cloud, with circuit breakers and automatic fallback chains. - π Immutable Event Sourcing (
Conversation) All interaction history is a frozen, append-only sequence of Pydantic events. State is derived via deterministic fold β replayable, zero-drift, fully auditable. - π Plan/Act/Verify Loop (
AgentLoop) Structured pipeline: task-spec generation, strict validation, human approval gates, and a verification phase with self-correction on test or lint failure. - π‘οΈ Context Compaction & Instruction Centrifugation (
ContextManager) Automatically compacts history at 75% window capacity while preserving active specs and key decisions, with instruction re-injection and progressive tool disclosure. - 𧬠Episodic Memory & Learning Loop (
EpisodicMemory) SQLite-backed trajectory engine that records execution attempts and repository fingerprints, injecting relevant few-shot examples into future tasks. - π Defense-in-Depth Security (
PermissionPolicy+Sandbox) Noshell=True, ever β subprocess execution via explicit argument lists, strict path resolution, and 3-tier permissions (allow/ask/deny). Underneath that, an OS-level fail-closed sandbox (Landlock on Linux β₯5.13, Seatbelt on macOS, Job Object on Windows): if a required sandbox is unavailable, execution is refused, never run unsandboxed. - πͺͺ Subagent Authority-Intersection Law (
Authority) A child subagent's effective authority is the meet of four factors β parent authority β§ delegation β§ child definition β§ policy. A capability is granted only if all four grant it; any single denial removes it outright, with noASKescape hatch. - π¦ Signed Plugins + SBOM (
PluginLoader) Plugins are signed, capability-manifested bundles. The signature covers identity, transport, capabilities, tool declarations, and a content-hashed SBOM β drift in any of them invalidates it. Verification is Ed25519, fail-closed at every branch. - π Deferred Tool Schemas
MCP tools register with minimal metadata; full input schemas hydrate on demand via a
search_toolstool, keeping the prompt small as the tool surface scales. - π§© Deterministic Workflow Orchestrator (
Workflow) A workflow is a Python function that composes subagents deterministically β fan-out, pipelines, and ordering are fixed by the script, never decided by an LLM. - β‘ Zero-Drift Stdio NDJSON Daemon (
nullain-agentd) A typed protocol with automated JSON Schema export (make schema), so any language can consume the SDK without hand-maintained bindings. - π Self-Hosted Search & Browser-Grade Fetch
web_searchqueries your own SearXNG instance first and falls back to DuckDuckGo on any failure.web_fetchcan render JavaScript-heavy pages through a real headless browser (Crawl4AI) and still falls back to the Wayback Machine on a bot-block response. Both opt-in; unconfigured behavior is unchanged. - ποΈ Non-Blocking Event Persistence (
PostgresEventStore)append()enqueues and returns immediately; a single background writer drains the queue in batches viaexecutemany, keeping per-event database round-trips off the agent loop's critical path. Reads flush first, so session resume never observes a truncated trajectory. - π OTLP Span Export
configure_tracing(exporter="otlp")ships spans to any OTLP/HTTP backend (Jaeger, Tempo, Honeycomb) honoring the standardOTEL_EXPORTER_OTLP_*environment variables. - ποΈ Tunable Plan Phase (
plan_complexity_threshold) Planning is what makes multi-file work coherent β and pure overhead for a conversational turn. The threshold (mediumdefault Β·highΒ·never) lets a deployment decide where the Plan phase earns its round-trip.
flowchart TB
subgraph API["π Public API"]
direction LR
Agent(["Agent facade"]) --- Loop(["AgentLoop"]) --- Conv(["Conversation"]) --- WF(["Workflow"])
end
subgraph ORCH["π§ Orchestration & Harness"]
direction LR
Intent(["IntentParser"]) --> Route(["ModelRouter"]) --> Plan(["SpecValidator"]) --> Act(["ReAct loop"]) --> Verify(["Verify / self-correct"])
end
subgraph CTX["π§ Context & Memory"]
direction LR
CM(["ContextManager\ncompaction"]) --- EM(["EpisodicMemory"]) --- PM(["PersistentMemory"])
end
subgraph MODEL["π Model Routing"]
direction LR
Router(["ModelRouter"]) --> CB(["CircuitBreaker"]) --> Provider(["OllamaCloudProvider"])
end
subgraph TOOLS["π§ Tools & Execution"]
direction LR
Registry(["ToolRegistry"]) --- MCP(["MCPClient"]) --- Policy(["PermissionPolicy"]) --- Sandbox(["Sandbox\nfail-closed"]) --- Auth(["Authority gate"]) --- Search(["SearchProvider\nWebSearch Β· Rust BM25"])
end
subgraph TRUST["π Plugins & Trust"]
direction LR
Loader(["PluginLoader"]) --> Sig(["Ed25519 SignatureVerifier"]) --> SBOM(["Capability-manifested SBOM"])
end
subgraph INFRA["π‘ Infrastructure & Transport"]
direction LR
Bus(["EventBus"]) --- Telemetry(["structlog Telemetry"]) --- NDJSON(["Stdio NDJSON Protocol"])
end
API --> ORCH --> CTX
ORCH --> MODEL
ORCH --> TOOLS
TOOLS --> TRUST
ORCH --> INFRA
NDJSON -.->|"consumed by"| Host(["Host process\nCLI Β· IDE Β· service"])
classDef api fill:#6366f1,stroke:#4338ca,color:#fff
classDef orch fill:#0ea5e9,stroke:#0369a1,color:#fff
classDef ctx fill:#8b5cf6,stroke:#6d28d9,color:#fff
classDef model fill:#14b8a6,stroke:#0f766e,color:#fff
classDef tools fill:#f59e0b,stroke:#b45309,color:#fff
classDef trust fill:#ef4444,stroke:#b91c1c,color:#fff
classDef infra fill:#64748b,stroke:#334155,color:#fff
class API api
class ORCH orch
class CTX ctx
class MODEL model
class TOOLS tools
class TRUST trust
class INFRA infra
A full walkthrough of the six-phase run pipeline (Intent β Route β Plan β Act β Verify β Memory) lives in docs/architecture.md.
Some ports ship an optional, heavier adapter as a separate extra instead of a hard dependency β the base install stays light, and a missing extra degrades to a documented fallback rather than breaking:
- Search β
SearchProvider(port) has an always-availableWebSearchProviderdefault (SearXNG/DuckDuckGo, no install needed) and an optional Rust-backed local BM25 index,RustSearchAdapter, published from nullain-sdk-search (tantivy core + PyO3 bindings). Install withpip install nullain-sdk[search-rust].
- Python 3.12+
- uv β fast Python package installer
- An API key for your LLM provider β Ollama Cloud (default, open-weight models) or any OpenAI-compatible endpoint (OpenAI, OpenRouter, Together, Groq, vLLM, LM Studio, ...). Sign up and grab a key, or just run
nullainwith no configuration and its first-run wizard will ask you to pick a provider and enter the key.
pip install nullain-sdk
# or, with the tools + daemon packages too:
pip install nullain-sdk nullain-tools nullain-agentdgit clone https://github.com/netty-linux/nullain-agent-sdk.git
cd nullain-agent-sdk
uv sync
export OLLAMA_API_KEY="your-key-here" # or skip β the setup wizard will ask# Run a single prompt
uv run nullain run "list the python files in this workspace"
# Structured NDJSON output for piping
uv run nullain run "list the python files" --json | jq '.type'
# Interactive multi-turn chat with TTY permission approval
uv run nullain chat
# Environment health checks
uv run nullain doctor
# Manage MCP servers declared in nullain.toml
uv run nullain mcp listThe Agent facade is the primary entry point β it assembles the provider, tools, permission policy, router, and sandbox with safe defaults:
import asyncio
from nullain import Agent
async def main() -> None:
agent = Agent(workspace_root=".")
result = await agent.run("Audit pyproject.toml and ensure all dependencies are up to date")
print(result.final_text)
if __name__ == "__main__":
asyncio.run(main())For scripts, run_sync is a thin synchronous facade:
from nullain import Agent
result = Agent(workspace_root=".").run_sync("say hello")
print(result.final_text)Or stream events as they happen:
import asyncio
from nullain import Agent, RunResult
async def main() -> None:
agent = Agent(workspace_root=".")
async for item in agent.stream("refactor the config loader"):
if isinstance(item, RunResult):
print(f"\nstatus={item.status} steps={item.steps}")
else:
print(f"[{item.event_type}]")
if __name__ == "__main__":
asyncio.run(main())| Doc | What's in it |
|---|---|
| docs/quickstart.md | From zero to a running agent. |
| docs/configuration.md | Every nullain.toml section. |
| docs/tools.md | The built-in tools and their capabilities. |
| docs/tui.md | What the terminal UI actually looks like. |
| docs/architecture.md | The full run pipeline and layer breakdown. |
| docs/api-stability.md | What is public API under SemVer. |
| CHANGELOG.md | Notable changes across versions. |
examples/ |
Runnable examples: 01_basic_agent.py β¦ 06_openai_compat_smoke.py. |
- CONTRIBUTING.md β dev setup, workflow, code style.
- SECURITY.md β how to report a vulnerability.
- CODE_OF_CONDUCT.md
nullain-agentd is the stdio IPC bridge between the SDK and any host process β a CLI, an IDE extension, a backend service β in any language.
uv run python -m nullain_agentd.mainEvery message is a versioned NDJSON envelope:
{
"v": 1,
"type": "user.message",
"id": "550e8400-e29b-41d4-a716-446655440000",
"payload": {
"session_id": "sess_100",
"prompt": "Create a test suite for the memory module"
}
}The full contract is exported as JSON Schema so non-Python hosts don't need hand-maintained bindings:
make schema # regenerates schema/protocol_v1.json- Subprocess isolation β zero reliance on
shell=True; commands run via argument-array execution with timeouts and output truncation. - Fail-closed OS sandbox β subprocess tools run inside Landlock (Linux β₯5.13), Seatbelt (macOS), or a Job Object (Windows), isolating filesystem and network. If a required sandbox is unavailable, execution is refused, never run unsandboxed.
- Workspace containment β file operations enforce absolute path resolution verified against
workspace_root; symlinks are resolved before authorization checks. - Subagent authority-intersection β a child's effective authority is the meet of parent β§ delegation β§ child definition β§ policy; any single denial removes a capability outright.
- Signed plugins & SBOM β the signature covers identity, transport, capabilities, tool declarations, and a content-hashed SBOM; the loader is fail-closed at every branch.
- Secret redaction β automatic redaction patterns keep API keys and credentials out of logs and LLM context.
A note for host applications.
ASKis only as strong as thepermission_callbackbehind it. With no callback configured,ToolRegistry.executedeniesASKoutright (fail-closed). A host that supplies a callback owns that decision β approving indiscriminately silently flattensASKintoALLOWfor every tool at that level, including any registered later. Scope the approval to the specific tools whose blast radius you have actually reasoned about, and deny the rest.
Found a vulnerability? See SECURITY.md for how to report it responsibly.
make check # lint + typecheck + test β the full gate
make test # test suite
make lint # ruff check
make typecheck # pyright, strict mode
make format # ruff check --fix + ruff format
make audit # pip-audit for known vulnerabilities
make schema # regenerate schema/protocol_v1.json
# Preview or apply a synchronized version bump across the monorepo
make bump-version VERSION=0.2.0
make bump-version-apply VERSION=0.2.0Plugin signing (optional). Ed25519 signature verification requires the
signingextra:uv sync --extra signing(installscryptography). Without it, the SDK installs cleanly and a signed plugin is refused fail-closed rather than loaded on trust.
This project is licensed under the MIT License.

