# `ai.tools` — Module Overview

> This document is partially auto-generated. Sections tagged `<!-- AUTO:id -->` are refreshed by the generator.
> Everything else is yours to edit freely and will never be overwritten.

<!-- AUTO:meta -->
## About This Document

This file is **partially auto-generated**. Sections wrapped in `<!-- AUTO:id -->` tags
are overwritten each time the generator runs. Everything else is yours to edit freely.

| Field | Value |
|-------|-------|
| Module | `matrx_ai/tools` |
| Last generated | 2026-04-07 11:38 |
| Output file | `matrx_ai/tools/MODULE_README.md` |
| Signature mode | `signatures` |


**Child READMEs detected** (signatures collapsed — see links for detail):

| README | |
|--------|---|
| [`matrx_ai/tools/arg_models/MODULE_README.md`](matrx_ai/tools/arg_models/MODULE_README.md) | last generated 2026-04-07 11:38 |
| [`matrx_ai/tools/implementations/MODULE_README.md`](matrx_ai/tools/implementations/MODULE_README.md) | last generated 2026-04-07 11:38 |
| [`matrx_ai/tools/tests/MODULE_README.md`](matrx_ai/tools/tests/MODULE_README.md) | last generated 2026-04-07 11:38 |
**To refresh auto-sections:**
```bash
python utils/code_context/generate_module_readme.py matrx_ai/tools --mode signatures
```

**To add permanent notes:** Write anywhere outside the `<!-- AUTO:... -->` blocks.
<!-- /AUTO:meta -->

<!-- HUMAN-EDITABLE: This section is yours. Agents & Humans can edit this section freely — it will not be overwritten. -->

## Access doctrine — per-row tools check ACCESS, not ownership

Per-row content tools (`note`, cloud files, …) act AS the user: owner passes on
`created_by`, everyone else resolves through `iam.has_access_for(user, kind, id, level)`
via `matrx_orm.call_function(..., mode="scalar")` — fail-closed, DB name borrowed from
the injection registry (`get_model("Notes")._database`), never hardcoded. Levels mirror
canonical RLS: read=viewer, write=editor, delete=admin. Unauthorized and missing return
the SAME content-free not-found. LIST stays owner-scoped ("mine first" contract).
Reference: `implementations/notes.py` `_authorized_note`; doctrine:
`common-docs/systems/access-architecture/FEATURE.md`.

## Change Log

- 2026-07-17 — `note` tool: get/update/patch/delete now honor shared access via `iam.has_access_for` (viewer/editor/admin); previously no per-user check at all. `note_list` stays owner-only.

## Architecture

> **Fill this in.** Describe the execution flow and layer map for this module.
> See `utils/code_context/MODULE_README_SPEC.md` for the recommended format.
>
> Suggested structure:
>
> ### Layers
> | File | Role |
> |------|------|
> | `entry.py` | Public entry point — receives requests, returns results |
> | `engine.py` | Core dispatch logic |
> | `models.py` | Shared data types |
>
> ### Call Flow (happy path)
> ```
> entry_function() → engine.dispatch() → implementation()
> ```


<!-- AUTO:tree -->
## Directory Tree

> Auto-generated. 58 files across 5 directories.

```
matrx_ai/tools/
├── MODULE_README.md
├── __init__.py
├── agent_tool.py
├── arg_models/
│   ├── MODULE_README.md
│   ├── __init__.py
│   ├── browser_args.py
│   ├── db_args.py
│   ├── fs_args.py
│   ├── math_args.py
│   ├── memory_args.py
│   ├── shell_args.py
│   ├── text_args.py
│   ├── web_args.py
├── browser_sessions.py
├── client_waiter.py
├── executor.py
├── external_handlers.py
├── external_mcp.py
├── guardrails.py
├── handle_tool_calls.py
├── implementations/
│   ├── MODULE_README.md
│   ├── __init__.py
│   ├── _summarize_helper.py
│   ├── browser.py
│   ├── code.py
│   ├── ctx.py
│   ├── database.py
│   ├── filesystem.py
│   ├── math.py
│   ├── memory.py
│   ├── news.py
│   ├── notes.py
│   ├── personal_tables.py
│   ├── questionnaire.py
│   ├── seo.py
│   ├── shell.py
│   ├── tasks.py
│   ├── text.py
│   ├── tool_component.py
│   ├── travel.py
│   ├── picklists_tools.py
│   ├── datasets_tools.py
│   ├── vsc.py
│   ├── web.py
├── lifecycle.py
├── logger.py
├── models.py
├── output_models/
│   ├── __init__.py
│   ├── seo.py
├── registry.py
├── streaming.py
├── tests/
│   ├── MODULE_README.md
│   ├── mimic_model_tests.py
│   ├── register_toolcomp_tools.py
│   ├── test_seo_tools.py
│   ├── test_tool_component.py
│   ├── test_toolcomp_tools.py
├── tools_db.py
# excluded: 7 .md
```
<!-- /AUTO:tree -->

<!-- AUTO:signatures -->
## API Signatures

> Auto-generated via `output_mode="{mode}"`. ~5-10% token cost vs full source.
> For full source, open the individual files directly.
> Submodules with their own `MODULE_README.md` are collapsed to a single stub line.

```
---
Filepath: matrx_ai/tools/external_handlers.py  [python]

  ExternalToolHandler = Callable[[dict[str, Any], ToolContext], Awaitable[ToolResult]]
  class ExternalHandlerRegistry:
      def __init__(self) -> None
      def get_instance(cls) -> ExternalHandlerRegistry
      def register(self, tool_name: str, handler: ExternalToolHandler) -> None
      def register_app_handler(self, source_app: str, handler: ExternalToolHandler) -> None
      def unregister(self, tool_name: str) -> bool
      def unregister_app_handler(self, source_app: str) -> bool
      def get_handler(self, tool_name: str, source_app: str | None) -> ExternalToolHandler | None
      def has_handler(self, tool_name: str, source_app: str | None) -> bool
      def registered_tools(self) -> list[str]
      def registered_apps(self) -> list[str]
      def __repr__(self) -> str
  class ExternalToolAdapter:
      def __init_subclass__(cls, **kwargs: Any) -> None
      def register(self, registry: ExternalHandlerRegistry | None = None) -> None
      def _make_handler(self, method: Callable[..., Awaitable[Any]], tool_name: str) -> ExternalToolHandler
      async def _app_dispatcher(self, args: dict[str, Any], ctx: ToolContext) -> ToolResult
      async def _on_conversation_end(self, conversation_id: str) -> None
      async def on_conversation_end(self, conversation_id: str) -> None
      async def dispatch(self, args: dict[str, Any], ctx: ToolContext) -> ToolResult
  def external_tool(tool_name: str) -> Callable[[_F], _F]
  def _normalise_result(raw: Any, tool_name: str, call_id: str, started_at: float) -> ToolResult
  def register_external_tool_handler(tool_name: str, handler: ExternalToolHandler) -> None
  def register_external_app_handler(source_app: str, handler: ExternalToolHandler) -> None
  async def invoke_external_handler(tool_name: str, source_app: str | None, args: dict[str, Any], ctx: ToolContext) -> ToolResult
  def decorator(func: _F) -> _F
  async def wrapper(self: ExternalToolAdapter, args: dict[str, Any], ctx: ToolContext) -> Any
  async def handler(args: dict[str, Any], ctx: ToolContext) -> ToolResult



---
Filepath: matrx_ai/tools/agent_tool.py  [python]

  async def execute_agent_tool(tool_def: ToolDefinition, args: dict[str, Any], ctx: ToolContext) -> ToolResult
  async def register_agent_as_tool(prompt_id: str, tool_name: str, description: str, input_schema: dict[str, Any] | None = None, max_calls_per_conversation: int = 5, cost_cap_per_call: float = 1.0, timeout_seconds: float = 300.0) -> ToolDefinition



---
Filepath: matrx_ai/tools/external_mcp.py  [python]

  class ExternalMCPClient:
      def __init__(self, timeout: float = 120.0)
      async def discover_tools(self, server_url: str, auth: dict[str, Any] | None = None) -> list[ToolDefinition]
      async def call_tool(self, tool_def: ToolDefinition, args: dict[str, Any], ctx: ToolContext) -> ToolResult
      async def _send(self, server_url: str, payload: dict[str, Any], auth: dict[str, Any] | None = None) -> dict[str, Any]
      def _build_request(self, method: str, params: dict[str, Any]) -> dict[str, Any]
      def _strip_namespace(name: str) -> str
      def _schema_to_params(input_schema: dict[str, Any]) -> dict[str, Any]



---
Filepath: matrx_ai/tools/models.py  [python]

  CxToolCallStatus = Literal['pending', 'running', 'completed', 'error']
  class ToolType(StrEnum):
  class ToolError(BaseModel):
      # fields: error_type: str, message: str, traceback: str | None = None, is_retryable: bool = False, suggested_action: str | None = None
      def to_agent_message(self) -> str
  class ToolResult(BaseModel):
      # fields: success: bool, output: Any = None, error: ToolError | None = None, usage: dict[str, Any] | None = None, child_usages: list[TokenUsage] = list(), started_at: float = 0.0, completed_at: float = 0.0, duration_ms: int = 0, tool_name: str = '', call_id: str = '', retry_count: int = 0, should_persist_output: bool = False, persist_key: str | None = None
      def compute_duration(self) -> None
      def to_tool_result_content(self) -> dict[str, Any]
  class GuardrailResult(BaseModel):
      # fields: blocked: bool = False, reason: str | None = None, error_type: str = 'guardrail', suggested_action: str | None = None
      def to_tool_result_content(self, call_id: str = '', tool_name: str = '') -> dict[str, Any]
  class ToolContext(BaseModel):
      # fields: call_id: str, tool_name: str = '', iteration: int = 0, parent_agent_name: str | None = None, user_role: str = 'user', recursion_depth: int = 0, cost_budget_remaining: float | None = None, calls_remaining_this_conversation: int | None = None
      def user_id(self) -> str
      def conversation_id(self) -> str
      def request_id(self) -> str
      def emitter(self) -> Emitter | None
      def api_keys(self) -> dict[str, str]
      def project_id(self) -> str | None
      def organization_id(self) -> str | None
  class CustomToolInputSchema(BaseModel):
      # fields: type: Literal['object'] = 'object', properties: dict[str, Any] = dict(), required: list[str] = list()
  class CustomTool(BaseModel):
      # fields: name: str, description: str = '', input_schema: CustomToolInputSchema = CustomToolInputSchema()
      def get_provider_format(self, provider: str) -> dict[str, Any]
      def from_dict(cls, data: dict[str, Any]) -> 'CustomTool'
  class ToolDefinition(BaseModel):
      # fields: name: str, tool_id: str | None = None, description: str = '', parameters: dict[str, Any] = dict(), output_schema: dict[str, Any] | None = None, annotations: list[dict[str, Any]] = list(), tool_type: ToolType = ToolType.LOCAL, function_path: str = '', source_app: str | None = None, category: str | None = None, tags: list[str] = list(), icon: str | None = None, is_active: bool = True, semver: str = '1.0.0', version: int = 1, prompt_id: str | None = None, mcp_server_url: str | None = None, mcp_server_auth: dict[str, Any] | None = None, max_calls_per_conversation: int | None = None, max_calls_per_minute: int | None = None, cost_cap_per_call: float | None = None, timeout_seconds: float = 120.0, max_recursion_depth: int = 3, on_call_message_template: str | None = None
      def _build_json_schema(self, *, strip_openai_unsupported: bool = False) -> dict[str, Any]
      def _process_nested(schema: dict[str, Any] | str, strip_unsupported: bool) -> dict[str, Any]
      def to_mcp_format(self) -> dict[str, Any]
      def to_openai_format(self) -> dict[str, Any]
      def to_openai_responses_format(self) -> dict[str, Any]
      def to_google_format(self) -> dict[str, Any]
      def to_anthropic_format(self) -> dict[str, Any]
      def get_provider_format(self, provider: str) -> dict[str, Any]
      def format_user_message(self, arguments: dict[str, Any]) -> str
  class CxToolCallRecord(BaseModel):
      # fields: id: str = lambda(), conversation_id: str, message_id: str | None = None, created_by: str, request_id: str | None = None, tool_name: str, tool_type: ToolType = ToolType.LOCAL, call_id: str, status: CxToolCallStatus = 'pending', arguments: dict[str, Any] = dict(), success: bool = True, output: str | None = None, output_type: str = 'text', is_error: bool = False, error_type: str | None = None, error_message: str | None = None, duration_ms: int = 0, started_at: datetime = lambda(), completed_at: datetime = lambda(), input_tokens: int = 0, output_tokens: int = 0, total_tokens: int = 0, cost_usd: Decimal = Decimal('0'), iteration: int = 0, retry_count: int = 0, parent_call_id: str | None = None, execution_events: list[dict[str, Any]] = list(), persist_key: str | None = None, file_path: str | None = None, metadata: dict[str, Any] = dict(), created_at: datetime = lambda(), deleted_at: datetime | None = None
  def _build(param_dict: dict[str, Any] | str) -> dict[str, Any]



---
Filepath: matrx_ai/tools/registry.py  [python]

  class ToolRegistryV2:
      def __init__(self) -> None
      def get_instance(cls) -> ToolRegistryV2
      async def load_from_database(self) -> int
      def load_from_database_sync(self) -> int
      def _load_rows(self, rows: list[dict[str, Any]]) -> int
      def load_from_definitions(self, definitions: list[ToolDefinition]) -> int
      def register_local(self, name: str, func: Callable[..., Awaitable[Any]], description: str = '', category: str | None = None, tags: list[str] | None = None, **overrides: Any) -> ToolDefinition
      def register(self, tool_def: ToolDefinition) -> None
      def unregister(self, name: str) -> bool
      def get(self, name_or_id: str) -> ToolDefinition | None
      def _resolve_tool_name(self, name_or_id: str) -> str | None
      def get_provider_tools(self, tool_names: list[str], provider: str) -> list[dict[str, Any]]
      def _emit_missing_tools_warning(self, missing: list[str], requested: list[str], provider: str) -> None
      def list_tools(self, category: str | None = None, tags: list[str] | None = None, tool_type: ToolType | None = None, active_only: bool = True) -> list[ToolDefinition]
      def list_tool_names(self) -> list[str]
      def loaded(self) -> bool
      def count(self) -> int
      async def _fetch_tools_async() -> list[dict[str, Any]]
      def _fetch_tools_via_orm_sync() -> list[dict[str, Any]]
      def _row_to_definition(row: dict[str, Any]) -> ToolDefinition
      def _resolve_callable(function_path: str) -> Callable[..., Awaitable[Any]]
      def _pydantic_to_param_dict(model_cls: type[BaseModel]) -> dict[str, Any]
      async def register_mcp_server(self, server_url: str, server_name: str, mcp_client: Any | None = None) -> list[str]
  async def _warn() -> None



---
Filepath: matrx_ai/tools/__init__.py  [python]




---
Filepath: matrx_ai/tools/browser_sessions.py  [python]

  SESSION_TTL_SECONDS = 300
  EVICTION_INTERVAL_SECONDS = 60
  class BrowserSession:
      async def close(self) -> None
  class BrowserSessionManager:
      def __init__(self) -> None
      def _ensure_eviction_task(self) -> None
      async def _eviction_loop(self) -> None
      async def create(self) -> BrowserSession
      async def get(self, session_id: str) -> BrowserSession | None
      async def close(self, session_id: str) -> None
      async def close_all(self) -> None
      async def evict_stale(self) -> None
      def active_count(self) -> int
      def session_ids(self) -> list[str]
  def get_browser_session_manager() -> BrowserSessionManager
  def _shutdown_browser_sessions() -> None



---
Filepath: matrx_ai/tools/logger.py  [python]

  class ToolExecutionLogger:
      async def log_started(self, ctx: ToolContext, tool_def: ToolDefinition, arguments: dict[str, Any]) -> str
      async def log_completed(self, row_id: str, result: ToolResult, execution_events: list[dict[str, Any]] | None = None) -> None
      async def log_error(self, row_id: str, result: ToolResult, execution_events: list[dict[str, Any]] | None = None) -> None
      async def link_message(self, row_id: str, message_id: str) -> None
      async def backfill_message_id(self, call_id: str, conversation_id: str, message_id: str) -> None
      async def _update_row(self, row_id: str, data: dict[str, Any]) -> None
      def _truncate_arguments(arguments: dict[str, Any]) -> dict[str, Any]
      def _serialize_output(output: Any) -> tuple[str | None, str]
      def _aggregate_usage(result: ToolResult) -> tuple[int, int, float]



---
Filepath: matrx_ai/tools/guardrails.py  [python]

  ToolCallLike = dict
  class _CallRecord:
  class GuardrailEngine:
      def __init__(self) -> None
      async def check(self, tool_name: str, arguments: dict, ctx: ToolContext, tool_def: ToolDefinition) -> GuardrailResult
      def record_call(self, tool_name: str, arguments: dict, ctx: ToolContext) -> None
      def clear_conversation(self, conversation_id: str) -> None
      def _check_duplicate(self, tool_name: str, arguments: dict, ctx: ToolContext) -> GuardrailResult
      def _check_rate_limit(self, tool_name: str, ctx: ToolContext, tool_def: ToolDefinition) -> GuardrailResult
      def _check_conversation_limit(self, tool_name: str, ctx: ToolContext, tool_def: ToolDefinition) -> GuardrailResult
      def _check_cost_budget(self, ctx: ToolContext, tool_def: ToolDefinition) -> GuardrailResult
      def _check_loop_detection(self, tool_name: str, arguments: dict, ctx: ToolContext) -> GuardrailResult
      def _check_recursion_depth(self, ctx: ToolContext, tool_def: ToolDefinition) -> GuardrailResult
      def _hash_args(arguments: dict) -> str
      def _similarity(hash_a: str, hash_b: str) -> float



---
Filepath: matrx_ai/tools/lifecycle.py  [python]

  CleanupFn = Callable[[], Coroutine[Any, Any, None]]
  AdapterCleanupFn = Callable[[str], Coroutine[Any, Any, None]]
  class ToolLifecycleManager:
      def __init__(self) -> None
      def get_instance(cls) -> ToolLifecycleManager
      def register_cleanup(self, conversation_id: str, cleanup_fn: CleanupFn) -> None
      def register_external_adapter_cleanup(self, cleanup_fn: AdapterCleanupFn) -> None
      def touch(self, conversation_id: str) -> None
      async def cleanup_conversation(self, conversation_id: str) -> int
      async def cleanup_idle(self) -> list[str]
      async def cleanup_all(self) -> int
      def sweep_running(self) -> bool
      def start_background_sweep(self) -> None
      def stop_background_sweep(self) -> None
      async def _sweep_loop(self) -> None
      def active_conversations(self) -> int
      def pending_cleanups(self) -> int



---
Filepath: matrx_ai/tools/handle_tool_calls.py  [python]

  def get_executor() -> ToolExecutor
  async def initialize_tool_system() -> int
  def initialize_tool_system_sync() -> int
  async def handle_tool_calls_v2(tool_calls_raw: list[dict[str, Any]], *, iteration: int, recursion_depth: int = 0, cost_budget_remaining: float | None = None, client_tools: frozenset[str] | None = None) -> tuple[list[dict[str, Any]], list[TokenUsage]]
  async def cleanup_conversation(conversation_id: str) -> None



---
Filepath: matrx_ai/tools/client_waiter.py  [python]

  class ClientToolWaiter:
      def __init__(self) -> None
      def get_instance(cls) -> ClientToolWaiter
      def create(self, call_id: str) -> asyncio.Future[ToolResult]
      async def wait_for_result(self, call_id: str, timeout: float = _DEFAULT_TIMEOUT_SECONDS) -> ToolResult
      def resolve(self, call_id: str, result: ToolResult) -> bool
      def reject(self, call_id: str, error_message: str) -> bool
      def pending_count(self) -> int
      def pending_call_ids(self) -> list[str]
      def is_pending(self, call_id: str) -> bool
      def __repr__(self) -> str
  def get_client_tool_waiter() -> ClientToolWaiter



---
Filepath: matrx_ai/tools/streaming.py  [python]

  class ToolStreamEvent(BaseModel):
      # fields: event: Literal['tool_started', 'tool_progress', 'tool_step', 'tool_result_preview', 'tool_completed', 'tool_error'], call_id: str, tool_name: str, timestamp: float = time(), message: str | None = None, show_spinner: bool = True, data: dict[str, Any] = dict()
  class ToolStreamManager:
      def __init__(self, emitter: Emitter | None, call_id: str, tool_name: str)
      async def emit(self, event: ToolStreamEvent) -> None
      async def started(self, message: str = 'Starting...', arguments: dict[str, Any] | None = None) -> None
      async def completed(self, message: str = 'Done', result: ToolResult | None = None) -> None
      async def error(self, message: str, error_type: str = 'execution') -> None
      async def progress(self, message: str, data: dict[str, Any] | None = None) -> None
      async def step(self, step_name: str, message: str, data: dict[str, Any] | None = None) -> None
      async def result_preview(self, preview: str) -> None
      def get_events_for_persistence(self) -> list[dict[str, Any]]



---
Filepath: matrx_ai/tools/tools_db.py  [python]

  ToolsBase = get_base('ToolsBase')
  class ToolsManager(ToolsBase):
      def __new__(cls, *args, **kwargs)
      def __init__(self)
      async def load_all_tools(self)
      async def get_all_source_app_tools(self, source_app: str)
      async def get_all_matrx_ai_tools(self)
      async def get_all_matrx_local_tools(self)
      async def get_tool_with_all_related(self, id: str)



---
Filepath: matrx_ai/tools/executor.py  [python]

  class ToolExecutor:
      def __init__(self, registry: ToolRegistryV2, guardrails: GuardrailEngine | None = None, execution_logger: ToolExecutionLogger | None = None, lifecycle: ToolLifecycleManager | None = None)
      def build_context(*, call_id: str, tool_name: str, iteration: int = 0, recursion_depth: int = 0, cost_budget_remaining: float | None = None, calls_remaining: int | None = None) -> ToolContext
      async def execute(self, tool_name: str, arguments: dict[str, Any], ctx: ToolContext, client_tools: frozenset[str] | None = None) -> tuple[dict[str, Any], ToolResult]
      async def execute_batch(self, tool_calls: list[dict[str, Any]], ctx_base: ToolContext, client_tools: frozenset[str] | None = None) -> tuple[list[dict[str, Any]], list[ToolResult]]
      async def _dispatch(self, tool_def: ToolDefinition, args: dict[str, Any], ctx: ToolContext, stream: ToolStreamManager, client_tools: frozenset[str] | None = None) -> ToolResult
      async def _execute_delegated(self, tool_def: ToolDefinition, args: dict[str, Any], ctx: ToolContext) -> ToolResult
      async def _execute_local(self, tool_def: ToolDefinition, args: dict[str, Any], ctx: ToolContext, stream: ToolStreamManager) -> ToolResult
      async def _execute_external_mcp(self, tool_def: ToolDefinition, args: dict[str, Any], ctx: ToolContext) -> ToolResult
      async def _execute_agent(self, tool_def: ToolDefinition, args: dict[str, Any], ctx: ToolContext) -> ToolResult
      async def _execute_external_handler(self, tool_def: ToolDefinition, args: dict[str, Any], ctx: ToolContext) -> ToolResult
      def _unknown_tool_result(tool_name: str, call_id: str, started_at: float) -> ToolResult



---
Filepath: matrx_ai/tools/output_models/seo.py  [python]

  class MonthlySearchItem(BaseModel):
      # fields: year: int = 0, month: int = 0, search_volume: int = 0
  class SeoKeywordDataItem(BaseModel):
      # fields: keyword: str = '', cpc: float = 0.0, competition: str = '', search_volume: int = 0, competition_index: int = 0, monthly_searches: list[MonthlySearchItem] = list()
  class SeoKeywordDataOutput(BaseModel):
      # fields: keywords_data: list[SeoKeywordDataItem] = list(), total_keywords: int = 0, date_range: dict[str, str] = dict(), search_parameters: dict[str, Any] = dict()
  def normalize_keyword_item(raw: dict[str, Any]) -> SeoKeywordDataItem



---
Filepath: matrx_ai/tools/output_models/__init__.py  [python]




---
Submodule: matrx_ai/tools/implementations/  [23 files — full detail in matrx_ai/tools/implementations/MODULE_README.md]

---
Submodule: matrx_ai/tools/arg_models/  [9 files — full detail in matrx_ai/tools/arg_models/MODULE_README.md]

```
<!-- /AUTO:signatures -->

<!-- AUTO:call_graph -->
## Call Graph

> Auto-generated. Scoped to: `handle_tool_calls, executor, registry, guardrails`.
> Shows which functions call which. `async` prefix = caller is an async function.
> Method calls shown as `receiver.method()`. Private methods (`_`) excluded by default.

### Call graph: ai.tools.executor

```
Global Scope → logging.getLogger(__name__) (line 19)
  ai.tools.executor.__init__ → ai.tools.executor.GuardrailEngine() (line 51)
  ai.tools.executor.__init__ → ai.tools.executor.ToolExecutionLogger() (line 52)
  ai.tools.executor.__init__ → ToolLifecycleManager.get_instance() (line 53)
  ai.tools.executor.build_context → ai.tools.executor.ToolContext() (line 69)
  async ai.tools.executor.execute → time.time() (line 96)
  async ai.tools.executor.execute → ...get(tool_name) (line 99)
  async ai.tools.executor.execute → ...list_tool_names() (line 107)
  async ai.tools.executor.execute → result.to_tool_result_content() (line 113)
  async ai.tools.executor.execute → ...log_started(ctx, tool_def, arguments) (line 116)
  async ai.tools.executor.execute → ai.tools.executor.ToolStreamManager(ctx.emitter, ctx.call_id, tool_name) (line 118)
  async ai.tools.executor.execute → ...check(tool_name, arguments, ctx, tool_def) (line 121)
  async ai.tools.executor.execute → ai.tools.executor.ToolResult() (line 125)
  async ai.tools.executor.execute → ai.tools.executor.ToolError() (line 127)
  async ai.tools.executor.execute → time.time() (line 133)
  async ai.tools.executor.execute → error_result.compute_duration() (line 137)
  async ai.tools.executor.execute → stream.error(guardrail_result.reason or 'Blocked', guardrail_result.error_type) (line 138)
  async ai.tools.executor.execute → stream.get_events_for_persistence() (line 141)
  async ai.tools.executor.execute → asyncio.create_task(self.execution_logger.log_error(row_id, error_result, events)) (line 142)
  async ai.tools.executor.execute → ...log_error(row_id, error_result, events) (line 143)
  async ai.tools.executor.execute → error_result.to_tool_result_content() (line 145)
  async ai.tools.executor.execute → ...record_call(tool_name, arguments, ctx) (line 147)
  async ai.tools.executor.execute → tool_def.format_user_message(arguments) (line 150)
  async ai.tools.executor.execute → stream.started(user_message) (line 151)
  async ai.tools.executor.execute → ...touch(ctx.conversation_id) (line 154)
  async ai.tools.executor.execute → asyncio.wait_for(self._dispatch(tool_def, arguments, ctx, stream)) (line 158)
  async ai.tools.executor.execute → ai.tools.executor.ToolResult() (line 163)
  async ai.tools.executor.execute → ai.tools.executor.ToolError() (line 165)
  async ai.tools.executor.execute → time.time() (line 172)
  async ai.tools.executor.execute → ai.tools.executor.ToolResult() (line 177)
  async ai.tools.executor.execute → ai.tools.executor.ToolError() (line 179)
  async ai.tools.executor.execute → tb.format_exc() (line 182)
  async ai.tools.executor.execute → time.time() (line 187)
  async ai.tools.executor.execute → result.compute_duration() (line 192)
  async ai.tools.executor.execute → stream.completed('Done') (line 196)
  async ai.tools.executor.execute → stream.error(msg, result.error.error_type if result.error else 'execution') (line 199)
  async ai.tools.executor.execute → stream.get_events_for_persistence() (line 204)
  async ai.tools.executor.execute → asyncio.create_task(self.execution_logger.log_completed(row_id, result, events)) (line 206)
  async ai.tools.executor.execute → ...log_completed(row_id, result, events) (line 207)
  async ai.tools.executor.execute → asyncio.create_task(self.execution_logger.log_error(row_id, result, events)) (line 210)
  async ai.tools.executor.execute → ...log_error(row_id, result, events) (line 210)
  async ai.tools.executor.execute → result.to_tool_result_content() (line 212)
  async ai.tools.executor.execute_batch → tc.get('name', '') (line 232)
  async ai.tools.executor.execute_batch → tc.get('arguments', {}) (line 233)
  async ai.tools.executor.execute_batch → tc.get('call_id') (line 234)
  async ai.tools.executor.execute_batch → tc.get('id') (line 234)
  async ai.tools.executor.execute_batch → ctx_base.model_copy() (line 236)
  async ai.tools.executor.execute_batch → tasks.append(self.execute(name, arguments, child_ctx)) (line 242)
  async ai.tools.executor.execute_batch → self.execute(name, arguments, child_ctx) (line 242)
  async ai.tools.executor.execute_batch → asyncio.gather(*tasks) (line 244)
  async ai.tools.executor.execute_batch → tc.get('call_id') (line 252)
  async ai.tools.executor.execute_batch → tc.get('id') (line 252)
  async ai.tools.executor.execute_batch → ai.tools.executor.ToolResult() (line 253)
  async ai.tools.executor.execute_batch → ai.tools.executor.ToolError() (line 255)
  async ai.tools.executor.execute_batch → tb.format_exc() (line 258)
  async ai.tools.executor.execute_batch → time.time() (line 260)
  async ai.tools.executor.execute_batch → time.time() (line 261)
  async ai.tools.executor.execute_batch → tc.get('name', '') (line 262)
  async ai.tools.executor.execute_batch → content_results.append(err_result.to_tool_result_content()) (line 265)
  async ai.tools.executor.execute_batch → err_result.to_tool_result_content() (line 265)
  async ai.tools.executor.execute_batch → full_results.append(err_result) (line 266)
  async ai.tools.executor.execute_batch → content_results.append(content_dict) (line 269)
  async ai.tools.executor.execute_batch → full_results.append(full_result) (line 270)
  async ai.tools.executor._dispatch → ai.tools.executor.ToolResult() (line 293)
  async ai.tools.executor._dispatch → ai.tools.executor.ToolError() (line 295)
  async ai.tools.executor._dispatch → time.time() (line 299)
  async ai.tools.executor._dispatch → time.time() (line 300)
  async ai.tools.executor._execute_local → ai.tools.executor.ToolResult() (line 314)
  async ai.tools.executor._execute_local → ai.tools.executor.ToolError() (line 316)
  async ai.tools.executor._execute_local → time.time() (line 320)
  async ai.tools.executor._execute_local → time.time() (line 321)
  async ai.tools.executor._execute_local → time.time() (line 326)
  async ai.tools.executor._execute_local → ai.tools.executor.func(args, ctx) (line 327)
  async ai.tools.executor._execute_local → time.time() (line 331)
  async ai.tools.executor._execute_local → raw_result.get('status') (line 338)
  async ai.tools.executor._execute_local → ai.tools.executor.ToolResult() (line 339)
  async ai.tools.executor._execute_local → raw_result.get('result') (line 342)
  async ai.tools.executor._execute_local → raw_result.get('error', raw_result.get('result')) (line 344)
  async ai.tools.executor._execute_local → raw_result.get('result') (line 344)
  async ai.tools.executor._execute_local → ai.tools.executor.ToolError() (line 346)
  async ai.tools.executor._execute_local → raw_result.get('error', raw_result.get('result', '')) (line 348)
  async ai.tools.executor._execute_local → raw_result.get('result', '') (line 348)
  async ai.tools.executor._execute_local → time.time() (line 353)
  async ai.tools.executor._execute_local → ai.tools.executor.ToolResult() (line 358)
  async ai.tools.executor._execute_local → time.time() (line 362)
  async ai.tools.executor._execute_external_mcp → ai.tools.executor.ExternalMCPClient() (line 375)
  async ai.tools.executor._execute_external_mcp → client.call_tool(tool_def, args, ctx) (line 376)
  async ai.tools.executor._execute_agent → ctx.model_copy() (line 386)
  async ai.tools.executor._execute_agent → ai.tools.executor.execute_agent_tool(tool_def, args, child_ctx) (line 391)
  ai.tools.executor._unknown_tool_result → ai.tools.executor.ToolResult() (line 401)
  ai.tools.executor._unknown_tool_result → ai.tools.executor.ToolError() (line 403)
  ai.tools.executor._unknown_tool_result → time.time() (line 409)
```

### Call graph: ai.tools.guardrails

```
Global Scope → logging.getLogger(__name__) (line 12)
  async ai.tools.guardrails.check → ai.tools.guardrails.GuardrailResult() (line 60)
  ai.tools.guardrails.record_call → append(_CallRecord(tool_name=tool_name, args_hash=self._hash_args(arguments), timestamp=time.time(), iteration=ctx.iteration)) (line 63)
  ai.tools.guardrails.record_call → ai.tools.guardrails._CallRecord() (line 64)
  ai.tools.guardrails.record_call → time.time() (line 67)
  ai.tools.guardrails.clear_conversation → ...pop(conversation_id, None) (line 73)
  ai.tools.guardrails._check_duplicate → ...get(ctx.conversation_id, []) (line 85)
  ai.tools.guardrails._check_duplicate → ai.tools.guardrails.GuardrailResult() (line 90)
  ai.tools.guardrails._check_duplicate → ai.tools.guardrails.GuardrailResult() (line 96)
  ai.tools.guardrails._check_rate_limit → ai.tools.guardrails.GuardrailResult() (line 105)
  ai.tools.guardrails._check_rate_limit → time.time() (line 107)
  ai.tools.guardrails._check_rate_limit → ...get(ctx.conversation_id, []) (line 108)
  ai.tools.guardrails._check_rate_limit → ai.tools.guardrails.GuardrailResult() (line 116)
  ai.tools.guardrails._check_rate_limit → ai.tools.guardrails.GuardrailResult() (line 122)
  ai.tools.guardrails._check_conversation_limit → ai.tools.guardrails.GuardrailResult() (line 131)
  ai.tools.guardrails._check_conversation_limit → ...get(ctx.conversation_id, []) (line 133)
  ai.tools.guardrails._check_conversation_limit → ai.tools.guardrails.GuardrailResult() (line 137)
  ai.tools.guardrails._check_conversation_limit → ai.tools.guardrails.GuardrailResult() (line 143)
  ai.tools.guardrails._check_cost_budget → ai.tools.guardrails.GuardrailResult() (line 151)
  ai.tools.guardrails._check_cost_budget → ai.tools.guardrails.GuardrailResult() (line 163)
  ai.tools.guardrails._check_cost_budget → ai.tools.guardrails.GuardrailResult() (line 172)
  ai.tools.guardrails._check_loop_detection → ...get(ctx.conversation_id, []) (line 180)
  ai.tools.guardrails._check_loop_detection → ai.tools.guardrails.GuardrailResult() (line 184)
  ai.tools.guardrails._check_loop_detection → ai.tools.guardrails.GuardrailResult() (line 192)
  ai.tools.guardrails._check_loop_detection → ai.tools.guardrails.GuardrailResult() (line 204)
  ai.tools.guardrails._check_recursion_depth → ai.tools.guardrails.GuardrailResult() (line 214)
  ai.tools.guardrails._check_recursion_depth → ai.tools.guardrails.GuardrailResult() (line 223)
  ai.tools.guardrails._hash_args → json.dumps(arguments) (line 231)
  ai.tools.guardrails._hash_args → hexdigest() (line 232)
  ai.tools.guardrails._hash_args → hashlib.md5(normalized.encode()) (line 232)
  ai.tools.guardrails._hash_args → normalized.encode() (line 232)
```

### Call graph: ai.tools.registry

```
ai.tools.registry.get_instance → ai.tools.registry.cls() (line 33)
  ai.tools.registry._load_rows → row.get('name', '?') (line 68)
  ai.tools.registry._load_rows → failed.append(tool_name) (line 76)
  ai.tools.registry._load_rows → row.get('function_path', '') (line 80)
  ai.tools.registry._load_rows → traceback.format_exc() (line 82)
  ai.tools.registry.load_from_definitions → traceback.format_exc() (line 107)
  ai.tools.registry.register_local → inspect.signature(func) (line 137)
  ai.tools.registry.register_local → ...values() (line 138)
  ai.tools.registry.register_local → overrides.pop('parameters', {}) (line 140)
  ai.tools.registry.register_local → ai.tools.registry.issubclass(annotation, BaseModel) (line 146)
  ai.tools.registry.register_local → ai.tools.registry.ToolDefinition() (line 153)
  ai.tools.registry.unregister → ...pop(name, None) (line 172)
  ai.tools.registry.get → ...get(name) (line 179)
  ai.tools.registry.get_provider_tools → ...keys() (line 203)
  ai.tools.registry.get_provider_tools → get_provider_format(provider) (line 211)
  ai.tools.registry.list_tools → ...values() (line 224)
  ai.tools.registry.list_tools → issubset(set(t.tags)) (line 229)
  ai.tools.registry.list_tools → result.append(t) (line 233)
  ai.tools.registry.list_tool_names → ...keys() (line 237)
  async ai.tools.registry._fetch_tools_async → tools_manager.filter_tool() (line 254)
  async ai.tools.registry._fetch_tools_async → item.to_dict() (line 255)
  async ai.tools.registry._fetch_tools_async → traceback.format_exc() (line 260)
  ai.tools.registry._fetch_tools_via_orm_sync → tools_manager.filter_items_sync() (line 270)
  ai.tools.registry._fetch_tools_via_orm_sync → item.to_dict() (line 272)
  ai.tools.registry._fetch_tools_via_orm_sync → traceback.format_exc() (line 278)
  ai.tools.registry._row_to_definition → row.get('function_path', '') (line 288)
  ai.tools.registry._row_to_definition → function_path.startswith('agent:') (line 291)
  ai.tools.registry._row_to_definition → function_path.split(':', 1) (line 293)
  ai.tools.registry._row_to_definition → function_path.startswith('mcp:') (line 294)
  ai.tools.registry._row_to_definition → row.get('annotations') (line 297)
  ai.tools.registry._row_to_definition → guardrail_config.update(ann) (line 302)
  ai.tools.registry._row_to_definition → row.get('parameters') (line 304)
  ai.tools.registry._row_to_definition → raw_params.get('type') (line 307)
  ai.tools.registry._row_to_definition → raw_params.get('required', []) (line 311)
  ai.tools.registry._row_to_definition → params.items() (line 312)
  ai.tools.registry._row_to_definition → ai.tools.registry.ToolDefinition() (line 318)
  ai.tools.registry._row_to_definition → row.get('description', '') (line 320)
  ai.tools.registry._row_to_definition → row.get('output_schema') (line 322)
  ai.tools.registry._row_to_definition → row.get('annotations') (line 323)
  ai.tools.registry._row_to_definition → row.get('category') (line 326)
  ai.tools.registry._row_to_definition → row.get('tags') (line 327)
  ai.tools.registry._row_to_definition → row.get('icon') (line 328)
  ai.tools.registry._row_to_definition → row.get('is_active', True) (line 329)
  ai.tools.registry._row_to_definition → row.get('version', '1.0.0') (line 330)
  ai.tools.registry._row_to_definition → guardrail_config.get('max_calls_per_conversation') (line 332)
  ai.tools.registry._row_to_definition → guardrail_config.get('max_calls_per_minute') (line 335)
  ai.tools.registry._row_to_definition → guardrail_config.get('cost_cap_per_call') (line 336)
  ai.tools.registry._row_to_definition → guardrail_config.get('timeout_seconds', 120.0) (line 337)
  ai.tools.registry._resolve_callable → function_path.startswith('agent:') (line 344)
  ai.tools.registry._resolve_callable → function_path.startswith('mcp:') (line 345)
  ai.tools.registry._resolve_callable → function_path.rsplit('.', 1) (line 348)
  ai.tools.registry._resolve_callable → importlib.import_module(module_path) (line 349)
  ai.tools.registry._pydantic_to_param_dict → model_cls.model_json_schema() (line 356)
  ai.tools.registry._pydantic_to_param_dict → schema.get('required', []) (line 358)
  ai.tools.registry._pydantic_to_param_dict → schema.get('properties', {}) (line 359)
  ai.tools.registry._pydantic_to_param_dict → properties.items() (line 361)
  ai.tools.registry._pydantic_to_param_dict → field_schema.get('type', 'string') (line 363)
  ai.tools.registry._pydantic_to_param_dict → field_schema.get('description', '') (line 364)
  async ai.tools.registry.register_mcp_server → ai.tools.registry.ExternalMCPClient() (line 396)
  async ai.tools.registry.register_mcp_server → mcp_client.discover_tools(server_url) (line 398)
  async ai.tools.registry.register_mcp_server → registered.append(namespaced) (line 406)
```

### Call graph: ai.tools.handle_tool_calls

```
Global Scope → logging.getLogger(__name__) (line 29)
  ai.tools.handle_tool_calls.get_executor → ToolRegistryV2.get_instance() (line 38)
  ai.tools.handle_tool_calls.get_executor → ai.tools.handle_tool_calls.ToolExecutor() (line 50)
  ai.tools.handle_tool_calls.get_executor → ai.tools.handle_tool_calls.GuardrailEngine() (line 52)
  ai.tools.handle_tool_calls.get_executor → ai.tools.handle_tool_calls.ToolExecutionLogger() (line 53)
  ai.tools.handle_tool_calls.get_executor → ToolLifecycleManager.get_instance() (line 54)
  async ai.tools.handle_tool_calls.initialize_tool_system → ToolRegistryV2.get_instance() (line 64)
  async ai.tools.handle_tool_calls.initialize_tool_system → registry.load_from_database() (line 65)
  async ai.tools.handle_tool_calls.initialize_tool_system → ai.tools.handle_tool_calls.get_executor() (line 76)
  async ai.tools.handle_tool_calls.initialize_tool_system → ToolLifecycleManager.get_instance() (line 78)
  async ai.tools.handle_tool_calls.initialize_tool_system → lifecycle.start_background_sweep() (line 79)
  ai.tools.handle_tool_calls.initialize_tool_system_sync → ToolRegistryV2.get_instance() (line 90)
  ai.tools.handle_tool_calls.initialize_tool_system_sync → registry.load_from_database_sync() (line 91)
  ai.tools.handle_tool_calls.initialize_tool_system_sync → ai.tools.handle_tool_calls.get_executor() (line 104)
  async ai.tools.handle_tool_calls.handle_tool_calls_v2 → ai.tools.handle_tool_calls.get_executor() (line 126)
  async ai.tools.handle_tool_calls.handle_tool_calls_v2 → ToolLifecycleManager.get_instance() (line 128)
  async ai.tools.handle_tool_calls.handle_tool_calls_v2 → lifecycle.start_background_sweep() (line 130)
  async ai.tools.handle_tool_calls.handle_tool_calls_v2 → ai.tools.handle_tool_calls.ToolContext() (line 132)
  async ai.tools.handle_tool_calls.handle_tool_calls_v2 → executor.execute_batch(tool_calls_raw, ctx) (line 140)
  async ai.tools.handle_tool_calls.handle_tool_calls_v2 → all_child_usages.extend(result.child_usages) (line 144)
  async ai.tools.handle_tool_calls.cleanup_conversation → ToolLifecycleManager.get_instance() (line 151)
  async ai.tools.handle_tool_calls.cleanup_conversation → lifecycle.cleanup_conversation(conversation_id) (line 152)
  async ai.tools.handle_tool_calls.cleanup_conversation → ai.tools.handle_tool_calls.get_executor() (line 154)
  async ai.tools.handle_tool_calls.cleanup_conversation → guardrails.clear_conversation(conversation_id) (line 155)
```
<!-- /AUTO:call_graph -->

<!-- AUTO:callers -->
## Upstream Callers

> Auto-discovered by scanning all project files that import from this module.
> Set `ENTRY_POINTS` in `generate_readme.py` to pin specific functions.

| Caller | Calls |
|--------|-------|
| `matrx_ai/config/unified_config.py` | `CustomTool()` |
| `matrx_ai/agent_runners/research.py` | `ToolContext()` |
| `matrx_ai/providers/anthropic/translator.py` | `ToolRegistryV2()` |
| `matrx_ai/providers/cerebras/translator.py` | `ToolRegistryV2()` |
| `matrx_ai/providers/generic_openai/translator.py` | `ToolRegistryV2()` |
| `matrx_ai/providers/google/translator.py` | `ToolRegistryV2()` |
| `matrx_ai/providers/groq/translator.py` | `ToolRegistryV2()` |
| `matrx_ai/providers/openai/translator.py` | `ToolRegistryV2()` |
| `matrx_ai/providers/together/translator.py` | `ToolRegistryV2()` |
| `matrx_ai/providers/xai/translator.py` | `ToolRegistryV2()` |
| `matrx_ai/db/persistence.py` | `get_executor()` |
| `matrx_ai/orchestrator/executor.py` | `handle_tool_calls_v2()` |
<!-- /AUTO:callers -->

<!-- AUTO:dependencies -->
## Dependencies

**External packages:** httpx, matrx_connect, matrx_orm, matrx_scraper, matrx_utils, playwright, pydantic, requests, rich, seo
**Internal modules:** matrx_ai._ext, matrx_ai.agent_runners, matrx_ai.agents, matrx_ai.config, matrx_ai.context, matrx_ai.db
<!-- /AUTO:dependencies -->

<!-- AUTO:config -->
## Generation Config

> Auto-managed. Contains the exact parameters used to generate this README.
> Used by parent modules to auto-refresh this file when it is stale.
> Do not edit manually — changes will be overwritten on the next run.

```json
{
  "subdirectory": "matrx_ai/tools",
  "mode": "signatures",
  "scope": null,
  "project_noise": null,
  "include_call_graph": false,
  "entry_points": null,
  "call_graph_exclude": [
    "tests"
  ],
  "signatures_exclude": [
    "tests",
    "db",
    "local_data",
    "docs"
  ]
}
```
<!-- /AUTO:config -->
