Dataface Tasks

Replace AI tool dispatch switch with registry-backed handlers

IDMCP_ANALYST_AGENT-REPLACE_AI_TOOL_DISPATCH_SWITCH_WITH_REGISTRY_BACKED_HANDLERS
Statuscompleted
Priorityp1
Milestonem1-ft-analytics-analyst-pilot
Ownerdata-ai-engineer-architect

Problem

Refactor dataface/ai/tools.py so canonical tool schemas and handlers are registered in one place instead of maintained through a growing string-based if/elif dispatch chain. The result should reduce drift between schemas, handlers, and client surfaces and make new tool additions additive rather than branching edits.

Context

dataface/ai/tools.py:dispatch_tool_call routes tool names to implementations via a 7-branch if/elif chain. Each branch manually extracts args from the dict and injects context values. Adding a tool means editing the middle of this function — branching rather than additive. The same tool names are already defined in tool_schemas.py (canonical schemas) and tool_contracts.py (response contracts), so there are three places that must stay in sync.

Key consumers: MCP server (mcp/server.py), playground (apps/playground/ai_service.py), cloud (apps/cloud/apps/ai/views.py), terminal agent (agent.py). All call dispatch_tool_call or handle_tool_call.

Possible Solutions

  1. Dict registry of handler functions — map tool name → callable (args, context) -> dict at module level. dispatch_tool_call becomes a dict lookup. Adding a tool = define one handler function + one dict entry. Minimal abstraction, no decorators, no classes. Recommended — simplest change that solves the problem.

  2. Decorator-based registry@register_tool("name") decorator auto-populates the dict. Slightly more magic; not warranted for 7 tools.

  3. Dataclass/TypedDict tool descriptors — unify schema + handler + contract into one object. Over-engineered for this task; larger blast radius.

Plan

  1. Write a test that asserts TOOL_HANDLERS dict exists and covers all tool names.
  2. In tools.py, define one handler function per tool (_handle_render_dashboard, etc.) that takes (args, context) and returns a dict.
  3. Build TOOL_HANDLERS: dict[str, Callable] mapping names to handlers.
  4. Replace the if/elif chain in dispatch_tool_call with a dict lookup + call.
  5. Keep handle_tool_call, get_tools, and all TOOL_* constants unchanged — public API stable.
  6. Run just test-file tests/core/test_ai_tools.py + just fix.

Implementation Progress

  • Replaced 7-branch if/elif chain in dispatch_tool_call with TOOL_HANDLERS dict mapping tool names to handler functions.
  • Each handler is a standalone function (args, context) -> dict with lazy imports.
  • Shared _resolve_base_dir helper for render/review tools.
  • information_schema alias preserved as a dict entry pointing to _handle_catalog.
  • dispatch_tool_call reduced to 3 lines: lookup, fallback error, call.
  • Public API (handle_tool_call, dispatch_tool_call, get_tools, TOOL_* constants) unchanged.
  • Added TestToolHandlersRegistry test class: coverage check, no-extras check, callable check, unknown-tool check.
  • All 45 AI tests pass, mypy clean, ruff clean, black formatted.

QA Exploration

  • [x] QA exploration completed (or N/A for non-UI tasks)

Review Feedback

  • [ ] Review cleared