SDK

Tools

The six built-in tools that make up the whole agent — plus how to add your own typed tools.

harnext keeps an intentionally tiny tool surface. These six tools are the entire executor; everything the agent does, it does through them.

Built-in tools

ToolDoesSide effects
readRead a file (or a slice of one) under cwd.None
writeCreate or overwrite a file.Filesystem
editApply a targeted find/replace edit to a file.Filesystem
bashRun a shell command in cwd.Shell
skillInvoke a loaded skill by name.Varies
mcpCall a tool exposed by a connected MCP server.Varies

Selecting tools

Pass the tools option to enable a subset. A read-only agent, for example, omits write, edit, and bash:

reader = Harnext(provider="anthropic", tools=["read"])
reader.run("Explain what src/auth.py does")

Custom tools

Give the agent new capabilities by wrapping a function as a tool. The SDK derives the schema from your type hints (Python) or a Zod/JSON schema (TypeScript), and the agent can call it like any built-in.

from harnext import Harnext, tool

@tool(description="Look up a customer by id")
def get_customer(id: str) -> dict:
    """Returns the customer record, or None if not found."""
    return db.customers.get(id)

@tool(description="Charge a customer in cents")
def charge(customer_id: str, amount_cents: int) -> dict:
    return billing.charge(customer_id, amount_cents)

agent = Harnext(
    provider="anthropic",
    tools=["read", "edit", get_customer, charge],
)
agent.run("Refund the most recent charge for customer c_123")
Schema inference
In Python, parameter names, type hints, and the docstring become the tool schema; use typing.Annotated to add per-argument descriptions. In TypeScript, the parameters schema is the single source of truth and fully types the execute argument.

Returning rich results

A tool can return a string, a JSON-serializable object, or a structured ToolResult to control what the model sees and mark success or failure.

from harnext import tool, ToolResult

@tool(description="Run the test suite")
def run_tests(path: str = ".") -> ToolResult:
    proc = subprocess.run(["pytest", path], capture_output=True, text=True)
    return ToolResult(
        output=proc.stdout,
        is_error=proc.returncode != 0,
    )

Approving tool calls

Custom tools respect the client's permission mode. In manual mode, your on_permission callback receives the tool name and arguments before the call runs, so you can gate destructive operations.

Tools run real code
A custom tool executes whatever you put in it with the model deciding the arguments. Validate inputs, scope credentials narrowly, and prefer manual permission mode for anything that moves money, data, or infrastructure.