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.
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.
# 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}
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.
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
§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.
# 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}
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
§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.
# 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}
@@ note records why the rule exists, so no one deletes it as boilerplate later.✓ 1 file - 0 error, 0 warning, 0 info
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.
$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
WHENnames the situation, theTHENnames 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
$TOOLbut 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.