toolnexus.builtin/builtin-tools
Clojure (JVM) + cljgo · package net.clojars.muthuishere/toolnexus · SPEC §4A · clojure/src/toolnexus/builtin.cljc
builtin-tools ; a VECTOR of ten Tools, in SPEC §4A table order — not a constructorbuiltin-names ; ["bash" "read" "write" "edit" "grep" "glob" ; "webfetch" "question" "apply_patch" "todowrite"]
;; each element is an ordinary Tool map{:name "read" :description "Read a UTF-8 text file." :input-schema {:type "object" :properties {…} :required ["path"]} :source "builtin" :execute (fn ([args]) ([args ctx]))}
;; the schemas are public values toobash-schema read-schema write-schema edit-schema grep-schemaglob-schema webfetch-schema question-schema apply-patch-schema todowrite-schemabuiltin-tools is a def, not a function. There is nothing to construct: a Tool here is the same
plain map every other source produces, so the ten built-ins are simply a vector you can filter,
inspect, or hand to toolnexus.tool/toolkit yourself.
That makes the built-in set inspectable in ways a constructor would not be. The :input-schema of
each tool is the §4A contract — the thing the model actually sees — and you can read it, diff it
against another port, or assert on it in a test without running anything.
Executing one directly is ((:execute t) args) or ((:execute t) args ctx). Both arities exist on
every builtin. Going through toolnexus.tool/execute instead adds the §0.8 boundary rule, which
converts a throw into an error ToolResult rather than letting it escape into your loop — prefer it
unless you are deliberately testing the raw closure.
When to use it
Section titled “When to use it”- Assembling a toolkit by hand — a subset of builtins plus your own tools, without going
through
toolnexus.core/build. - Reading the schemas — checking what §4A actually pins for
editorwebfetch, or wiring the same schema into something else. - Calling one tool without an LLM — a script that wants the
globorapply_patchimplementation and nothing else. - Conformance work — comparing the ten names, descriptions and schemas against another port.
Why this and not the alternative
Section titled “Why this and not the alternative”Nothing here is lazy or stateful. The vector is built once at namespace load; the tools close over no session, hold no handle, and need no shutdown.
Examples
Section titled “Examples”Look at the set, then execute one directly
Section titled “Look at the set, then execute one directly”(require '[toolnexus.builtin :as builtin] '[toolnexus.tool :as tool])
builtin/builtin-names;; => ["bash" "read" "write" "edit" "grep" "glob" "webfetch" "question" "apply_patch" "todowrite"]
(count builtin/builtin-tools) ;=> 10(:source (first builtin/builtin-tools)) ;=> "builtin"
(def read-tool (first (filter #(= "read" (:name %)) builtin/builtin-tools)))
(:input-schema read-tool);; => {:type "object";; :properties {:path {:type "string" :description "Path to the file to read"};; :offset {:type "number" :description "1-based line to start from"};; :limit {:type "number" :description "Number of lines to read"}};; :required ["path"]}
((:execute read-tool) {:path "README.md" :offset 1 :limit 3});; => {:output "# toolnexus — Clojure\n\nOne `.cljc` source tree, two runtimes…" :isError false}
;; A missing file is a value the model can read, not an exception.((:execute read-tool) {:path "nope.md"});; => {:output "read: file not found: nope.md" :isError true}A toolkit of exactly the tools you want
Section titled “A toolkit of exactly the tools you want”(require '[toolnexus.builtin :as builtin] '[toolnexus.native :as native] '[toolnexus.tool :as tool])
(def safe-names #{"read" "grep" "glob"})
(def whoami (native/native-tool {:name "whoami" :description "Who this agent runs as" :run (fn [_] "reporting-bot")}))
;; Builtins first, yours after — later tools win a name collision, which is;; how §0.11's precedence rule is expressed.(def tk (tool/toolkit (conj (vec (filter #(contains? safe-names (:name %)) builtin/builtin-tools)) whoami)))
(tool/tool-names tk) ;=> ["glob" "grep" "read" "whoami"](:output (tool/execute tk "glob" {:pattern "**/*.md" :path "docs"}))The two builtins that do not just return a string
Section titled “The two builtins that do not just return a string”(require '[koine.json :as json] '[toolnexus.builtin :as builtin] '[toolnexus.tool :as tool])
(def tk (tool/toolkit builtin/builtin-tools))
;; `question` suspends: metadata.pending carries a §10 Request, and the client;; loop resumes the call with ctx.answer once a human has replied.(def asked (tool/execute tk "question" {:questions [{:question "Ship it?" :options ["yes" "no"]}]}))(:isError asked) ;=> true(get-in asked [:metadata :pending :kind]) ;=> "question"(get-in asked [:metadata :pending :prompt]) ;=> "Ship it? (options: yes, no)"
;; Re-executed with an answer in the Context, it resolves to that answer.(:output (tool/execute tk "question" {:questions [{:question "Ship it?"}]} {:answer {:ok true :data {:choice "yes"}}}));; => "{\"choice\":\"yes\"}"
;; `apply_patch` plans every file change from reads before writing anything,;; so a hunk that does not match leaves the tree untouched.(:output (tool/execute tk "apply_patch" {:patchText "*** Begin Patch\n*** Add File: notes.md\n+hello\n*** End Patch\n"}));; => "Applied 1 file change(s): add notes.md"| Tool | What it does | Notes |
|---|---|---|
bash |
Runs a shell command, combined stdout+stderr. | timeout in milliseconds, default 60000. A timeout and a non-zero exit are both isError. |
read |
Reads a UTF-8 text file. | Optional 1-based offset and limit. |
write |
Writes a file, creating parent directories. | Returns the UTF-8 byte count written. |
edit |
Exact-string replace. | Refuses a non-unique oldString unless replaceAll is true. |
grep |
Regex search over file contents. | Dialect differs per host — java.util.regex versus Go RE2 — so only the common subset is portable. |
glob |
Lists files matching a glob. | ** crosses separators, * and ? do not. |
webfetch |
HTTP GET, returned as markdown, text or html. |
timeout in seconds, default 30. |
question |
Asks the human. | Suspends via metadata.pending; see above. |
apply_patch |
Add / update / delete files from a patch. | Atomic: all changes are planned from reads first. |
todowrite |
Replaces the session todo list. | Stateless in this version — it echoes the list back. |
See also
Section titled “See also”toolnexus.builtin/enabled-builtins— Pick which built-in file/shell/web tools are in play, by name or by source toggle.toolnexus.native/native-tool— the same Tool shape, for code of your own.