| """The tool invocation seam (§8.4) — the one interface the TaskRunner calls. |
| |
| The agent layer stays tool-agnostic (INV-7) by invoking every tool through this |
| protocol, never importing a tool module directly. The **tool team owns the |
| implementation** (KM-418); this file defines only the contract the TaskRunner |
| depends on. |
| |
| Frozen guarantees the implementation must hold: |
| 1. **Never throws.** A tool failure returns `ToolOutput(kind="error", error=...)`, |
| not an exception — the TaskRunner's degrade-and-continue (§7.4) relies on this. |
| (The TaskRunner still wraps calls defensively, as a backstop.) |
| 2. **Returns the `ToolOutput` envelope** (§8.1) — structured data only, never |
| rendered tables or prose (that is the Assembler's job). |
| 3. **`tool_name` comes from the registry** (§9.2); unknown names return an error |
| envelope rather than throwing. |
| """ |
|
|
| from __future__ import annotations |
|
|
| from typing import Any, Protocol, runtime_checkable |
|
|
| from ..planner.contracts import ToolOutput |
|
|
|
|
| @runtime_checkable |
| class ToolInvoker(Protocol): |
| async def invoke(self, tool_name: str, args: dict[str, Any]) -> ToolOutput: ... |
|
|