Skip to content

Commit 6337eb8

Browse files
committed
Add optional model and variant parameters to spawn_agent tool
1 parent 39e3440 commit 6337eb8

4 files changed

Lines changed: 442 additions & 40 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
## Unreleased
44

5-
- Drop UPX compression for all native image builds.
6-
- Optimize native image for size with `-Os`.
5+
- Improve native image size for smaller eca binaries
6+
- Drop UPX compression for all native image builds.
7+
- Optimize native image for size with `-Os`.
8+
- Add optional `model` and `variant` parameters to `spawn_agent` tool, allowing users to run subagents on a different model or variant than the current conversation.
79

810
## 0.114.2
911

src/eca/features/tools.clj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@
150150
f.tools.chat/definitions
151151
f.tools.skill/definitions
152152
f.tools.task/definitions
153-
(f.tools.agent/definitions config)
153+
(f.tools.agent/definitions config db)
154154
(f.tools.custom/definitions config))))
155155

156156
(defn native-tools [db config]

src/eca/features/tools/agent.clj

Lines changed: 111 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"Tool for spawning subagents to perform focused tasks in isolated context."
33
(:require
44
[clojure.string :as str]
5+
[eca.config :as config]
56
[eca.features.tools.util :as tools.util]
67
[eca.logger :as logger]
78
[eca.messenger :as messenger]))
@@ -81,6 +82,44 @@
8182
(catch Exception e
8283
(logger/warn logger-tag (format "Error stopping subagent '%s': %s" agent-name (.getMessage e)))))))
8384

85+
(defn ^:private available-model-names
86+
"Returns a sorted list of available model names from the runtime db."
87+
[db]
88+
(some->> (:models db)
89+
keys
90+
sort
91+
vec))
92+
93+
(defn ^:private available-variant-names
94+
"Returns a sorted union of all variant names across all available models."
95+
[config db]
96+
(let [model-keys (keys (:models db))]
97+
(when (seq model-keys)
98+
(let [all-variants (->> model-keys
99+
(mapcat (fn [^String full-model]
100+
(let [idx (.indexOf full-model "/")]
101+
(when (pos? idx)
102+
(let [provider (subs full-model 0 idx)
103+
model (subs full-model (inc idx))
104+
user-variants (get-in config [:providers provider :models model :variants])]
105+
(keys (config/effective-model-variants config provider model user-variants)))))))
106+
(into (sorted-set)))]
107+
(when (seq all-variants)
108+
(vec all-variants))))))
109+
110+
(defn ^:private model-variant-names
111+
"Returns sorted variant names for a specific full model string (e.g. \"anthropic/claude-sonnet-4-6\")."
112+
[config ^String full-model]
113+
(when full-model
114+
(let [idx (.indexOf full-model "/")]
115+
(when (pos? idx)
116+
(let [provider (subs full-model 0 idx)
117+
model (subs full-model (inc idx))
118+
user-variants (get-in config [:providers provider :models model :variants])
119+
variants (config/effective-model-variants config provider model user-variants)]
120+
(when (seq variants)
121+
(vec (sort (keys variants)))))))))
122+
84123
(defn ^:private spawn-agent
85124
"Handler for the spawn_agent tool.
86125
Spawns a subagent to perform a focused task and returns the result."
@@ -110,10 +149,35 @@
110149
;; Create subagent chat session using deterministic id based on tool-call-id
111150
subagent-chat-id (->subagent-chat-id tool-call-id)
112151

152+
user-model (get arguments "model")
153+
_ (when user-model
154+
(let [available-models (:models db)]
155+
(when (and (seq available-models)
156+
(not (contains? available-models user-model)))
157+
(throw (ex-info (format "Model '%s' is not available. Available models: %s"
158+
user-model
159+
(str/join ", " (available-model-names db)))
160+
{:model user-model
161+
:available (available-model-names db)})))))
162+
113163
parent-model (get-in db [:chats chat-id :model])
114-
subagent-model (or (:model subagent) parent-model)]
164+
subagent-model (or user-model (:model subagent) parent-model)
165+
166+
;; Variant validation: reject only when the resolved model has configured
167+
;; variants and the user-specified one isn't among them. Models with no
168+
;; configured variants accept any variant (the LLM API will reject if invalid).
169+
user-variant (get arguments "variant")
170+
_ (when user-variant
171+
(let [valid-variants (model-variant-names config subagent-model)]
172+
(when (and (seq valid-variants)
173+
(not (some #{user-variant} valid-variants)))
174+
(throw (ex-info (format "Variant '%s' is not available for model '%s'. Available variants: %s"
175+
user-variant subagent-model (str/join ", " valid-variants))
176+
{:variant user-variant
177+
:model subagent-model
178+
:available valid-variants})))))]
115179

116-
(logger/info logger-tag (format "Spawning agent '%s' for task: %s" agent-name task))
180+
(logger/info logger-tag (format "Spawning agent '%s' for task: %s (model: %s, variant: %s)" agent-name task subagent-model (or user-variant "default")))
117181

118182
(let [max-steps-limit (max-steps subagent)]
119183
(swap! db* assoc-in [:chats subagent-chat-id]
@@ -132,11 +196,12 @@
132196
task max-steps-limit)
133197
task)]
134198
(chat-prompt
135-
{:message task-prompt
136-
:chat-id subagent-chat-id
137-
:model subagent-model
138-
:agent agent-name
139-
:contexts []}
199+
(cond-> {:message task-prompt
200+
:chat-id subagent-chat-id
201+
:model subagent-model
202+
:agent agent-name
203+
:contexts []}
204+
user-variant (assoc :variant user-variant))
140205
db*
141206
messenger
142207
config
@@ -204,39 +269,54 @@
204269
(str base-description agents-section)))
205270

206271
(defn definitions
207-
[config]
208-
{"spawn_agent"
209-
{:description (build-description config)
210-
:parameters {:type "object"
211-
:properties {"agent" {:type "string"
212-
:description "Name of the agent to spawn"}
213-
"task" {:type "string"
214-
:description "Clear description of what the agent should accomplish"}
215-
"activity" {:type "string"
216-
:description "Concise label (max 3-4 words) shown in the UI while the agent runs, e.g. \"exploring codebase\", \"reviewing changes\", \"analyzing tests\"."}}
217-
:required ["agent" "task" "activity"]}
218-
:handler #'spawn-agent
219-
:summary-fn (fn [{:keys [args]}]
220-
(if-let [agent-name (get args "agent")]
221-
(let [activity (get args "activity" "working")]
222-
(format "%s: %s" agent-name activity))
223-
"Spawning agent"))}})
272+
[config db]
273+
(let [model-names (available-model-names db)
274+
variant-names (available-variant-names config db)]
275+
{"spawn_agent"
276+
{:description (build-description config)
277+
:parameters {:type "object"
278+
:properties {"agent" {:type "string"
279+
:description "Name of the agent to spawn"}
280+
"task" {:type "string"
281+
:description "Clear description of what the agent should accomplish"}
282+
"activity" {:type "string"
283+
:description "Concise label (max 3-4 words) shown in the UI while the agent runs, e.g. \"exploring codebase\", \"reviewing changes\", \"analyzing tests\"."}
284+
"model" (cond-> {:type "string"
285+
:description (str "Model to use for the subagent."
286+
"Pick the closest match from the enum when the user asks for a model by informal name (e.g. \"sonnet\" → latest sonnet available)."
287+
"Optional — defaults to the agent's configured model or the current conversation model.")}
288+
(seq model-names) (assoc :enum model-names))
289+
"variant" (cond-> {:type "string"
290+
:description (str "Variant (Usually reasoning related) for the subagent."
291+
"Available variants are model-dependent; pick the closest match when the user asks informally (e.g. \"high reasoning\"\"high\")."
292+
"Optional — defaults to the agent's or model's configured variant.")}
293+
(seq variant-names) (assoc :enum variant-names))}
294+
:required ["agent" "task" "activity"]}
295+
:handler #'spawn-agent
296+
:summary-fn (fn [{:keys [args]}]
297+
(if-let [agent-name (get args "agent")]
298+
(let [activity (get args "activity" "working")]
299+
(format "%s: %s" agent-name activity))
300+
"Spawning agent"))}}))
224301

225302
(defmethod tools.util/tool-call-details-before-invocation :spawn_agent
226303
[_name arguments _server {:keys [db config chat-id tool-call-id]}]
227304
(let [agent-name (get arguments "agent")
305+
user-model (get arguments "model")
306+
user-variant (get arguments "variant")
228307
subagent (when agent-name
229308
(get-agent agent-name config))
230309
parent-model (get-in db [:chats chat-id :model])
231-
subagent-model (or (:model subagent) parent-model)
310+
subagent-model (or user-model (:model subagent) parent-model)
232311
subagent-chat-id (when tool-call-id
233312
(->subagent-chat-id tool-call-id))]
234-
{:type :subagent
235-
:subagent-chat-id subagent-chat-id
236-
:model subagent-model
237-
:agent-name agent-name
238-
:step (get-in db [:chats subagent-chat-id :current-step] 1)
239-
:max-steps (max-steps subagent)}))
313+
(cond-> {:type :subagent
314+
:subagent-chat-id subagent-chat-id
315+
:model subagent-model
316+
:agent-name agent-name
317+
:step (get-in db [:chats subagent-chat-id :current-step] 1)
318+
:max-steps (max-steps subagent)}
319+
user-variant (assoc :variant user-variant))))
240320

241321
(defmethod tools.util/tool-call-details-after-invocation :spawn_agent
242322
[_name _arguments before-details _result {:keys [db chat-id tool-call-id]}]

0 commit comments

Comments
 (0)