Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/nullclaw/nullclaw/llms.txt

Use this file to discover all available pages before exploring further.

Tools Overview

NullClaw provides 30+ built-in tools that enable agents to perform actions in the real world. Tools follow a vtable-based architecture that allows easy extension and testing.

Available Tools

NullClaw ships with comprehensive tool coverage across multiple domains:

File Operations (4 tools)

  • file_read — Read file contents with workspace scoping
  • file_write — Write or overwrite files with path security
  • file_edit — Find and replace text in files
  • file_append — Append content to files

Shell & Git (2 tools)

  • shell — Execute shell commands with security policy validation
  • git_operations — Structured Git operations (status, diff, log, commit, add, checkout, stash)

Memory (4 tools)

  • memory_store — Persist facts to long-term memory
  • memory_recall — Search memory with hybrid retrieval
  • memory_list — List all stored memories
  • memory_forget — Delete memory entries

Web & Browser (4 tools)

  • http_request — Make HTTP requests with domain allowlisting
  • web_search — Search via multiple providers (Brave, DuckDuckGo, Jina, Perplexity, etc.)
  • web_fetch — Fetch and parse web pages with Jina Reader
  • browser — Open URLs and fetch page content
  • browser_open — Open allowlisted URLs in default browser

Scheduling & Delegation (3 tools)

  • schedule — Schedule future tool executions
  • delegate — Delegate tasks to named agents
  • spawn — Launch async subagent tasks

Hardware (5 tools)

  • hardware_info — Query connected hardware boards
  • hardware_memory — Read/write hardware memory regions
  • i2c — I2C bus operations
  • spi — SPI bus operations (planned)
  • screenshot — Capture screen regions

Cron (6 tools)

  • cron_add — Register cron jobs
  • cron_list — List scheduled jobs
  • cron_remove — Delete jobs
  • cron_runs — View job execution history
  • cron_run — Manually trigger job
  • cron_update — Modify existing job

Integrations (4 tools)

  • composio — Composio API integration
  • pushover — Pushover notifications
  • message — Send messages to channels
  • image — Image analysis and manipulation

Tool Architecture

Tool vtable Interface

All tools implement the Tool vtable interface defined in src/tools/root.zig:
pub const Tool = struct {
    ptr: *anyopaque,
    vtable: *const VTable,

    pub const VTable = struct {
        execute: *const fn (ptr: *anyopaque, allocator: Allocator, args: JsonObjectMap) anyerror!ToolResult,
        name: *const fn (ptr: *anyopaque) []const u8,
        description: *const fn (ptr: *anyopaque) []const u8,
        parameters_json: *const fn (ptr: *anyopaque) []const u8,
        deinit: ?*const fn (ptr: *anyopaque, allocator: Allocator) void = null,
    };
};
Every tool must provide:
  • name() — Tool identifier for LLM function calling
  • description() — Human-readable description
  • parameters_json() — JSON Schema for tool parameters
  • execute() — Core execution logic
  • deinit() — Optional cleanup function

ToolResult

Tools return a ToolResult struct indicating success/failure:
pub const ToolResult = struct {
    success: bool,
    output: []const u8,      // Heap-allocated, caller must free
    error_msg: ?[]const u8,  // Heap-allocated error if not null
};
Ownership rules:
  • output and error_msg are owned by the caller
  • Use allocator.free() to free them after use
  • Exception: static strings from ToolResult.ok("") or ToolResult.fail("literal") must NOT be freed

Tool Execution Flow

  1. LLM generates function call with tool name and JSON arguments
  2. Dispatcher parses JSON into std.json.ObjectMap
  3. Tool vtable execute() is called with parsed args
  4. Tool validates inputs using root.getString(), root.getInt(), root.getBool() helpers
  5. Tool performs action (read file, run shell command, etc.)
  6. ToolResult returned with success/failure and output
  7. Result serialized back to LLM as assistant message

Argument Extraction Helpers

Tools use type-safe helpers to extract arguments from the parsed JSON:
const path = root.getString(args, "path") orelse 
    return ToolResult.fail("Missing 'path' parameter");

const limit = root.getInt(args, "limit") orelse 5;

const cached = root.getBool(args, "cached") orelse false;

Tool Registration

Tools are registered in factory functions:

Default Tools (3 core tools)

pub fn defaultTools(
    allocator: std.mem.Allocator,
    workspace_dir: []const u8,
) ![]Tool {
    // Returns: shell, file_read, file_write, file_edit
}

All Tools (30+ tools)

pub fn allTools(
    allocator: std.mem.Allocator,
    workspace_dir: []const u8,
    opts: struct {
        http_enabled: bool = false,
        browser_enabled: bool = false,
        memory: ?Memory = null,
        // ... more config
    },
) ![]Tool

Subagent Tools (restricted subset)

pub fn subagentTools(
    allocator: std.mem.Allocator,
    workspace_dir: []const u8,
    opts: struct {
        http_enabled: bool = false,
        allowed_paths: []const []const u8 = &.{},
    },
) ![]Tool
Subagent tools exclude:
  • message — prevent cross-channel side effects
  • spawn, delegate — prevent infinite loops
  • schedule — prevent uncontrolled scheduling
  • memory_* — prevent memory pollution
  • composio, browser_open — reduce blast radius

Tool Configuration

Tools accept configuration at initialization:
const st = try allocator.create(shell.ShellTool);
st.* = .{
    .workspace_dir = workspace_dir,
    .allowed_paths = opts.allowed_paths,
    .timeout_ns = 60 * std.time.ns_per_s,
    .max_output_bytes = 1_048_576,
    .policy = opts.policy,  // SecurityPolicy for command validation
};

Security Model

Workspace Path Scoping

All file/shell tools enforce workspace path scoping:
  • Relative paths resolved within workspace_dir
  • Absolute paths require allowed_paths configuration
  • Symlinks validated after resolution
  • Path traversal (../) blocked by isPathSafe()
  • System-sensitive paths (e.g., /etc/passwd) blocked

Command Validation

Shell and Git tools sanitize inputs:
  • Block shell metacharacters (;, |, >, `, $(), etc.)
  • Block dangerous git options (--exec=, --pager=, -c)
  • Validate against SecurityPolicy.allowed_commands
  • Require approval for medium/high risk commands

Domain Allowlisting

HTTP/browser tools enforce domain allowlists:
  • Only https:// URLs accepted (no http://)
  • Host extracted and validated against allowed_domains
  • Subdomain matching supported (api.example.com matches example.com)
  • Localhost and private IPs blocked by default

Creating a Custom Tool

Example tool implementation:
const std = @import("std");
const root = @import("root.zig");
const Tool = root.Tool;
const ToolResult = root.ToolResult;
const JsonObjectMap = root.JsonObjectMap;

pub const MyTool = struct {
    config_value: []const u8,

    pub const tool_name = "my_tool";
    pub const tool_description = "Does something useful";
    pub const tool_params =
        \\{"type":"object","properties":{"input":{"type":"string"}},"required":["input"]}
    ;

    const vtable = root.ToolVTable(@This());

    pub fn tool(self: *MyTool) Tool {
        return .{
            .ptr = @ptrCast(self),
            .vtable = &vtable,
        };
    }

    pub fn execute(self: *MyTool, allocator: std.mem.Allocator, args: JsonObjectMap) !ToolResult {
        const input = root.getString(args, "input") orelse
            return ToolResult.fail("Missing 'input' parameter");

        // Do work...
        const output = try std.fmt.allocPrint(allocator, "Processed: {s}", .{input});
        return ToolResult{ .success = true, .output = output };
    }
};
Register in factory:
const my_tool = try allocator.create(MyTool);
my_tool.* = .{ .config_value = "example" };
try tool_list.append(allocator, my_tool.tool());

Testing Tools

Tools include comprehensive test coverage:
test "my_tool basic execution" {
    var mt = MyTool{ .config_value = "test" };
    const t = mt.tool();
    const parsed = try root.parseTestArgs("{\"input\": \"hello\"}");
    defer parsed.deinit();
    const result = try t.execute(std.testing.allocator, parsed.value.object);
    defer if (result.output.len > 0) std.testing.allocator.free(result.output);
    defer if (result.error_msg) |e| std.testing.allocator.free(e);
    
    try std.testing.expect(result.success);
    try std.testing.expect(std.mem.indexOf(u8, result.output, "hello") != null);
}

Schema Cleaning

NullClaw automatically cleans JSON schemas for LLM compatibility using src/tools/schema.zig:
  • Gemini — strips most validation keywords (minLength, pattern, format, etc.)
  • Anthropic — removes $ref and definitions
  • OpenAI — minimal cleaning (most permissive)
Schemas are cleaned before sending to the LLM:
const cleaned = try SchemaCleanr.cleanForGemini(allocator, tool.parametersJson());

Next Steps

  • File Operations — Read, write, edit, append files
  • Shell — Execute shell commands with security policies
  • Memory — Persistent memory with hybrid search
  • Browser — Web browsing and content fetching
  • Git — Structured Git operations