Skip to content

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.

  • 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 the AgentDef it was spawned from.
  • Deciding whether an agent may delegate at all — :team is the ONLY switch. An AgentDef with no :team (or an empty one) never sees the task tool 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 task delegation automatically — a delegated child inherits the same :completion and :guardrails its parent would have applied to a direct run, because the runtime’s build-client compiles :guardrails into 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 spawn time — see Budget.

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.

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.

  • create-runtime — The six host verbs that drive sub-agents spawned from a registry of AgentDefs, plus the read-only handles/inspect views.
  • Handle — The live state machine one AgentDef produces per spawn call.
  • Budget — The shape and enforcement of the :budget field.
  • task-tool — The opt-in tool, gated on :team, that lets the model itself invoke spawn→wake→wait→close against this def’s teammates.