Skip to content

ToolContext — what execute receives

Clojure (JVM) + cljgo · package net.clojars.muthuishere/toolnexus · SPEC §1 · clojure/src/toolnexus/tool.cljc

Optional per-call context handed to execute: cancellation, identity, and host-supplied state.

In Clojure an optional trailing argument is arity, not a nullable parameter. toolnexus.tool/execute calls (f args) when no context was supplied and (f args ctx) when one was, so a tool that wants the context is written multi-arity:

(require '[toolnexus.tool :as tool])
(def t
(tool/tool {:name "answerer"
:execute (fn ([args] (tool/success "no context"))
([args ctx] (tool/success (str "answer: " (:answer ctx)))))}))
(def tk (tool/toolkit [t]))
(tool/execute tk "answerer" {}) ;; => {:output "no context" :isError false}
(tool/execute tk "answerer" {} {:answer "yes"}) ;; => {:output "answer: yes" :isError false}

The context is whatever map the caller passes. The port itself puts exactly one thing there: when a tool suspends and a :wait-for resolves it, the client re-executes the same call with {:answer answer} as the context. That is the resume path of SPEC §10 — the first execution has no context, the retry does, which is why the two-arity form is the portable contract here.

toolnexus.native/native-tool takes a single-argument :run function by default and declares the context with a flag:

(require '[toolnexus.native :as native])
;; no context — the common case
(native/native-tool {:name "upper"
:description "Uppercase the text"
:run (fn [args] (clojure.string/upper-case (str (:text args))))})
;; opts in: :run now receives (args ctx)
(native/native-tool {:name "confirm"
:description "Act on a resolved answer"
:ctx? true
:run (fn [args ctx]
(if (:answer ctx)
(str "confirmed " (:id args))
"no answer yet"))})

The flag exists because the two hosts share no portable way to ask a function how many arguments it takes: arity introspection is java.lang.reflect on the JVM and absent on cljgo. JavaScript, Python and Go can hand run both values and let the callee ignore the extra one; Clojure cannot. Passing two arguments to a one-argument fn is an arity error, not a silently ignored extra — so the flag is the honest version of what the other ports get for free. It costs one keyword and never guesses wrong.

There is no cancellation handle and no identity carried in the context by the port. If you need either, put it in the map yourself at the call site: the context is opaque to execute, which only decides whether to pass it.