← Tidelines/Best practices

Why your agent won't use the tool you gave it

You defined a lookup tool, wired the endpoint, tested the API by hand. The agent still answers order questions from memory and never calls it. The bug is not in your tool code. It is one missing line in the prompt.

by TypeGlish team6 min read#best-practices
The tool it never calls.

TL;DR An agent ignores a tool when no rule tells it when to call one. Defining a $TOOL makes it available, not discoverable. The checker flags a defined-but-unreferenced tool as structure/unused-tool; the fix is a single conditional rule that points at it: WHEN a customer asks about an order THEN call @[lookup_order].

This is one of the most common "my agent is broken" tickets, and the tool code is almost never the culprit. You give the agent a tool, confirm the endpoint returns data, and the agent cheerfully makes up an answer instead of calling it. The reason is simple once you see it: a model does not use a tool because the tool exists. It uses a tool because an instruction tells it that, in this situation, this is the tool to reach for. Leave that instruction out and the tool is furniture.

§1The symptom

Here is the setup that produces the ticket. A support agent with a clean, correct order-lookup tool defined right there in the prompt, and a couple of sensible-looking instructions. Ask it "where is my order?" and it will improvise a status rather than call the tool.

orders.tg (the tool is defined)⚠ warning
# Role
You are a support agent for an online store.

# Instructions
- Answer customer questions about their orders.
- MUST reply in at most 4 sentences.

$TOOL lookup_order
  - description:: Looks up an order by its id.
  - input::
    - order_id:: string
  - request:: GET https://api.store.example.com/orders/@{order_id}
Everything looks present: a role, an instruction to answer order questions, and a fully defined tool. But read the instructions again. Not one of them mentions the tool. "Answer customer questions about their orders" is a goal, not a trigger, and the model has no line telling it that answering means calling lookup_order.

§2The cause, named

You do not have to guess at this. The checker sees a tool with no rule pointing at it and says so, in the exact terms of the problem.

tg check - output
orders.tg:8:1  warn   structure/unused-tool  Defined tool
  "lookup_order" is never used in the prompt - the model cannot
  discover a tool no rule mentions. Reference it in a rule (e.g.
  "call lookup_order when ...") or delete the definition.

 1 file - 0 error, 1 warning, 0 info
"The model cannot discover a tool no rule mentions" is the whole bug in one line. A tool definition is a capability you have made available. It becomes reachable only when an instruction connects a situation to it. This is a warning, not a hard error, because a dead tool will not crash the agent; it will just quietly never fire, which is precisely the ticket you are debugging.

§3The fix everyone tries first

The instinct is right: reference the tool in a rule. So you add "when a customer asks about an order, call lookup_order" and move on. That is closer, and it still does not compile.

orders.tg (naming it in prose)✗ blocked
# Role
You are a support agent for an online store.

# Instructions
- WHEN a customer asks about an order THEN call lookup_order.
- MUST reply in at most 4 sentences.

$TOOL lookup_order
  - description:: Looks up an order by its id.
  - input::
    - order_id:: string
  - request:: GET https://api.store.example.com/orders/@{order_id}
The rule now mentions the tool, but by typing its name as bare prose. The compiler sees a string that happens to match a tool name and refuses to guess whether you meant the tool or the words.
tg check - output
orders.tg:5:49  error  structure/bare-tool-ref
  "lookup_order" is a tool, but this mention is bare prose - the
  compiler cannot bind it. Point it with @[lookup_order] (a checked
  reference), or quote it ("lookup_order") to speak the name as text.

 1 file - 1 error, 0 warning, 0 info
This is the same defect the booking-agent walkthrough hits when it wires its first tool. A bare-prose mention is a reference nothing can track: rename the tool later and the rule silently rots. The compiler wants you to declare the dependency, the way a linter wants your imports.

§4The real fix: point a rule at it

Make the reference a checked pointer with @[lookup_order]. Now the rule is bound to the definition: the tool is discoverable, the mention is tracked, and a future rename becomes a compile error instead of a production surprise.

orders.tg (fixed)✓ compiles · B (88/100)
# Role
You are a support agent for an online store.

# Instructions
@@ order_lookup: the model only calls a tool a rule points it at
- WHEN a customer asks about an order THEN call @[lookup_order].
@@ brevity: chat replies over four sentences get skimmed
- MUST reply in at most 4 sentences.

$TOOL lookup_order
  - description:: Looks up an order by its id.
  - input::
    - order_id:: string
  - request:: GET https://api.store.example.com/orders/@{order_id}
One conditional rule with a checked pointer, and the tool goes from furniture to reachable. It compiles clean and scores a B. The @@ note records why the rule exists, so no one deletes it as boilerplate later.
tg check - output
 1 file - 0 error, 0 warning, 0 info
The warning is gone because the tool is now referenced, and the reference is checked rather than bare. Both the broken versions and this one are re-run against the real compiler by the CI guard on this post, so the error and the clean pass are facts, not screenshots.

The lesson generalizes past tools. A model acts on what the prompt connects to a situation, not on what merely exists in its context. A capability you define but never trigger, an example you show but never label, a variable you declare but never point at: all of it is inert until a rule reaches for it. Wiring a tool is just the case where the inertness is easiest to catch, because the compiler counts the references for you. If you would rather build the whole agent this way from the start, the booking-agent walkthrough runs the loop end to end, and the bloated-prompt rewrite shows the same discipline applied to plain rules.

Field note

$TOOL definitions, checked @[tool] pointers, and the structure/unused-tool and structure/bare-tool-ref diagnostics all ship in TypeGlish today (the tooling landed in 0.1.0). Run tg check on your own agent prompt: if it names a tool you swear the agent ignores, the odds are good the checker already knows why.

FAQCommon questions

Why won't my AI agent call the tool I gave it?
Almost always because no rule in the system prompt tells it when to. Defining a tool makes it available; it does not make it discoverable. A model reaches for a tool when an instruction connects a situation to that tool, so an agent with a lookup tool but no rule that says "when a customer asks about an order, call it" will answer from memory and never make the call. TypeGlish flags exactly this as structure/unused-tool: a defined tool no rule mentions.
How do I tell an agent when to use a tool?
Write a conditional rule that pairs the trigger with the tool: "WHEN a customer asks about an order THEN call @[lookup_order]." The WHEN names the situation, the THEN names the action, and the @[lookup_order] pointer is a checked reference to the tool definition. That one rule is the difference between a tool that sits idle and a tool the agent actually reaches for.
What does structure/unused-tool mean in TypeGlish?
It means you defined a $TOOL but no rule in the prompt references it, so the model cannot discover it. The checker treats it as a warning rather than a hard error, because an unreferenced tool is dead weight rather than a contradiction: it will not crash the agent, it will just never fire. The fix is to point a rule at it with @[name], or delete the definition if it is genuinely unused.
Why do I get structure/bare-tool-ref when I mention the tool by name?
Because typing the tool's exact name into a rule as plain prose gives the compiler a string it cannot bind to the definition. It is the near-miss fix: you referenced the tool, but not in a way anything can track. Point it with @[name] to make it a checked reference (so renaming the tool later becomes a compile error, not a silent break), or quote the name to say it is deliberately plain text.
∿ washed up Jul 17, 2026 ∿