Skip to content

Work alongside a harness's own tools - #18

Merged
JenD86 merged 1 commit into
mainfrom
feat/harness-tools
Nov 2, 2026
Merged

JenD86 merged 1 commit into
mainfrom
feat/harness-tools

Conversation

@JenD86

@JenD86 JenD86 commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

Modulo treated every {"name": ..., "arguments": ...} in the model's output as its own call. That made it hard to use inside an agent framework that has tools of its own:

  • A call to the framework's tool got an "Unknown tool" reply and was removed from prose.
  • The framework's tool definitions never reached the chat template.

Changes

  • step(messages, tools=[...]) passes the framework's tool schemas (or functions, as for apply_chat_template) to the chat template.
    • Calls to those tools come back in the new StepResult.other_tool_calls. Modulo doesn't run them, and they stay in prose with their <tool_call> tags.
    • Modulo's own calls are still run and removed, tags included, and tool_messages() holds only their results.
  • SteeringAgent(pass_through_unknown_tools=True) treats every call that isn't Modulo's as the framework's. It's for frameworks that describe their tools in their own system prompt, and it's needed on OLMo-2, whose template ignores tools=.
  • Unknown tools: without either option, an unknown call still gets "Unknown tool", and the message now lists the framework's tool names too. That keeps the standalone demo's behaviour.
  • "parameters" is read as "arguments", because Llama-3.1's template asks for that format. Objects with a "description" are echoed tool definitions, not calls.
  • AGENTS.md explains how to integrate a framework with its own tools, adds the new fields, and notes the template quirks in the model table.

Testing

  • CPU tests: 130 pass (test_agent_logic, test_cli, test_chat).
  • Real chat templates (Qwen2.5, Llama-3.1, OLMo-2, Mistral v0.3), rendered on CPU:
    • All four render with tools=, keep the system prompt, and show tool results.
    • Qwen, Llama and Mistral list the tools; OLMo doesn't.
  • Not run on a GPU. Generation isn't affected unless tools= is given.

🤖 Generated with Claude Code

Modulo treated every {"name": ..., "arguments": ...} in the output as its own
call: a harness's tool calls got "Unknown tool" and vanished from prose, and
the harness's tool definitions never reached the chat template.

- step(messages, tools=[...]) passes the harness's tool schemas (or
  functions) to the chat template. Calls to them come back in
  StepResult.other_tool_calls, not run, and stay in prose with their
  <tool_call> tags. Modulo's own calls are still run and stripped with their
  tags, and tool_messages() holds only their results.
- SteeringAgent(pass_through_unknown_tools=True) treats every non-Modulo call
  as the harness's, for harnesses that describe tools in their own system
  prompt (needed on OLMo-2, whose template ignores tools=).
- Without either, unknown calls still get "Unknown tool", now listing the
  harness's tool names too.
- "parameters" is read as "arguments", since Llama-3.1's template asks for
  that format; objects with a "description" (echoed tool definitions) aren't
  calls.

Checked against the real Qwen2.5, Llama-3.1, OLMo-2 and Mistral v0.3 chat
templates: all render with tools=, and all but OLMo list the tools.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@JenD86
JenD86 merged commit e481fbf into main Nov 2, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant