Skip to main content

agent daemon

Project description

agentd

LLM agent utilities featuring:

  1. Programmatic Tool Calling (PTC) - Bash-enabled agents with MCP tools exposed as AgentSkills
  2. Patched Responses API + Agent Daemon - Traditional tool_calls with MCP, plus YAML-configured reactive agents

Installation

pip install agentd
# or
uv add agentd

Programmatic Tool Calling (PTC)

PTC gives you a bash-enabled agent that unifies MCP tools with the AgentSkills spec.

Instead of JSON tool_calls, the LLM writes code in fenced blocks. MCP tools and @tool functions are auto-converted to Python bindings in a discoverable skills directory.

from agentd import patch_openai_with_ptc, display_events, tool
from openai import OpenAI

@tool
def calculate(expression: str) -> str:
    """Evaluate a math expression."""
    import math
    return str(eval(expression, {"__builtins__": {}}, {"sqrt": math.sqrt}))

client = patch_openai_with_ptc(OpenAI(), cwd="./workspace")

stream = client.responses.create(
    model="claude-sonnet-4-20250514",
    input=[{"role": "user", "content": "List files, then calculate sqrt(144)"}],
    stream=True
)

for event in display_events(stream):
    if event.type == "text_delta":
        print(event.text, end="", flush=True)
    elif event.type == "code_execution":
        print(f"\n$ {event.code}\n{event.output}\n")

Key Features

Bash-enabled agent: The LLM can run shell commands directly:

```bash:execute
ls -la
git status
curl https://api.example.com/data
```

MCP + AgentSkills unified: Tools from MCP servers and @tool decorators are exposed as Python functions following the AgentSkills spec:

```python:execute
from lib.tools import read_file, fetch_url
result = read_file(path="/tmp/data.txt")
print(result)
```

File creation: The LLM can create new scripts:

```my_script.py:create
print("Hello from generated script!")
```

XML support: Also parses Claude's XML function call format:

<invoke name="bash:execute">
  <parameter name="command">ls -la</parameter>
</invoke>

Auto-Generated Skills Directory

PTC generates a skills directory combining MCP tools and local functions:

skills/
  lib/
    tools.py              # Python bindings for ALL tools (MCP + @tool)
  filesystem/             # From @modelcontextprotocol/server-filesystem
    SKILL.md              # AgentSkills spec: YAML frontmatter + docs
    scripts/
      read_file_example.py
  local/                  # From @tool decorated functions
    SKILL.md
    scripts/
      calculate_example.py

The LLM discovers tools by exploring:

ls skills/                           # List available skills
cat skills/filesystem/SKILL.md       # Read skill documentation

Then imports and uses them:

from lib.tools import read_file, calculate

MCP Bridge

An HTTP bridge runs locally to route tool calls:

# Auto-generated in skills/lib/tools.py
def read_file(path: str) -> dict:
    return _call("read_file", path=path)  # POST to http://localhost:PORT/call/read_file

The bridge dispatches to MCP servers or local Python functions as appropriate.

PTC with MCP Servers

from agents.mcp.server import MCPServerStdio
from agentd import patch_openai_with_ptc

mcp_server = MCPServerStdio(
    params={"command": "npx", "args": ["-y", "@modelcontextprotocol/server-everything"]},
    cache_tools_list=True
)

client = patch_openai_with_ptc(OpenAI(), cwd="./workspace")

response = client.responses.create(
    model="claude-sonnet-4-20250514",
    input="Explore the available skills and use one",
    mcp_servers=[mcp_server],
    stream=True
)

Display Events

from agentd import display_events

for event in display_events(stream):
    match event.type:
        case "text_delta":
            print(event.text, end="")
        case "code_execution":
            print(f"Code: {event.code}")
            print(f"Output: {event.output}")
            print(f"Status: {event.status}")  # "completed" or "failed"
        case "turn_end":
            print("\n---")

Microsandbox Executor

Run code in hardware-isolated microVMs instead of subprocesses for secure execution.

Install microsandbox:

# Linux (requires KVM) or macOS (Apple Silicon only)
curl -sSL https://get.microsandbox.dev | sh

# Start the server
msb server start --dev

Usage:

from agentd import patch_openai_with_ptc, create_microsandbox_cli_executor
from openai import OpenAI

# Create sandboxed executor
executor = create_microsandbox_cli_executor(
    conversation_id="my_session",
    image="python",
    memory=1024,
    timeout=60,
)

client = patch_openai_with_ptc(
    OpenAI(),
    cwd=str(executor.snapshot_manager.workspace_dir),
    executor=executor,
)

stream = client.responses.create(
    model="claude-sonnet-4-20250514",
    input=[{"role": "user", "content": "Run some Python code"}],
    stream=True
)

# ... handle events ...

# Create snapshot for time travel
executor.snapshot("checkpoint_1")

# Restore to previous state
executor.restore("checkpoint_1")

executor.close()

Features:

  • Hardware isolation: Code runs in microVMs, not just containers
  • Persistent workspace: Volume mounting preserves files across executions
  • Snapshots: Save and restore workspace state at any point
  • Drop-in replacement: Same interface as the default subprocess executor

Sandbox Runtime Executor

Lightweight OS-level sandboxing using Anthropic's sandbox-runtime. Uses sandbox-exec on macOS and bubblewrap on Linux - no containers or VMs required.

Install sandbox-runtime:

npm install -g @anthropic-ai/sandbox-runtime

Linux only: If using AppArmor, you may need to allow unprivileged user namespaces:

sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0

Usage:

from agentd import patch_openai_with_ptc, create_sandbox_runtime_executor
from openai import OpenAI

executor = create_sandbox_runtime_executor(
    conversation_id="my_session",
    # Network restrictions (allow-list)
    allowed_domains=["github.com", "pypi.org"],
    # Filesystem restrictions
    deny_read=["~/.ssh", "~/.aws", "~/.gnupg"],
    # allow_write defaults to workspace only
)

# Verify sandbox works on this system
ok, msg = executor.verify()
if not ok:
    print(f"Sandbox unavailable: {msg}")

client = patch_openai_with_ptc(
    OpenAI(),
    cwd=str(executor.workspace_dir),
    executor=executor,
)

stream = client.responses.create(
    model="claude-sonnet-4-20250514",
    input=[{"role": "user", "content": "Run some Python code"}],
    stream=True
)

# ... handle events ...

# Snapshots work the same as microsandbox
executor.snapshot("checkpoint_1")
executor.restore("checkpoint_1")

executor.close()

Features:

  • OS-level isolation: Network and filesystem restrictions via OS primitives
  • No containers: Lighter weight than microVMs, faster startup
  • Network allow-list: Only specified domains are accessible
  • Filesystem protection: Block reads to sensitive paths, restrict writes
  • Snapshots: Same time-travel API as microsandbox executor
  • MCP tools support: Uses Unix sockets to bridge tool calls from sandbox to host

MCP Tools: The sandbox runtime uses network namespace isolation, but MCP tools work via Unix sockets. The MCP bridge listens on a socket in the workspace directory, which is accessible from inside the sandbox.

Comparison:

Executor Isolation Requirements Best For
SubprocessExecutor None - Development, trusted code
SandboxRuntimeExecutor OS-level srt CLI Lightweight isolation
MicrosandboxCLIExecutor MicroVM msb CLI + KVM Maximum isolation

Traditional Tool Calling

For cases where you want standard JSON tool_calls instead of code fences.

Patched Responses API

A lightweight agentic loop that patches the OpenAI client to transparently handle MCP tool calls. Works with any provider via LiteLLM.

from agents.mcp.server import MCPServerStdio
from agentd import patch_openai_with_mcp
from openai import OpenAI

fs_server = MCPServerStdio(
    params={
        "command": "npx",
        "args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp/"],
    },
    cache_tools_list=True
)

client = patch_openai_with_mcp(OpenAI())

response = client.chat.completions.create(
    model="gemini/gemini-2.0-flash",  # Any provider via LiteLLM
    messages=[{"role": "user", "content": "List files in /tmp/"}],
    mcp_servers=[fs_server],
)

print(response.choices[0].message.content)

What it does:

  • Patches chat.completions.create and responses.create
  • Auto-connects to MCP servers and extracts tool schemas
  • Intercepts tool calls, executes via MCP, feeds results back
  • Loops until no more tool calls (max 20 iterations)
  • Supports streaming

Multi-provider support:

model="gpt-4o"                      # OpenAI
model="claude-sonnet-4-20250514"    # Anthropic
model="gemini/gemini-2.0-flash"     # Google

Agent Daemon

YAML-configured agents with MCP resource subscriptions. Agents react to resource changes automatically.

uvx agentd config.yaml

Configuration:

agents:
  - name: news_agent
    model: gpt-4o-mini
    system_prompt: |
      You monitor a URL for changes. When new content arrives,
      save it to ./output/data.txt using the edit_file tool.
    mcp_servers:
      - type: stdio
        command: uv
        arguments: ["run", "mcp_subscribe", "--poll-interval", "5", "--", "uvx", "mcp-server-fetch"]
      - type: stdio
        command: npx
        arguments: ["-y", "@modelcontextprotocol/server-filesystem", "./output/"]
    subscriptions:
      - "tool://fetch/?url=https://example.com/api/data"

How subscriptions work:

  1. Agent connects to MCP servers
  2. Subscribes to resource URIs (e.g., tool://fetch/?url=...)
  3. When resource changes, MCP server sends notification
  4. Agent calls the tool, gets result, sends to LLM
  5. LLM responds (can call more tools)

Built on mcp-subscribe.

Each agent also has an interactive REPL:

news_agent> What files have you saved?
Assistant: I've saved 3 files to ./output/...

API Reference

Patching Functions

from agentd import patch_openai_with_mcp, patch_openai_with_ptc

# PTC: bash + skills (no isolation)
client = patch_openai_with_ptc(OpenAI(), cwd="./workspace")

# PTC with OS-level sandbox (lightweight)
from agentd import create_sandbox_runtime_executor
executor = create_sandbox_runtime_executor(conversation_id="my_session")
client = patch_openai_with_ptc(OpenAI(), executor=executor)

# PTC with microsandbox isolation (microVM)
from agentd import create_microsandbox_cli_executor
executor = create_microsandbox_cli_executor(conversation_id="my_session")
client = patch_openai_with_ptc(OpenAI(), executor=executor)

# Traditional tool_calls
client = patch_openai_with_mcp(OpenAI())

Microsandbox Executor

from agentd import create_microsandbox_cli_executor

executor = create_microsandbox_cli_executor(
    conversation_id="session_1",  # Sandbox name prefix
    image="python",               # microsandbox image
    memory=1024,                  # MB
    timeout=60,                   # seconds
)

# Snapshot API
snapshot = executor.snapshot("label")     # Save state
executor.restore(snapshot.id)             # Restore state
snapshots = executor.list_snapshots()     # List all snapshots

executor.close()  # Cleanup

Sandbox Runtime Executor

from agentd import create_sandbox_runtime_executor

executor = create_sandbox_runtime_executor(
    conversation_id="session_1",
    timeout=60,
    # Network (allow-list pattern)
    allowed_domains=["github.com", "*.python.org"],
    denied_domains=[],
    allow_local_binding=False,
    # Filesystem
    deny_read=["~/.ssh", "~/.aws"],    # Block reading these paths
    allow_write=None,                   # None = workspace only
    deny_write=[".env"],                # Block within allowed zones
)

# Check if sandbox works on this system
ok, msg = executor.verify()

# Same snapshot API as microsandbox
snapshot = executor.snapshot("label")
executor.restore(snapshot.id)

executor.close()

Tool Decorator

from agentd import tool

@tool
def my_function(arg1: str, arg2: int = 10) -> str:
    """Description goes here.

    arg1: Description of arg1
    arg2: Description of arg2
    """
    return f"Result: {arg1}, {arg2}"

Examples

See examples/:

  • ptc_with_mcp.py - PTC with MCP servers
  • ptc_with_tools.py - PTC with @tool decorator
  • ptc_microsandbox.py - PTC with microsandbox isolation (microVM)
  • ptc_sandbox_runtime.py - PTC with sandbox-runtime isolation (OS-level)

See config/ for agent daemon configs.


Architecture

┌─────────────────────────────┐     ┌─────────────────────────────┐
│            PTC              │     │       Agent Daemon          │
│     (bash, skills, MCP)     │     │    (YAML, subscriptions)    │
└──────────────┬──────────────┘     └──────────────┬──────────────┘
               │                                   │
               ▼                                   ▼
┌──────────────────────────────────────────────────────────────────┐
│                       Patched OpenAI Client                      │
│  ┌────────────────────────┐    ┌────────────────────────────┐   │
│  │   patch_openai_ptc     │    │     patch_openai_mcp       │   │
│  │   (fence parse/exec)   │    │     (tool_calls loop)      │   │
│  └───────────┬────────────┘    └─────────────┬──────────────┘   │
└──────────────┼───────────────────────────────┼──────────────────┘
               │                               │
               ▼                               │
┌──────────────────────────────────────────┐   │
│              Executors                   │   │
│  ┌──────────┐ ┌─────────┐ ┌───────────┐  │   │
│  │Subprocess│ │ Sandbox │ │Microsandbox│  │   │
│  │(default) │ │ Runtime │ │ (microVM) │  │   │
│  │          │ │(OS-level)│ │           │  │   │
│  └──────────┘ └─────────┘ └───────────┘  │   │
└──────────────────┬───────────────────────┘   │
                   │                           │
                   ▼                           │
┌──────────────────────────────┐               │
│         MCP Bridge           │               │
│        (HTTP server)         │               │
└──────────────┬───────────────┘               │
               │                               │
               └───────────────┬───────────────┘
                               ▼
               ┌───────────────────────────────┐
               │          MCP Servers          │
               └───────────────────────────────┘

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

agentd-0.8.6.tar.gz (44.7 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

agentd-0.8.6-py3-none-any.whl (64.7 kB view details)

Uploaded Python 3

File details

Details for the file agentd-0.8.6.tar.gz.

File metadata

  • Download URL: agentd-0.8.6.tar.gz
  • Upload date:
  • Size: 44.7 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.7.3

File hashes

Hashes for agentd-0.8.6.tar.gz
Algorithm Hash digest
SHA256 ae17a1e8af910eb67beb88db83a13ea1bf153cc692f192beffdb6227e1fd955b
MD5 4a0908500139eb8ded6bc0eee978e705
BLAKE2b-256 5cdca430a4fce7175e5aca13b5bae6df81c61098ee93ae9615d263cf37306ade

See more details on using hashes here.

File details

Details for the file agentd-0.8.6-py3-none-any.whl.

File metadata

  • Download URL: agentd-0.8.6-py3-none-any.whl
  • Upload date:
  • Size: 64.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.7.3

File hashes

Hashes for agentd-0.8.6-py3-none-any.whl
Algorithm Hash digest
SHA256 ebd33ed37bc158a092df9f0bc54aacbe2b367eca87b645735ad318e833a7360a
MD5 2fbf43ae16c861d8c8fc8eacc9f60695
BLAKE2b-256 df8455d972ece21c93d9528903c14d961dca621835756557add8645cb6dc3835

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page