Skip to main content

3tears Agent Tools

Tool framework for LLM agents. Provides tool routing, execution, context management, MCP integration, and a set of builtin tools.

Part of the 3tears framework.

ToolServer baseline audit

ToolServer.handle_call stamps every dispatch with a unified AuditEvent envelope (event_type='tool.call') via threetears.agent.audit.publish_audit. The baseline emission fires in a finally block so success, failure (tool returned success=False), and error (tool raised) outcomes all produce a row. Identity axes carry from the active ToolCallScope (actor_user_id, calling_agent_id, owner_agent_id, customer_id, correlation_id); resource_namespace_id / resource_namespace_type stay None at the baseline layer since the tool resolves its target inside execute. Per-tool additive events (e.g. workspace.fs_write) still publish via publish_audit and ride alongside the baseline row under the same correlation_id, which ties a request's events together and is not a deduplication key: every tool.call in a turn shares it, and each is its own row. Each envelope's id is its identity, so a JetStream redelivery (which repeats the id) collapses to one row. Emission is fire-and-forget: NATS publish failures log WARN and never taint the tool's response.

Naming a refusal: ToolResult.error_code

A tool that refuses a call for a reason its caller can act on names it with a code, not only a sentence:

from threetears.agent.tools.base_tool import CONFLICT, ToolResult

return ToolResult(success=False, content="", error="that changed while you were editing it", error_code=CONFLICT)

ToolServer copies error_code onto CallResponse.error_code, and the registry forwards it unchanged onto ProxyCallResponse.error_code, so a caller (and ToolCallClient's ToolCallError.error_code) branches on the tool's code exactly as on a registry refusal. The platform maps each code to an HTTP status, an agent summary and a channel sentence; a failure with no code renders as the generic fallback (HTTP 502).

The vocabulary is closed: error_code must be one of TOOL_RESULT_ERROR_CODES, or ToolResult raises ValueError at construction, naming the declared spelling when it was only the case that was wrong. Every code is upper case, like every platform code, so one condition never travels under two spellings. A success may not name a code.

Code Meaning Platform face
CONFLICT what the call changes was changed by someone else at the same moment; nothing was written. Read it again and retry. HTTP 409, retryable
MALFORMED_REQUEST the call cannot be served as asked: an argument names something the tool does not have, or a value it does not take. The message says which. HTTP 400, not retryable
TOOL_NOT_READY the tool cannot answer yet (its data is still being loaded or checked). Try again shortly. HTTP 503, retryable

A new code is a change to TOOL_RESULT_ERROR_CODES here and a face in the platform's error map, released together.

Tool-as-namespace emission

Tool namespace materialization is platform-owned. ToolServer.publish_registration writes the RegistrationManifest (carrying pod_id + tools + the owner_agent_id / customer_id envelope fields), and a platform-side namespace emitter subscribes to {ns}.tools.register and upserts one namespaces row of type tool per tool. This is the sole writer in the platform.

Agent-spun ToolServers stamp agent_id + customer_id on the RegistrationManifest so the emitter lands rows with the right owner scope; platform-built-in pods (admin tool server, datasource tool pod) leave both None and the row lands with NULL owner columns (admitted under the widened namespaces_row_scope_customer_ck carve-out for tool type alongside system / model).

The canonical name shape is tools.<sanitized-mcp>.<sanitized-version> (per build_namespace_name); metadata carries the pre-sanitized natural-identity fields mcp_name / mcp_version / pod_id so downstream pattern matching (platform access materializer agent.yaml access.tools patterns + registry authorizer canonical-name lookup) does not need to reverse the sanitization rules. Deterministic uuid5 derived from (mcp_name, version, owner_agent_id_hex) keeps concurrent emitters race-safe via ON CONFLICT (id) DO UPDATE.

ToolServer holds no NamespaceCollection and has no constructor parameter to take one: register_tool / deregister_tool publish the manifest and nothing else. The pod-side emitter that once wrote and deleted these rows is deleted -- its write could not land (the agent's L3 proxy resolves platform-scoped writes to the per-agent agent_<hex> schema, which has no namespaces table) and its delete raised on every call (it passed a bare UUID to a Collection keyed on the composite (row_scope, namespace_id)). packages/agent/tools/tests/enforcement/test_no_agent_side_namespace_writes.py fails a build that brings any of it back.

Tool pod lifecycle: shutdown and owner process

ToolServerBootstrap runs every tool pod's lifecycle. Two guarantees about how a pod stops:

  • SIGTERM always ends the process. SIGTERM and SIGINT run one shutdown path, ToolServerBootstrap.shutdown_server. If the server's own shutdown raises (a NATS drain against a reconnecting server raised ConnectionResetError in production) or overruns its bound, the failure is logged once at ERROR with its cause, the serve loop is ended anyway, and run() exits EX_SOFTWARE (70). A caller driving run_async() itself receives ToolPodShutdownError, chaining the cause. ToolServer.shutdown also releases serve() in a finally, so a pod not run by the bootstrap does not hang either.
  • A pod can follow the process that owns it. Set THREETEARS_TOOL_POD_OWNER_PID to the pid of the process that spawned the pod, and the pod shuts itself down through the same path once that process no longer exists (a WARNING names the pid). Opt-in; unset watches nothing. The check is kill(pid, 0), portable across macOS and Linux; when the owner is the pod's parent, being reparented counts as the owner gone too. The value must be a decimal integer greater than 1 and not the pod's own pid -- 0, negatives, 1, blank and non-integers are refused at startup as a ToolPodConfigError, and run() exits EX_CONFIG (78).
Variable Default Meaning
THREETEARS_TOOL_POD_OWNER_PID unset pid of the owning process; the pod exits when it is gone
THREETEARS_TOOL_POD_OWNER_POLL_INTERVAL_SECONDS 1.0 how often the owner is checked (positive)
THREETEARS_TOOL_POD_SHUTDOWN_TIMEOUT_SECONDS 20.0 bound on the server's shutdown, and separately on the teardown after it (positive)

Installation

pip install 3tears-agent-tools

# Optional extras for builtin tools
pip install "3tears-agent-tools[calculator]"   # simpleeval
pip install "3tears-agent-tools[units]"        # pint
pip install "3tears-agent-tools[fetch]"        # trafilatura
pip install "3tears-agent-tools[document]"     # PyMuPDF, python-docx, openpyxl
pip install "3tears-agent-tools[all]"          # everything

Components

ToolRouter

Routes user messages to the appropriate tool using a lightweight LLM call. Includes recall-intent detection to avoid re-invoking tools when users ask about previous results.

from threetears.agent.tools import ToolRouter, is_recall_intent

# Quick check -- no LLM call needed
if is_recall_intent("show me what the calculator said"):
    # User wants to recall, not invoke

# Full routing with LLM
router = ToolRouter(chat_model)
decision = await router.route(user_message, tool_descriptions)
# decision.tool_name, decision.reasoning

ToolExecutor

Invokes a tool-LLM: sends the user message to a secondary model configured for a specific task.

from threetears.agent.tools import ToolExecutor

executor = ToolExecutor()
result = await executor.invoke_with_tools(
    chat_model=tool_model,
    user_message="What is 42 * 17?",
    tools=[calculator_tool],
    tool_name="calculator",
)
# result.content, result.tool_calls

ToolContextManager

Tracks tool invocations and results across a conversation for recall support.

from threetears.agent.tools import ToolContextManager

ctx = ToolContextManager()
await ctx.record_invocation("calculator", "42 * 17", "714")
await ctx.get_recall_context("calculator")  # Returns formatted recall string

McpClient

MCP (Model Context Protocol) integration for connecting to external tool servers.

from threetears.agent.tools import McpClient

async with McpClient(server_config) as client:
    tools = await client.list_tools()
    result = await client.invoke_tool("tool_name", {"param": "value"})

Builtin Tools

Register all builtin tools at once:

from threetears.agent.tools import register_builtins, ToolRegistry

registry = ToolRegistry()
register_builtins(registry)
# Registers: calculator, unit_converter, dice_roller, date_time,
#            random_number, web_fetch, text_transform, parse_document

Todo Tools

Todo list management behind a storage protocol:

from threetears.agent.tools import TodoStorage, load_todo_tools_from_storage

class MyTodoStorage(TodoStorage):
    async def add(self, conv_id, user_id, title, list_name, msg_id) -> dict: ...
    async def list_all(self, conv_id) -> list[dict]: ...
    # ... other methods

tools = load_todo_tools_from_storage(my_storage, snapshot_callback=on_snapshot)

Protocols

For media-related capabilities, implement these protocols:

from threetears.agent.tools import (
    ImageGenerationBackend,
    MediaStorage,
    VisionProvider,
    TranscriptionProvider,
)

Document Parsing

Parse PDF, DOCX, XLSX, and plain text with optional OCR:

from threetears.agent.tools import DocumentParseError, OcrConfig, parse_document

try:
    result = await parse_document(
        data,
        "application/pdf",
        "report.pdf",
        ocr_config=OcrConfig(enabled=True),
    )
except DocumentParseError as exc:
    ...  # exc.reason is "unsupported_type" or "parse_failed"; the parser's error is exc.__cause__
# result.sections -- list of DocumentSection with heading, content, page numbers

A document that cannot be read raises DocumentParseError; its failure is never returned as the document's text.

Metadata

Release files for 3tears-agent-tools 0.69.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for 3tears-agent-tools 0.69.0
File Size Uploaded
3tears_agent_tools-0.69.0.tar.gz 656.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for 3tears-agent-tools 0.69.0
File Interpreter ABI Platform
3tears_agent_tools-0.69.0-py3-none-any.whl Python 3 none any Details

Total release size: 1.1 MB

Release files / 3tears_agent_tools-0.69.0.tar.gz

Download URL 3tears_agent_tools-0.69.0.tar.gz
Size 656.3 kB
Tags Source
SHA-256 checksum
How to use checksums
a1fb1ab6d2ba459c56b8c188d3136bdb7f00b9d7e00da2df5a7622401eb15921
BLAKE2b-256 checksum
How to use checksums
d4126f69c3e86599db61bee7e75d6f42c8ed28e08e03fe58c7a25453af23cf76
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.

Transparency log

Release files / 3tears_agent_tools-0.69.0-py3-none-any.whl

Download URL 3tears_agent_tools-0.69.0-py3-none-any.whl
Size 398.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ab1cc7e8c149f8692139a413615ad74d07a373deaf5b87a70214c2d791cb6074
BLAKE2b-256 checksum
How to use checksums
22d8143b2946be2014a1672f54149cb91cfc9762a447409e94059cea35043234
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.69.0 This release

2 release files

0.68.0

2 release files

0.67.0

2 release files

0.66.0

2 release files

0.57.0

2 release files

0.56.3

2 release files

0.56.2

2 release files

0.56.1

2 release files

0.56.0

2 release files

0.55.0

2 release files

0.54.0

2 release files

0.53.0

2 release files

0.52.1

2 release files

0.52.0

2 release files

0.51.1

2 release files

0.51.0

2 release files

0.50.0

2 release files

0.49.0

2 release files

0.48.0

2 release files

0.47.1

2 release files

0.47.0

2 release files

0.46.1

2 release files

0.46.0

2 release files

0.45.1

2 release files

0.45.0

2 release files

0.44.0

2 release files

0.43.0

2 release files

0.42.0

2 release files

0.41.4

2 release files

0.41.3

2 release files

0.41.2

2 release files

0.41.1

2 release files

0.41.0

2 release files

0.40.0

2 release files

0.39.0

2 release files

0.38.0

2 release files

0.37.0

2 release files

0.30.0

2 release files

0.29.0

2 release files

0.28.0

2 release files

0.27.0

2 release files

0.26.1

2 release files

0.26.0

2 release files

0.25.0

2 release files

0.24.7

2 release files

0.24.6

2 release files

0.24.5

2 release files

0.24.4

2 release files

0.24.3

2 release files

0.24.2

2 release files

0.24.1

2 release files

0.24.0

2 release files

0.23.9

2 release files

0.22.4

2 release files

0.22.3

2 release files

0.22.2

2 release files

0.22.1

2 release files

0.22.0

2 release files

0.21.0

2 release files

0.20.0

2 release files

0.19.4

2 release files

0.19.3

2 release files

0.19.2

2 release files

0.19.1

2 release files

0.19.0

2 release files

0.18.0

2 release files

0.17.9

2 release files

0.17.8

2 release files

0.17.7

2 release files

0.17.6

2 release files

0.17.5

2 release files

0.17.4

2 release files

0.17.3

2 release files

0.17.2

2 release files

0.17.1

2 release files

0.17.0

2 release files

0.16.1

2 release files

0.16.0

2 release files

0.15.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page