AgentDef
Clojure (JVM) + cljgo · package net.clojars.muthuishere/toolnexus · SPEC §7D · clojure/src/toolnexus/agents/runtime.cljc:494-513
;; an AgentDef is a PLAIN MAP — no defrecord, no protocol{:name "writer" ; registry key, and the id prefix a spawned handle gets :does "writes prose" ; one-line capability blurb, used to compose the `task` ; tool's description for whoever has this agent on :team :soul "You are a…" ; system prompt (mutually exclusive with :soul-file below) :soul-file nil ; §7E: read once at spawn, wins over :soul when both are set :model "gpt-4.1" ; or "inherit" to take the runtime's :llm model :tools [...] ; this agent's OWN tools, always in its toolkit :team ["writer" "auditor"] ; registry names this agent may `task`-delegate to — ; ABSENT or empty ⇒ no `task` tool is ever offered (opt-in) :budget {:max-turns 6 ...} ; see Budget — carved from the parent's pool at spawn :wait-for (fn [request] answer) ; this agent's §10 interpreter authority, if any :on-spawn (fn [runtime handle-id] ...) ; fires once, before the first turn :on-close (fn [runtime handle-id reason] ...) ; fires on close, reason = "closed"|"interrupted" :hooks {...} ; §8 lifecycle hooks — resolve def-over-runtime, replace not merge :on-metric (fn [event] ...) ; §8 observability sink — resolved independently of :hooks :guardrails [(fn [event] nil-or-deny-reason) ...] ; policy checks compiled into one :before-tool :completion {:verify fn :max-attempts n}} ; the gate that decides "is it actually done"Define a sub-agent with its own toolkit, prompt and budget, callable as a tool by its parent. An
AgentDef is the thing you register with a runtime — see create-runtime
— under a name, and it is also what spawn resolves that name
against every time it creates a new Handle.
When to use it
Section titled “When to use it”- Registering a sub-agent’s identity, once — soul, model, its own tools, and (if it may
delegate) its
:team. Everything a spawned handle does traces back to theAgentDefit was spawned from. - Deciding whether an agent may delegate at all —
:teamis the ONLY switch. AnAgentDefwith no:team(or an empty one) never sees thetasktool in its toolkit, no matter how many agents exist in the registry: “delegation, like recursion, is opt-in” (clojure/src/toolnexus/agents/runtime.cljc:842-850). - Carrying a completion gate or a guardrail policy WITH the def, so it travels through
taskdelegation automatically — a delegated child inherits the same:completionand:guardrailsits parent would have applied to a direct run, because the runtime’sbuild-clientcompiles:guardrailsinto the client hooks at the point it builds the child’s own client, not at the caller’s site. - Setting per-agent budget ceilings that get carved out of the parent’s shared pool at
spawntime — seeBudget.
Why this and not the alternative
Section titled “Why this and not the alternative”An AgentDef on its own does nothing — it only becomes a live agent once handed to
create-runtime’s :registry and named by a
spawn call, or (for a top-level, non-delegated agent) wrapped
directly by agent-tool or driven with run-agent. Reach for
toolnexus.agents.loop’s harness when the agent has no :team,
no :wait-for, and needs no child delegation at all — the plain §8 loop degenerates to exactly
one client/run and is the smaller concept for that case.
Examples
Section titled “Examples”1. The smallest useful call — a registry entry that just answers
Section titled “1. The smallest useful call — a registry entry that just answers”(require '[toolnexus.agents.runtime :as rt])
(println)
(def agent-def {:name "writer" :does "writes prose" :model "gpt-4.1"})
(def runtime (rt/create-runtime {:registry {"writer" agent-def} :llm {:base-url "https://api.example.com" :model "gpt-4.1"}}))
(assert (= agent-def (get (:registry runtime) "writer")))(println "OK")No :team, no :tools, no :budget — this agent can be spawned and woken, but it can never
delegate: its toolkit view is empty of task.
2. The realistic case — a def with a team, a budget, and lifecycle hooks
Section titled “2. The realistic case — a def with a team, a budget, and lifecycle hooks”(require '[toolnexus.agents.runtime :as rt])
(println)
(def boss-def {:name "boss" :does "coordinates the writing team" :model "gpt-4.1" :team ["writer" "auditor"] ; the ONLY names it may `task`-delegate to :budget {:max-tokens 20000 :max-depth 5 :max-children 4} :on-spawn (fn [_rt id] (println "spawned:" id)) :on-close (fn [_rt id reason] (println id "closed:" reason))})
(def writer-def {:name "writer" :does "writes prose" :model "gpt-4.1"})(def auditor-def {:name "auditor" :does "checks numbers" :model "gpt-4.1"})
(def runtime (rt/create-runtime {:registry {"boss" boss-def "writer" writer-def "auditor" auditor-def} :llm {:base-url "https://api.example.com" :model "gpt-4.1"}}))
(assert (= #{"boss" "writer" "auditor"} (set (keys (:registry runtime)))))(println "OK")The task tool’s model-facing description is composed ONLY from boss’s own :team, sorted by
name and rendered from each agent’s :does — e.g. "Available agents — auditor: checks numbers; writer: writes prose" — never from the whole registry, so an agent outside the team (say, a
"secret" entry also registered here) is never advertised.
3. The full surface — a guarded, gated def whose fields travel through delegation
Section titled “3. The full surface — a guarded, gated def whose fields travel through delegation”(require '[toolnexus.agents.loop :as tnloop])
(println)
(def worker-def {:name "worker" :does "does the risky part" :model "gpt-4.1" :guardrails [(fn [ev] (when (= "delete_file" (:tool ev)) "deletion requires approval"))] :completion {:verify tnloop/all-todos-done :max-attempts 2} :budget {:max-turns 6 :max-tool-calls 20 :max-wall-ms 60000} :wait-for (fn [request] nil)}) ; this level does not interpret §10 requests; they escalate
(assert (fn? (:verify (:completion worker-def))))(assert (= 2 (:max-attempts (:completion worker-def))))(assert (fn? (first (:guardrails worker-def))))(assert (fn? (:wait-for worker-def)))(println "OK"):guardrails and :completion here are honoured whether worker is spawned directly, or
delegated to via a parent’s task call — they are compiled into the child’s own client at the
point the runtime builds it, so a delegated worker is governed exactly as if it had been driven
by hand.
See also
Section titled “See also”create-runtime— The six host verbs that drive sub-agents spawned from a registry ofAgentDefs, plus the read-onlyhandles/inspectviews.Handle— The live state machine oneAgentDefproduces perspawncall.Budget— The shape and enforcement of the:budgetfield.task-tool— The opt-in tool, gated on:team, that lets the model itself invokespawn→wake→wait→closeagainst this def’s teammates.