Skip to main content

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

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.8.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.8-py3-none-any.whl (64.9 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: agentd-0.8.8.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.8.tar.gz
Algorithm Hash digest
SHA256 1472bce7950dddeb1f3e203db35ca66e2dfe1b22b1c7869c8fd2ab39cbf63655
MD5 89825442af0ba72e2442021332bc472d
BLAKE2b-256 27b052e75cb4449cd6b98bc8699cc782c6250436e94f24d66e63c1821eca867a

See more details on using hashes here.

File details

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

File metadata

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

File hashes

Hashes for agentd-0.8.8-py3-none-any.whl
Algorithm Hash digest
SHA256 985eeae6dc9fcaf66a4299a402092b55bf9e65960245e4674c7f2d93ea25eb4d
MD5 2358613d4cb12bde09c6d30ad5e911cd
BLAKE2b-256 8fa441f1fd0de3f571061da3e5b0971da5a7c9f75d650250cc3368608d56bcb4

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.8.8 This release

2 files

0.8.7

2 files

0.8.6

2 files

0.8.5

2 files

0.8.4

2 files

0.8.3

2 files

0.8.2

2 files

0.8.1

2 files

0.8.0

2 files

0.7.3

2 files

0.7.2

2 files

0.7.1

2 files

0.7.0

2 files

0.6.2

2 files

0.6.1

2 files

0.6.0

2 files

0.5.4

2 files

0.5.3

2 files

0.5.2

2 files

0.5.1

2 files

0.5.0

2 files

0.4.2

2 files

0.4.1

2 files

0.4.0

2 files

0.3.7

2 files

0.3.6

2 files

0.3.5

2 files

0.3.4

2 files

0.3.3

2 files

0.3.2

2 files

0.3.1

2 files

0.3.0

2 files

0.2.2

2 files

0.2.1

2 files

0.2.0.post3

2 files

0.2.0.post2

2 files

0.2.0.post1

2 files

0.2.0

2 files

0.1.8

2 files

0.1.7

2 files

0.1.6

2 files

0.1.5

2 files

0.1.4

2 files

0.1.2

2 files

0.1.1

2 files

0.1.0

2 files

Supported by

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