`ToolManager.get_all_tools_schema()` exposes every tool to the LLM regardless of agent state
- Dominant language
- Python
- Stars
- 73
- Forks
- 89
- Avg merge
- 14d 58m
- Merged PRs (30d)
- 2
Description
`ToolManager.get_all_tools_schema()` exposes every tool to the LLM regardless of agent state.
A wolf with `0` energy still sees `move_one_step`.
An agent with no grid still sees `teleport_to_location`.
The LLM picks from all options — if it picks wrong, you get either a crash or a silent bad state.
### The Core Problem
Right now in `ToolManager.get_all_tools_schema()`:
```python
# Every agent, every step, gets EVERY tool
return [fn.__tool_schema__ for fn in self.tools.values()]
```
A wolf with `0` energy still sees `move_one_step`.
An agent at the grid boundary still sees `teleport_to_location` with no hint that `(5, 99)` is out of bounds.
The LLM selects from **all possible tools** and hopes the choice is valid.
This ignores whether the tool is actually usable in the current state.
### Example Issues
| Tool | Implicit Assumption | What Breaks |
|-----|-----|-----|
| `move_one_step` | `agent.pos` is not `None` | `TypeError: cannot unpack non-iterable NoneType` |
| `move_one_step` | `grid/space` exists on model | `AttributeError` |
| `teleport_to_location` | coordinates in bounds | silent wrong state or `IndexError` |
| `speak_to` | target agents exist | empty list → silent no-op |
The LLM selects from **all possible tools**, even when they are impossible to execute.
---
## Proposed API
Keep the existing method and add two new ones.
```python
class ToolManager:
# Existing (unchanged)
def get_all_tools_schema(self, selected_tools=None) -> list[dict]: ...
# NEW — hard filter (executor safe)
def get_feasible_tools_schema(self, agent, selected_tools=None) -> list[dict]: ...
# NEW — soft annotations with reasons (planner guidance)
def get_annotated_tools_schema(self, agent, selected_tools=None) -> list[dict]: ...
```
### Pros
- Clear API
- Explicit semantics
- No breaking changes
- Executors only receive feasible tools
- Planners can still reason about infeasible tools
---
## Files That Change
| File | Change |
|-----|-----|
| `tool_decorator.py` | Add `@requires` decorator |
| `tool_manager.py` | Add `get_feasible_tools_schema()` and `get_annotated_tools_schema()` |
| `llm_agent.py` | Add `tool_filter()` method |
| `inbuilt_tools.py` | Add `@requires` to built-in tools |
| `cot.py` | Annotated for planner, feasible for executor |
| `react.py` | Annotated for planner |
| `rewoo.py` | Annotated for planner |
| `reasoning.py` | Use feasible tools for execution |
Contributor guide
Research direction
Start by reading tool_decorator.py and tool_manager.py to understand the existing tool schema flow, then trace how llm_agent.py, cot.py, react.py, rewoo.py, and reasoning.py consume it. Review inbuilt_tools.py for the built-in tool assumptions. Done means the proposed decorators, feasible and annotated schema methods, and planner/executor integrations are implemented without changing the existing method's behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- ai, tooling
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100