Skip to content

feat: dynamic tool registration — agent creates new tools at runtime#120

Open
nv78 wants to merge 1 commit intoclaude/autonomous-agent-improvementsfrom
claude/dynamic-tool-registration
Open

feat: dynamic tool registration — agent creates new tools at runtime#120
nv78 wants to merge 1 commit intoclaude/autonomous-agent-improvementsfrom
claude/dynamic-tool-registration

Conversation

@nv78
Copy link
Member

@nv78 nv78 commented Mar 24, 2026

What this does

The agent can now build its own tools on the fly. When it encounters a task no existing tool covers, it calls register_tool — writes the Python implementation, defines the schema, and the tool becomes available for the rest of the conversation.

The loop:

  1. Agent hits an unsupported task → calls think to plan the implementation
  2. Calls register_tool(name, description, parameters_schema, python_code)
  3. Backend validates + compiles the code in a sandboxed namespace
  4. Tool appears in the LLM's tool list on the next iteration
  5. Agent calls its new tool as if it were built-in

Architecture

Session Registry

Each conversation gets a session_registry = {specs: [], executors: {}}. This mutable dict is threaded through every layer — _run → _run_anthropic/_run_openai → _execute_tool — so dynamic tools are injected into every subsequent LLM call within the same conversation.

Safety (AST-based validation)

_validate_tool_code() walks the AST before any exec():

  • Blocks imports of os, sys, subprocess, socket, shutil, ctypes, pickle, etc.
  • Blocks dangerous builtins: eval, exec, open, compile, __import__
  • Blocks dunder attribute access (__globals__, __subclasses__, etc.)
  • Requires a run(inputs: dict) -> str function to be defined

Allowed libraries in the sandbox: json, re, math, datetime, collections, itertools, urllib, requests, pandas, numpy, bs4.

Execution order

register_tool always runs sequentially (it mutates session_registry). All other tools continue to run in parallel via ThreadPoolExecutor.

Frontend

  • tool_registered SSE event → new reasoning step with emerald styling + wrench icon
  • Shows the fn name(inputs) signature and description so the user can see what was built

Example use cases

  • "Fetch live stock prices for AAPL, MSFT, GOOGL" → agent creates fetch_stock_price wrapping the Yahoo Finance API, then calls it 3 times in parallel
  • "Parse all invoice totals from this text" → agent creates parse_invoice with regex logic, then calls it
  • "Convert this CSV to a markdown table" → agent creates csv_to_markdown using pandas, then calls it
  • "Query the OpenWeatherMap API for Berlin" → agent builds the wrapper once, reuses it for multiple cities

Test plan

  • Ask "what's the weather in Tokyo?" (no weather tool exists) → agent should call register_tool to build a weather API wrapper, then call it
  • Ask to parse a custom data format → agent should register a parser and call it
  • Try to register a tool using import os → should be blocked with a validation error
  • Try to register a tool without a run function → should return error
  • Register a tool, then ask a follow-up that uses it → tool should persist across turns
  • Verify tool_registered event appears in the reasoning panel with emerald color

https://claude.ai/code/session_01C9mHttiQ4ZAaBbQecVV7uu

The agent can now call register_tool(name, description, parameters_schema,
python_code) to define a brand-new Python-backed tool mid-conversation.
The tool is immediately available for all subsequent turns.

Backend (autonomous_agent.py):
- register_tool added to _TOOL_SPECS (16th built-in tool)
- _new_session_registry(): per-conversation mutable store for dynamic tools
  {specs: [...], executors: {name: callable}}
- session_registry threaded through _run → _run_anthropic/_run_openai →
  _execute_tool so dynamic tools are visible on every LLM iteration
- _validate_tool_code(): AST-based safety check blocks os/sys/subprocess/
  socket/eval/exec/open and other dangerous patterns; requires run() fn
- _tool_register_tool(): validates, exec()s in sandboxed namespace with
  safe builtins + allowed libs (requests, pandas, numpy, bs4, json, re...)
- _tool_run_dynamic(): dispatches calls to registered executors
- register_tool runs sequentially (mutates registry); all other tools
  still execute in parallel via ThreadPoolExecutor
- tool_registered SSE event emitted on successful registration
- Unknown tool error now hints the agent to use register_tool

Frontend:
- messageUtils.js: handles tool_registered SSE event → reasoning step
- ThinkingIndicator.js: emerald styling + wrench icon for tool_registered;
  shows fn signature and description of newly created tools

https://claude.ai/code/session_01C9mHttiQ4ZAaBbQecVV7uu
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants