Tools & Agents
Tools
Tools let you define functions that the LLM can invoke during a conversation. The LLM sees the tool's name, description, and parameter schema, and can call it when appropriate.
deftool
Define a tool with a name, description, parameter schema, and handler function.
(deftool lookup-capital
"Look up the capital of a country"
{:country {:type :string :description "Country name"}}
(lambda (country)
(cond
((= country "Norway") "Oslo")
((= country "France") "Paris")
(else "Unknown"))))Using Tools with Chat
Pass tools to llm/chat — the LLM will call them automatically when needed.
(llm/chat
(list (message :user "What is the capital of Norway?"))
{:tools (list lookup-capital) :max-tokens 100})Inspecting Tools
tool/name
(tool/name lookup-capital) ; => "lookup-capital"tool/description
(tool/description lookup-capital) ; => "Look up the capital..."tool/parameters
(tool/parameters lookup-capital) ; => {:country {:type :string ...}}tool?
(tool? lookup-capital) ; => #tAgents
Agents combine a system prompt, tools, and a multi-turn loop. They handle the back-and-forth of tool calls automatically.
defagent
Define an agent with a system prompt, tools, model, and turn limit.
(deftool get-weather
"Get weather for a city"
{:city {:type :string}}
(lambda (city)
(format "~a: 22°C, sunny" city)))
(defagent weather-bot
{:system "You are a weather assistant. Use the get-weather tool."
:tools [get-weather]
:model "claude-haiku-4-5-20251001"
:max-turns 3})agent/run
Run an agent with a user message. The agent loops, calling tools as needed, until it has a final answer or hits the turn limit. The two-argument form returns the final answer as a string:
(agent/run weather-bot "What's the weather in Tokyo?") ; => "It's sunny, 22°C."An optional third argument takes per-run options. Passing an options map changes the return value to a map with the final reply and the full message history:
(define result
(agent/run weather-bot "What's the weather in Tokyo?"
{:reasoning-effort :high ; reasoning effort for this run (see Completion)
:messages prior-history ; seed the loop with prior conversation
:on-tool-call observe-tool})) ; observe each tool call — see below
(:response result) ; => the final answer string
(:messages result) ; => the full conversation (to continue or inspect)Observing tool calls. :on-tool-call fires once when each tool starts and once when it ends. The event is a map — branch on (:event e), the string "start" or "end":
(define (observe-tool e)
(when (= (:event e) "end")
(println (:tool e) "→" (:result e) (format "(~ams)" (:duration-ms e)))))The event map carries :event ("start" / "end"), :tool (the tool name), and :args; on "end" it adds :result (a preview of the return value), :error (a boolean), and :duration-ms.
Error recovery. A tool that throws, isn't found, or is called with arguments that don't match its declared schema does not abort the run — the error is fed back to the model as the tool result so it can correct itself and continue. The loop is bounded by :max-turns and aborts after 5 consecutive tool errors.
Inspecting Agents
agent/name
(agent/name weather-bot) ; => "weather-bot"agent/system
(agent/system weather-bot) ; => "You are a weather assistant..."agent/tools
(agent/tools weather-bot) ; => list of tool valuesagent/model
(agent/model weather-bot) ; => "claude-haiku-4-5-20251001"agent/max-turns
(agent/max-turns weather-bot) ; => 3agent?
(agent? weather-bot) ; => #t