Skip to content

Latest commit

Β 

History

154 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Nullain Agent SDK

Nullain Agent SDK

The Production-Grade Autonomous Agent Engine & SDK for Python

CI PyPI version Downloads Coverage Gate Python 3.12+ Type Checker: Pyright Strict Code Style: Ruff Architecture: Hexagonal License: MIT

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


πŸ“Œ Overview

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.

Built with it

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.


✨ Features

  • 🧠 Tiered Model Routing (ModelRouter) Task classification routes requests to fast, balanced, or deep model 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) No shell=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 no ASK escape 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_tools tool, 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_search queries your own SearXNG instance first and falls back to DuckDuckGo on any failure. web_fetch can 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 via executemany, 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 standard OTEL_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 (medium default Β· high Β· never) lets a deployment decide where the Plan phase earns its round-trip.

πŸ—οΈ Architecture

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
Loading

A full walkthrough of the six-phase run pipeline (Intent β†’ Route β†’ Plan β†’ Act β†’ Verify β†’ Memory) lives in docs/architecture.md.

Optional sub-SDKs

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-available WebSearchProvider default (SearXNG/DuckDuckGo, no install needed) and an optional Rust-backed local BM25 index, RustSearchAdapter, published from nullain-sdk-search (tantivy core + PyO3 bindings). Install with pip install nullain-sdk[search-rust].

πŸš€ Quick Start

Prerequisites

  • 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 nullain with no configuration and its first-run wizard will ask you to pick a provider and enter the key.

Install from PyPI

pip install nullain-sdk
# or, with the tools + daemon packages too:
pip install nullain-sdk nullain-tools nullain-agentd

…or clone and run from source

git 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

CLI

# 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 list

Python SDK

The 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())

Documentation

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


πŸ”„ Protocol & Daemon

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.main

Every 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

πŸ”’ Security & Threat Model

  1. Subprocess isolation β€” zero reliance on shell=True; commands run via argument-array execution with timeouts and output truncation.
  2. 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.
  3. Workspace containment β€” file operations enforce absolute path resolution verified against workspace_root; symlinks are resolved before authorization checks.
  4. Subagent authority-intersection β€” a child's effective authority is the meet of parent ∧ delegation ∧ child definition ∧ policy; any single denial removes a capability outright.
  5. Signed plugins & SBOM β€” the signature covers identity, transport, capabilities, tool declarations, and a content-hashed SBOM; the loader is fail-closed at every branch.
  6. Secret redaction β€” automatic redaction patterns keep API keys and credentials out of logs and LLM context.

A note for host applications. ASK is only as strong as the permission_callback behind it. With no callback configured, ToolRegistry.execute denies ASK outright (fail-closed). A host that supplies a callback owns that decision β€” approving indiscriminately silently flattens ASK into ALLOW for 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.


πŸ§ͺ Development

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.0

Plugin signing (optional). Ed25519 signature verification requires the signing extra: uv sync --extra signing (installs cryptography). Without it, the SDK installs cleanly and a signed plugin is refused fail-closed rather than loaded on trust.


πŸ“„ License

This project is licensed under the MIT License.

About

Nullain - AGENT SDK

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages