wiki: refresh Internals.md §4.2 + §9.1 for #56 tool registry refactor

Jeff Smith 2026-04-11 10:19:10 -06:00
parent 725ef62edd
commit 08f2d9865d

@ -211,20 +211,26 @@ budget; raising the cap would expose this. See **#51**.
### 4.2 Tool dispatch
Tools are plain functions in `ai.py`, registered into `_TOOL_DISPATCH` at
`ai.py:650`. `_execute_tool()` (`ai.py:664`) is a small function that
looks up the handler by name, calls it, logs the turn to
`investigation.log`, and returns the result string. **The control-flow
tool `submit_report` is NOT in `_TOOL_DISPATCH`** because
`_handle_turn_response()` recognizes it specially: it sets `done = True`
and extracts the summary directly from the tool input.
Tools are plain functions in `ai.py`. They are wired up via a single
`register_tool()` call (`ai.py:172`) that lands the schema in one or
more scope lists (`_DIR_TOOLS`, `_SYNTHESIS_TOOLS`, `_SURVEY_TOOLS`)
and the handler in `_TOOL_DISPATCH`. The registrations live below the
tool implementations in `ai.py` and read top-to-bottom in dir-then-
synthesis-then-survey order.
`_execute_tool()` looks up the handler by name in `_TOOL_DISPATCH`,
calls it, logs the turn to `investigation.log`, and returns the result
string. **Tools intercepted by the loop body — `submit_report` and
`submit_survey` — register their schema only and have no handler entry.**
`_handle_turn_response()` recognizes `submit_report` specially: it sets
`done = True` and extracts the summary directly from the tool input.
`think`, `checkpoint`, and `flag` *are* in dispatch, but they have side
effects that just print to stderr or append to `flags.jsonl` — the return
value is always `"ok"`.
When you add a tool: write the function, add it to `_TOOL_DISPATCH`, add
its schema to `_DIR_TOOLS`. That's it.
When you add a tool: write the function, then add one `register_tool()`
call below it. That's it. There is no second place to forget.
### 4.3 Pre-loaded context
@ -425,20 +431,26 @@ A cookbook for the kinds of changes that come up most often.
### 9.1 Add a new tool the dir agent can call
1. Write the implementation: `_tool_<name>(args, target, cache)` somewhere
in the tool implementations section of `ai.py` (~lines 486642).
Return a string.
2. Add it to `_TOOL_DISPATCH` at `ai.py:645`.
3. Add its schema to `_DIR_TOOLS` at `ai.py:151`. The schema must follow
Anthropic tool-use shape: `name`, `description`, `input_schema`.
4. Decide whether the survey should be able to filter it out (default:
1. Write the implementation: `_tool_<name>(args, target, cache)` in the
tool implementations section of `ai.py`. Return a string.
2. Add a `register_tool()` call in the registrations block at the bottom
of the tool implementations section, with `scopes=["dir"]` and
`handler=_tool_<name>`. The schema follows Anthropic tool-use shape:
`name`, `description`, `input_schema`.
3. Decide whether the survey should be able to filter it out (default:
yes — leave it out of `_PROTECTED_DIR_TOOLS`) or whether it's
control-flow critical (add to `_PROTECTED_DIR_TOOLS`).
5. Update `_DIR_SYSTEM_PROMPT` in `prompts.py` if the agent needs
4. Update `_DIR_SYSTEM_PROMPT` in `prompts.py` if the agent needs
instructions on when to use the new tool.
6. There is no unit test for tool registration today (`ai.py` is exempt).
If you want coverage, the test would mock `client.messages.stream` and
assert that the dispatch table contains your tool.
5. There is no unit test for tool registration today (`ai.py` is exempt).
If you want coverage, the test would assert that `_TOOL_DISPATCH`
contains your handler and `_DIR_TOOLS` contains your schema after
importing `luminos_lib.ai`.
To make a tool available in synthesis or survey instead of (or in
addition to) dir, pass `scopes=["synthesis"]`, `scopes=["survey"]`, or
`scopes=["dir", "synthesis"]`. Tools whose schema differs by scope (like
`submit_report`) get a separate `register_tool()` call per scope.
### 9.2 Add a whole new pass