Dev.to AI 🤖 Ai 👁 0 📖 4 min read

How to Steer the model via tool call to improve Harness Quality

Do this first: pick one rule from your system prompt that a script could check. Delete the line. Enforce it at the tool call instead. That is the whole idea. The rest of this post shows how OpenHands SDK, OpenCode, Herm

Do this first: pick one rule from your system prompt that a script could check. Delete the line. Enforce it at the tool call instead.

That is the whole idea. The rest of this post shows how OpenHands SDK, OpenCode, Hermes Agent, DeepSeek harness and Strands do it.

Why the tool call beats the prompt

  • A prompt rule is read once and fades as the conversation grows.
  • A rejected tool call arrives at the exact moment of the mistake, about the exact action, at the end of the context.
  • The model doesn't have to remember the rule. It only has to read the reply.

The steering loop: four steps

Every harness above runs the same loop on each tool call:

  1. Detect. Check the call against a rule a script can decide: was the file read, is this a repeat, is the budget spent, is the path allowed.
  2. Reject. Don't run it. Return a tool result marked as an error.
  3. Name the next move. Say why it failed and what the legal next action is.
  4. Let the model retry. It reads the reply on its next turn and corrects itself. No prompt line needed.

Step 3 is the one people skip. A bare "denied" makes the model guess, and it guesses by sending the same call with a small change.

Four checks to build first

  1. Read before edit. Keep a record of what was read. Refuse an edit to an unread or changed file. DeepSeek: edit requires reading "X" first (fs-observation-policy).
  2. Repeated calls. Count calls with the same tool and arguments. DeepSeek reminds at 3, 5 and 8 repeats: "Do not call this tool with these exact arguments again" (repeat-tool-reminder). Hermes blocks at 5: "Stop retrying it unchanged; change strategy or explain the blocker" (tool_guardrails.py).
  3. The step budget. When it's spent, OpenCode turns tools off (toolChoice: "none"), appends a wrap-up message, and refuses any call that still arrives (llm.ts). The run ends with an answer.
  4. Dangerous paths and commands. Parse the command before running it. OpenCode checks each command and each target folder as its own permission (shell.ts). Hermes keeps a blocklist no mode can override: "not even with --yolo" (approval_floors.py).

Example: PDFs

Your agent keeps trying to read PDFs as text. Two ways to fix it in the harness, no prompt line:

  1. Reject the wrong route. The model runs cat paper.pdf or a shell one-liner to dump it. The shell tool spots the .pdf target and refuses: "PDFs can't be read as text. Use the parse_pdf tool." The model switches tools.
  2. Do the work instead of refusing. The file-read tool gets a PDF. Instead of an error, the harness parses it, saves the text as paper.md, and replies: "Parsed paper.pdf. Read paper.md." The model never sees the failure.

Option 2 is better whenever the harness can do the job itself. It moves the work from the model to code.

DeepSeek uses the same trick for tools that must be called from code: "only run_code is callable directly — call X from inside a run_code program instead". Its comment explains why: with a bare "unknown tool", the model concluded the deployment was broken (core/tools).

Messages that steer

Each one answers why and what next:

Hooks: steering without touching tool code

Hooks run your check before or after any tool call:

  1. PreToolUse can deny with a reason. DeepSeek turns a hook's deny into a tool denial the model reads (hooks-claude-code).
  2. Exit code 2 blocks. OpenHands follows Claude Code's convention: a hook script that exits 2 stops the call (hooks/types.py, hooks/manager.py).
  3. Hooks can add context instead of blocking. Hermes hooks return JSON to block, rewrite the input, or attach a note (shell_hooks.py).
  4. A Stop hook can refuse "done". DeepSeek and OpenHands feed the hook's reason back and keep the run going.

"Isn't this custom code?"

No. It is the harness doing its job. A read ledger, a call counter, a path check, a hook runner: these are why OpenCode, Hermes and the OpenHands SDK each run past 20k lines outside their prompts. The model supplies judgement. The harness supplies the guard rails, and the guard rails are most of the code.

Where to draw the line

  1. Something the environment can make impossible: change the environment.
  2. A rule a script can check: enforce it at the tool call, reply with the next move.
  3. Judgement (what to search, how to write, when to ask): leave it to the prompt.

Next: open your system prompt and count the lines that start with "never" or "always". Each one is a candidate for step 2.

📰 Read the original article on Dev.to AI

Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.