zilmate 1.7.2 → 1.7.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/.env.example +36 -36
  2. package/README.md +503 -503
  3. package/SDK.md +320 -320
  4. package/agent-docs.md +276 -276
  5. package/dist/agents/automation-planner.agent.d.ts.map +1 -1
  6. package/dist/agents/coding.agent.d.ts +18 -0
  7. package/dist/agents/coding.agent.d.ts.map +1 -1
  8. package/dist/agents/finance.agent.d.ts +60 -0
  9. package/dist/agents/finance.agent.d.ts.map +1 -0
  10. package/dist/agents/finance.agent.js +30 -0
  11. package/dist/agents/finance.agent.js.map +1 -0
  12. package/dist/agents/manager.d.ts +94 -1
  13. package/dist/agents/manager.d.ts.map +1 -1
  14. package/dist/agents/manager.js +102 -36
  15. package/dist/agents/manager.js.map +1 -1
  16. package/dist/agents/swarm/engineering/qa.d.ts +3 -0
  17. package/dist/agents/swarm/engineering/qa.d.ts.map +1 -0
  18. package/dist/agents/swarm/engineering/qa.js +19 -0
  19. package/dist/agents/swarm/engineering/qa.js.map +1 -0
  20. package/dist/agents/swarm/growth/seo.d.ts +3 -0
  21. package/dist/agents/swarm/growth/seo.d.ts.map +1 -0
  22. package/dist/agents/swarm/growth/seo.js +19 -0
  23. package/dist/agents/swarm/growth/seo.js.map +1 -0
  24. package/dist/agents/swarm/main.d.ts +22 -0
  25. package/dist/agents/swarm/main.d.ts.map +1 -0
  26. package/dist/agents/swarm/main.js +65 -0
  27. package/dist/agents/swarm/main.js.map +1 -0
  28. package/dist/agents/swarm/operations/finance.d.ts +3 -0
  29. package/dist/agents/swarm/operations/finance.d.ts.map +1 -0
  30. package/dist/agents/swarm/operations/finance.js +19 -0
  31. package/dist/agents/swarm/operations/finance.js.map +1 -0
  32. package/dist/agents/swarm/registry.d.ts +4 -0
  33. package/dist/agents/swarm/registry.d.ts.map +1 -0
  34. package/dist/agents/swarm/registry.js +361 -0
  35. package/dist/agents/swarm/registry.js.map +1 -0
  36. package/dist/cli/composer.d.ts.map +1 -1
  37. package/dist/cli/composer.js +54 -4
  38. package/dist/cli/composer.js.map +1 -1
  39. package/dist/cli/doctor.d.ts.map +1 -1
  40. package/dist/cli/doctor.js +13 -0
  41. package/dist/cli/doctor.js.map +1 -1
  42. package/dist/cli/format.d.ts.map +1 -1
  43. package/dist/cli/format.js +46 -24
  44. package/dist/cli/format.js.map +1 -1
  45. package/dist/cli/interactive.d.ts.map +1 -1
  46. package/dist/cli/interactive.js +47 -3
  47. package/dist/cli/interactive.js.map +1 -1
  48. package/dist/cli/render.d.ts.map +1 -1
  49. package/dist/cli/render.js +18 -9
  50. package/dist/cli/render.js.map +1 -1
  51. package/dist/cli/setup.d.ts +1 -0
  52. package/dist/cli/setup.d.ts.map +1 -1
  53. package/dist/cli/setup.js +74 -2
  54. package/dist/cli/setup.js.map +1 -1
  55. package/dist/cli/swarm.d.ts +4 -0
  56. package/dist/cli/swarm.d.ts.map +1 -0
  57. package/dist/cli/swarm.js +21 -0
  58. package/dist/cli/swarm.js.map +1 -0
  59. package/dist/cli/theme.d.ts +9 -0
  60. package/dist/cli/theme.d.ts.map +1 -1
  61. package/dist/cli/theme.js +36 -0
  62. package/dist/cli/theme.js.map +1 -1
  63. package/dist/cli/tunnel.d.ts.map +1 -1
  64. package/dist/cli/tunnel.js +2 -1
  65. package/dist/cli/tunnel.js.map +1 -1
  66. package/dist/cli/voice.js +11 -11
  67. package/dist/cli/welcome.d.ts.map +1 -1
  68. package/dist/cli/welcome.js +2 -0
  69. package/dist/cli/welcome.js.map +1 -1
  70. package/dist/index.js +19 -1
  71. package/dist/index.js.map +1 -1
  72. package/dist/jobs/anomaly.js +5 -5
  73. package/dist/memory/heal.js +43 -43
  74. package/dist/observability/doctor.d.ts +3 -0
  75. package/dist/observability/doctor.d.ts.map +1 -0
  76. package/dist/observability/doctor.js +33 -0
  77. package/dist/observability/doctor.js.map +1 -0
  78. package/dist/observability/usage.d.ts +13 -0
  79. package/dist/observability/usage.d.ts.map +1 -0
  80. package/dist/observability/usage.js +34 -0
  81. package/dist/observability/usage.js.map +1 -0
  82. package/dist/runtime/confirm.d.ts.map +1 -1
  83. package/dist/runtime/confirm.js +12 -2
  84. package/dist/runtime/confirm.js.map +1 -1
  85. package/dist/runtime/prompts/builder.d.ts +11 -0
  86. package/dist/runtime/prompts/builder.d.ts.map +1 -0
  87. package/dist/runtime/prompts/builder.js +29 -0
  88. package/dist/runtime/prompts/builder.js.map +1 -0
  89. package/dist/runtime/registry.d.ts +14 -0
  90. package/dist/runtime/registry.d.ts.map +1 -0
  91. package/dist/runtime/registry.js +39 -0
  92. package/dist/runtime/registry.js.map +1 -0
  93. package/dist/runtime/storage/interface.d.ts +16 -0
  94. package/dist/runtime/storage/interface.d.ts.map +1 -0
  95. package/dist/runtime/storage/interface.js +19 -0
  96. package/dist/runtime/storage/interface.js.map +1 -0
  97. package/dist/runtime/storage/local.d.ts +12 -0
  98. package/dist/runtime/storage/local.d.ts.map +1 -0
  99. package/dist/runtime/storage/local.js +48 -0
  100. package/dist/runtime/storage/local.js.map +1 -0
  101. package/dist/runtime/storage/redis.d.ts +11 -0
  102. package/dist/runtime/storage/redis.d.ts.map +1 -0
  103. package/dist/runtime/storage/redis.js +21 -0
  104. package/dist/runtime/storage/redis.js.map +1 -0
  105. package/dist/runtime/swarm/reports.d.ts +9 -0
  106. package/dist/runtime/swarm/reports.d.ts.map +1 -0
  107. package/dist/runtime/swarm/reports.js +43 -0
  108. package/dist/runtime/swarm/reports.js.map +1 -0
  109. package/dist/runtime/swarm.d.ts +37 -0
  110. package/dist/runtime/swarm.d.ts.map +1 -0
  111. package/dist/runtime/swarm.js +90 -0
  112. package/dist/runtime/swarm.js.map +1 -0
  113. package/dist/runtime/telemetry.d.ts +15 -0
  114. package/dist/runtime/telemetry.d.ts.map +1 -0
  115. package/dist/runtime/telemetry.js +25 -0
  116. package/dist/runtime/telemetry.js.map +1 -0
  117. package/dist/runtime/tool-utils.d.ts +12 -0
  118. package/dist/runtime/tool-utils.d.ts.map +1 -0
  119. package/dist/runtime/tool-utils.js +31 -0
  120. package/dist/runtime/tool-utils.js.map +1 -0
  121. package/dist/tools/browser.tool.d.ts +54 -0
  122. package/dist/tools/browser.tool.d.ts.map +1 -0
  123. package/dist/tools/browser.tool.js +193 -0
  124. package/dist/tools/browser.tool.js.map +1 -0
  125. package/dist/tools/business-apps.tool.d.ts +18 -0
  126. package/dist/tools/business-apps.tool.d.ts.map +1 -0
  127. package/dist/tools/business-apps.tool.js +43 -0
  128. package/dist/tools/business-apps.tool.js.map +1 -0
  129. package/dist/tools/computer-use.tool.js +66 -66
  130. package/dist/tools/cross-app-ledger.tool.d.ts +7 -0
  131. package/dist/tools/cross-app-ledger.tool.d.ts.map +1 -0
  132. package/dist/tools/cross-app-ledger.tool.js +70 -0
  133. package/dist/tools/cross-app-ledger.tool.js.map +1 -0
  134. package/dist/tools/desktop.tool.js +10 -10
  135. package/dist/tools/documents.tool.js +5 -5
  136. package/dist/tools/finance.tool.d.ts +18 -0
  137. package/dist/tools/finance.tool.d.ts.map +1 -0
  138. package/dist/tools/finance.tool.js +88 -0
  139. package/dist/tools/finance.tool.js.map +1 -0
  140. package/dist/tools/image-intelligence.tool.d.ts +12 -0
  141. package/dist/tools/image-intelligence.tool.d.ts.map +1 -0
  142. package/dist/tools/image-intelligence.tool.js +62 -0
  143. package/dist/tools/image-intelligence.tool.js.map +1 -0
  144. package/dist/tools/notify.tool.js +8 -8
  145. package/dist/tools/osint-install.tool.js +8 -8
  146. package/dist/tools/pentest-install.tool.js +6 -6
  147. package/dist/tools/setup-assistant.tool.d.ts +1 -1
  148. package/dist/tools/shell.tool.d.ts +18 -0
  149. package/dist/tools/shell.tool.d.ts.map +1 -1
  150. package/dist/tools/shell.tool.js +34 -0
  151. package/dist/tools/shell.tool.js.map +1 -1
  152. package/dist/tools/triggers.tool.d.ts.map +1 -1
  153. package/dist/tools/web-intelligence.tool.d.ts +12 -0
  154. package/dist/tools/web-intelligence.tool.d.ts.map +1 -0
  155. package/dist/tools/web-intelligence.tool.js +27 -0
  156. package/dist/tools/web-intelligence.tool.js.map +1 -0
  157. package/dist/workspace/init.js +18 -18
  158. package/dist/workspace/output-paths.d.ts +1 -1
  159. package/dist/workspace/output-paths.d.ts.map +1 -1
  160. package/dist/workspace/output-paths.js +1 -0
  161. package/dist/workspace/output-paths.js.map +1 -1
  162. package/dist/workspace/paths.d.ts +1 -0
  163. package/dist/workspace/paths.d.ts.map +1 -1
  164. package/dist/workspace/paths.js +1 -0
  165. package/dist/workspace/paths.js.map +1 -1
  166. package/install.ps1 +62 -62
  167. package/package.json +106 -103
  168. package/scripts/postinstall.mjs +42 -42
  169. package/scripts/release-github.mjs +91 -91
  170. package/src/doc/README.md +49 -49
  171. package/src/doc/admin-tools-and-sms-campaigns.md +99 -99
  172. package/src/doc/escalation-checklist.md +97 -97
  173. package/src/doc/payments-and-payouts.md +82 -82
  174. package/src/doc/shift-lifecycle-and-disputes.md +95 -95
  175. package/src/doc/support-macros.md +40 -40
  176. package/src/doc/venue-support-playbook.md +145 -145
  177. package/src/doc/verification-and-trust.md +88 -88
  178. package/src/doc/worker-support-playbook.md +159 -159
package/agent-docs.md CHANGED
@@ -1,276 +1,276 @@
1
- ZilMate coding-agent note
2
-
3
- ZilMate uses the AI SDK v6 subagent pattern for coding work:
4
-
5
- - Manager delegates software work to the main Coding Agent.
6
- - Coding Agent stays responsible for orchestration and the final report.
7
- - Coding Agent can call `appBuilder` for full app/game/software implementation.
8
- - Coding Agent can call `qaIntegration` for builds, tests, debugging, and release-readiness checks.
9
- - Both internal coding delegates use skills, file tools, shell tools, and notebook tools.
10
- - Use scratchpad for temporary run coordination and notebook for durable project memory.
11
- - Delegate tools pass `abortSignal` into subagents and emit subagent progress events so the CLI can show what is happening.
12
-
13
- Building Subagents in the Vercel AI SDK v6
14
- 11 min read
15
- ·
16
- Josh
17
- Josh
18
- Copy article
19
-
20
- https://upstash.com/blog/subagents-in-ai-sdk-v6
21
- A subagent in the AI SDK v6 is one agent wrapped inside a tool() so another agent can call it. The parent agent treats the subagent like any other tool: it sends a prompt, gets back text, and decides what to do next.
22
-
23
- I find them to be the single most useful pattern to avoid context bloat. No matter how large their task or own context load is, they only return the most important information from their process back to the main agent.
24
-
25
- Subagents take care of context-intensive tasks (e.g. research)
26
- Subagents take care of context-intensive tasks (e.g. research)
27
-
28
- The new v6 ToolLoopAgent
29
- Before v6, building a multi-agent setup meant chaining generateText calls and passing messages between them. The functions to generate or stream text were independant primitives:
30
-
31
- In v5, generateText and streamText are primitives
32
- In v5, generateText and streamText are primitives
33
-
34
- In v6, an agent is its own class we can now call functions on. We define it once with a model, instructions, and tools, then call generate or stream on it:
35
-
36
- New: tools, prompts etc. move to a single class
37
- New: tools, prompts etc. move to a single class
38
-
39
- The class is ToolLoopAgent. The name describes what it does: it runs the model, executes any tool calls, feeds the results back, and loops until a stop condition fires.
40
-
41
-
42
- import { anthropic } from "@ai-sdk/anthropic";
43
- import { stepCountIs, ToolLoopAgent } from "ai";
44
-
45
- const agent = new ToolLoopAgent({
46
- model: anthropic("claude-sonnet-4-6"),
47
- instructions: "You are a research agent. Answer the task autonomously.",
48
- tools: {
49
- /* ... */
50
- },
51
- stopWhen: stepCountIs(10),
52
- });
53
-
54
- const result = await agent.generate({ prompt: "Summarize the latest on X." });
55
- console.log(result.text);
56
- A subagent is just a tool
57
- A subagent is a ToolLoopAgent that a parent agent calls through a tool(). The tool's execute function runs the subagent and returns its text.
58
-
59
-
60
- import { anthropic } from "@ai-sdk/anthropic";
61
- import { stepCountIs, tool, ToolLoopAgent } from "ai";
62
- import { z } from "zod";
63
-
64
- const researchSubagent = new ToolLoopAgent({
65
- model: anthropic("claude-sonnet-4-6"),
66
- instructions: "You are a focused research subagent. Return only a summary.",
67
- stopWhen: stepCountIs(10),
68
- });
69
-
70
- const researchTool = tool({
71
- description: "Delegate a research task to a subagent.",
72
- inputSchema: z.object({ prompt: z.string() }),
73
- execute: async ({ prompt }, { abortSignal }) => {
74
- const result = await researchSubagent.generate({ prompt, abortSignal });
75
- return result.text;
76
- },
77
- });
78
-
79
- const parentAgent = new ToolLoopAgent({
80
- model: anthropic("claude-sonnet-4-6"),
81
- instructions: "Delegate research, then synthesize an answer.",
82
- tools: { research: researchTool },
83
- stopWhen: stepCountIs(10),
84
- });
85
- Two details are important here.
86
-
87
- First, the tool field is inputSchema, not parameters. Earlier AI SDK versions used parameters; v5 renamed it to inputSchema to align with the Model Context Protocol, and v6 keeps that name.
88
-
89
- Second, the execute function takes abortSignal from its second argument and passes it into the subagent. If the parent request is cancelled, that cancellation reaches the subagent too. Without it, a cancelled request leaves subagents running in the background, still using tokens.
90
-
91
- Controlling the subagent output
92
- By default, the parent receives whatever the subagent tool returns. A research subagent might run ten steps and produce a lot of text, and we may not want all of that landing back in the parent's context window.
93
-
94
- With toModelOutput, we can decouple what the tool returns from what gets passed into the parent model. It's like a separate parsing step.
95
-
96
-
97
- const researchTool = tool({
98
- description: "Delegate a research task to a subagent.",
99
- inputSchema: z.object({ prompt: z.string() }),
100
- execute: async ({ prompt }, { abortSignal }) => {
101
- const result = await researchSubagent.generate({ prompt, abortSignal });
102
- return result.text;
103
- },
104
- toModelOutput: ({ output }) => ({ type: "text", value: output }),
105
- });
106
- This way the parent's context stays small while the subagent can consume an almost arbitrary amount of tokens, just bounded by it's context limit. Because either way, it will not bloat our parent.
107
-
108
- This patterns is also super useful for keeping the parent's token count low as the number of subagents grows.
109
-
110
- Creating a stop condition
111
- A ToolLoopAgent keeps looping until a StopCondition tells it to stop. The default is stepCountIs(20), so an agent with no stopWhenwill run up to 20 steps:
112
-
113
-
114
- import { anthropic } from "@ai-sdk/anthropic";
115
- import { hasToolCall, stepCountIs, type StopCondition } from "ai";
116
-
117
- // custom stop condition
118
- const stopAfterAnyToolUse: StopCondition<any, any> = ({ steps }) =>
119
- steps.some((step) => step.toolCalls.length > 0);
120
-
121
- const agent = new ToolLoopAgent({
122
- model: anthropic("claude-sonnet-4-6"),
123
- stopWhen: [stepCountIs(10), hasToolCall("done"), stopAfterAnyToolUse],
124
- });
125
- We can pass an array of conditions, and the loop stops when any one of them is true. stepCountIs(n) caps the step count, hasToolCall(name) stops once the agent uses any tool, and a custom function gets the full steps array so we can stop on anything we can compute from it, like a token budget.
126
-
127
- By the way, prepareStep runs before every step and lets us change the model, the tools, or the messages for that step:
128
-
129
-
130
- const agent = new ToolLoopAgent({
131
- model: anthropic("claude-sonnet-4-6"),
132
- tools: { research: researchTool, done: doneTool },
133
- prepareStep: ({ stepNumber }) => ({
134
- toolChoice: stepNumber > 8 ? { type: "tool", toolName: "done" } : "auto",
135
- }),
136
- });
137
- This one forces the agent toward a done tool as it nears its step limit, instead of letting it stall.
138
-
139
- The isolation problem
140
- A subagent invocation starts with a fresh context window every time. The subagents docs call context isolation a feature, and for a single delegated task it is. The subagent doesn't load the parent's full history, and the parent shouldn't know about the subagent's intermediate steps.
141
-
142
- The isolation goes both ways. But in two cases it kinda gets in the way:
143
-
144
- Parallel subagents. The main agent runs three research subagents at once and none of them can see what the others found. If two should avoid duplicating work, there's no way for them to coordinate.
145
- Separate requests. In serverless, each HTTP request can be a cold start. Anything a subagent held in memory on the last request is gone. The orchestrator on the second request doesn't know what the subagents did on the first request.
146
- Parallel subagents cannot talk to each other.
147
- Parallel subagents cannot talk to each other.
148
-
149
- Moving the shared state out of process fixes both problems. The official memory docs point at hosted memory services for this, but for short-lived agent state we use Redis. It works with HTTP and the key expiry handles cleanup automatically.
150
-
151
- Sharing state across subagents with Redis
152
- A pattern I really like is a "shared scratchpad". It's one Redis string keyed by the current message id. Each subagent gets two tools: one to read what the others have already written, and one to append its own findings. We pass the same mocked message id to every subagent so they all point at the same key.
153
-
154
-
155
- import { redis } from "@/lib/redis";
156
- import { anthropic } from "@ai-sdk/anthropic";
157
- import { stepCountIs, tool, ToolLoopAgent } from "ai";
158
- import { z } from "zod";
159
-
160
- function createNoteTools(messageId: string) {
161
- return {
162
- readNotes: tool({
163
- description: "Read what the other subagents have found so far.",
164
- inputSchema: z.object({}),
165
- execute: async () => {
166
- return (await redis.get<string>(`notes:${messageId}`)) ?? "(empty)";
167
- },
168
- }),
169
- appendToNotes: tool({
170
- description: "Append your findings to the shared notes.",
171
- inputSchema: z.object({ findings: z.string() }),
172
- execute: async ({ findings }) => {
173
- await redis.append(`notes:${messageId}`, `\n${findings}`);
174
- return "Appended.";
175
- },
176
- }),
177
- };
178
- }
179
-
180
- // this comes from the ai sdk
181
- const EXAMPLE_MESSAGE_ID = "example-run-001";
182
-
183
- const researchSubagent = new ToolLoopAgent({
184
- model: anthropic("claude-sonnet-4-6"),
185
- instructions: `You are a research subagent. Read your notes to see what others found, then append your research.`,
186
- tools: createNoteTools(EXAMPLE_MESSAGE_ID),
187
- stopWhen: stepCountIs(10),
188
- });
189
-
190
- const parent = new ToolLoopAgent({
191
- model: anthropic("claude-sonnet-4-6"),
192
- instructions: `Start three research subagents in parallel on these topics: 1. Serverless databases 2. Edge computing 3. AI inference costs.`,
193
- tools: {
194
- subagent: tool({
195
- description: "Run a research subagent on a topic.",
196
- inputSchema: z.object({ topic: z.string() }),
197
- execute: async ({ topic }, { abortSignal }) => {
198
- const result = await researchSubagent.generate({
199
- prompt: `Research this topic: ${topic}`,
200
- abortSignal,
201
- });
202
- return result.text;
203
- },
204
- }),
205
- readNotes: createNoteTools(EXAMPLE_MESSAGE_ID).readNotes,
206
- },
207
- stopWhen: stepCountIs(10),
208
- });
209
-
210
- const result = await parent.generate({ prompt: "Start the research." });
211
- Each subagent runs in isolation but writes into the same Redis string. The parent kicks off the three subagents, and once they finish it calls readNotes itself to pull the full notes before synthesizing. Anthropic's orchestrator-workers pattern is the same shape: a central agent splits the work, workers run it, the central agent synthesizes.
212
-
213
- One note: this works because research subtopics are independent. If subagent B needs what subagent A found, we can't fan them out in parallel. We run them in sequence, or have the parent make a second round of calls after reading the first round's results from Redis.
214
-
215
- This patterns also allows us to implement a mechanism for the main agent to follow up (e.g. "keep chating") to research subagents. Because they keep their own message history and state, if the main model is unhappy or wants to follow up, we could simply pass the conversation ID into the research agent and it automatically can read and interact with previous notes.
216
-
217
- Persisting message history across requests
218
- The second use of Redis is saving message history. The AI SDK's useChat works with UIMessage[]. We save that array to Redis at the end of a request and load it at the start of the next one.
219
-
220
-
221
- import { Redis } from "@upstash/redis";
222
- import type { UIMessage } from "ai";
223
-
224
- const redis = new Redis({
225
- url: process.env.UPSTASH_REDIS_REST_URL!,
226
- token: process.env.UPSTASH_REDIS_REST_TOKEN!,
227
- });
228
-
229
- async function saveHistory(sessionId: string, messages: UIMessage[]) {
230
- await redis.set(`chat:${sessionId}`, messages, { ex: 86_400 });
231
- }
232
-
233
- async function loadHistory(sessionId: string) {
234
- const messages = await redis.get<UIMessage[]>(`chat:${sessionId}`);
235
- return messages ?? [];
236
- }
237
- Streaming subagent progress to the UI
238
- If a subagent runs for a while, we want to show the user it is working instead of "freezing" the UI until it finishes. A tool's execute can be an async generator. Each value it yields becomes a partial tool result that the client can render before the final chunk arrives.
239
-
240
-
241
- import { readUIMessageStream, tool } from "ai";
242
- import { z } from "zod";
243
-
244
- const streamingResearchTool = tool({
245
- description: "Delegate research to a streaming subagent.",
246
- inputSchema: z.object({ prompt: z.string() }),
247
- async *execute({ prompt }, { abortSignal }) {
248
- const result = await researchSubagent.stream({ prompt, abortSignal });
249
-
250
- for await (const message of readUIMessageStream({
251
- stream: result.toUIMessageStream(),
252
- })) {
253
- yield message;
254
- }
255
- },
256
- });
257
- The streamed result exposes a UI message stream. The readUIMessageStream helper turns that into an async iterable, where each value is the full message built up so far. The generator yields each update, and the client can now render the subagent's progress in real time.
258
-
259
- When to use a subagent and when not to
260
- Subagents add a layer of complexity. Every level of delegation is another model running its own loop. A single ToolLoopAgent with a good set of tools handles most tasks, and it is cheaper and easier to debug.
261
-
262
- But on the other hand, I find subagents to be the single most useful tool to avoid context bloat. By splitting my research and code verification into separate subagents for a project I'm building, the main model's output has become significantly better.
263
-
264
- So I'd add a subagent when one of these is true:
265
-
266
- Situation Single agent Subagent
267
- One task, a handful of tools Cheaper, easier to debug. Wins Overkill
268
- Work that fans out into independent subtasks Context bloat Wins. Run them in parallel, isolate each context.
269
- One subtask needs a different model or tool set Awkward to switch mid-loop Wins. Each subagent has its own model and tools.
270
- Exploration that would blow the context window Hits the model's limit or context bloat Wins. toModelOutput keeps the parent's context smal
271
- Recap
272
- A subagent is a ToolLoopAgent wrapped in a tool(); the parent calls it like any tool.
273
- Pass the abortSignal through so cancellation can reach the subagent.
274
- Subagent contexts are isolated by design
275
- With a shared Redis string keyed by a mocked message id, we can give parallel subagents a "scratchpad", and save UIMessage[] to Redis to persist message history.
276
- I'd add subagents when work is parallel, needs isolated context, or needs a different model; otherwise a single agent is the right default.
1
+ ZilMate coding-agent note
2
+
3
+ ZilMate uses the AI SDK v6 subagent pattern for coding work:
4
+
5
+ - Manager delegates software work to the main Coding Agent.
6
+ - Coding Agent stays responsible for orchestration and the final report.
7
+ - Coding Agent can call `appBuilder` for full app/game/software implementation.
8
+ - Coding Agent can call `qaIntegration` for builds, tests, debugging, and release-readiness checks.
9
+ - Both internal coding delegates use skills, file tools, shell tools, and notebook tools.
10
+ - Use scratchpad for temporary run coordination and notebook for durable project memory.
11
+ - Delegate tools pass `abortSignal` into subagents and emit subagent progress events so the CLI can show what is happening.
12
+
13
+ Building Subagents in the Vercel AI SDK v6
14
+ 11 min read
15
+ ·
16
+ Josh
17
+ Josh
18
+ Copy article
19
+
20
+ https://upstash.com/blog/subagents-in-ai-sdk-v6
21
+ A subagent in the AI SDK v6 is one agent wrapped inside a tool() so another agent can call it. The parent agent treats the subagent like any other tool: it sends a prompt, gets back text, and decides what to do next.
22
+
23
+ I find them to be the single most useful pattern to avoid context bloat. No matter how large their task or own context load is, they only return the most important information from their process back to the main agent.
24
+
25
+ Subagents take care of context-intensive tasks (e.g. research)
26
+ Subagents take care of context-intensive tasks (e.g. research)
27
+
28
+ The new v6 ToolLoopAgent
29
+ Before v6, building a multi-agent setup meant chaining generateText calls and passing messages between them. The functions to generate or stream text were independant primitives:
30
+
31
+ In v5, generateText and streamText are primitives
32
+ In v5, generateText and streamText are primitives
33
+
34
+ In v6, an agent is its own class we can now call functions on. We define it once with a model, instructions, and tools, then call generate or stream on it:
35
+
36
+ New: tools, prompts etc. move to a single class
37
+ New: tools, prompts etc. move to a single class
38
+
39
+ The class is ToolLoopAgent. The name describes what it does: it runs the model, executes any tool calls, feeds the results back, and loops until a stop condition fires.
40
+
41
+
42
+ import { anthropic } from "@ai-sdk/anthropic";
43
+ import { stepCountIs, ToolLoopAgent } from "ai";
44
+
45
+ const agent = new ToolLoopAgent({
46
+ model: anthropic("claude-sonnet-4-6"),
47
+ instructions: "You are a research agent. Answer the task autonomously.",
48
+ tools: {
49
+ /* ... */
50
+ },
51
+ stopWhen: stepCountIs(10),
52
+ });
53
+
54
+ const result = await agent.generate({ prompt: "Summarize the latest on X." });
55
+ console.log(result.text);
56
+ A subagent is just a tool
57
+ A subagent is a ToolLoopAgent that a parent agent calls through a tool(). The tool's execute function runs the subagent and returns its text.
58
+
59
+
60
+ import { anthropic } from "@ai-sdk/anthropic";
61
+ import { stepCountIs, tool, ToolLoopAgent } from "ai";
62
+ import { z } from "zod";
63
+
64
+ const researchSubagent = new ToolLoopAgent({
65
+ model: anthropic("claude-sonnet-4-6"),
66
+ instructions: "You are a focused research subagent. Return only a summary.",
67
+ stopWhen: stepCountIs(10),
68
+ });
69
+
70
+ const researchTool = tool({
71
+ description: "Delegate a research task to a subagent.",
72
+ inputSchema: z.object({ prompt: z.string() }),
73
+ execute: async ({ prompt }, { abortSignal }) => {
74
+ const result = await researchSubagent.generate({ prompt, abortSignal });
75
+ return result.text;
76
+ },
77
+ });
78
+
79
+ const parentAgent = new ToolLoopAgent({
80
+ model: anthropic("claude-sonnet-4-6"),
81
+ instructions: "Delegate research, then synthesize an answer.",
82
+ tools: { research: researchTool },
83
+ stopWhen: stepCountIs(10),
84
+ });
85
+ Two details are important here.
86
+
87
+ First, the tool field is inputSchema, not parameters. Earlier AI SDK versions used parameters; v5 renamed it to inputSchema to align with the Model Context Protocol, and v6 keeps that name.
88
+
89
+ Second, the execute function takes abortSignal from its second argument and passes it into the subagent. If the parent request is cancelled, that cancellation reaches the subagent too. Without it, a cancelled request leaves subagents running in the background, still using tokens.
90
+
91
+ Controlling the subagent output
92
+ By default, the parent receives whatever the subagent tool returns. A research subagent might run ten steps and produce a lot of text, and we may not want all of that landing back in the parent's context window.
93
+
94
+ With toModelOutput, we can decouple what the tool returns from what gets passed into the parent model. It's like a separate parsing step.
95
+
96
+
97
+ const researchTool = tool({
98
+ description: "Delegate a research task to a subagent.",
99
+ inputSchema: z.object({ prompt: z.string() }),
100
+ execute: async ({ prompt }, { abortSignal }) => {
101
+ const result = await researchSubagent.generate({ prompt, abortSignal });
102
+ return result.text;
103
+ },
104
+ toModelOutput: ({ output }) => ({ type: "text", value: output }),
105
+ });
106
+ This way the parent's context stays small while the subagent can consume an almost arbitrary amount of tokens, just bounded by it's context limit. Because either way, it will not bloat our parent.
107
+
108
+ This patterns is also super useful for keeping the parent's token count low as the number of subagents grows.
109
+
110
+ Creating a stop condition
111
+ A ToolLoopAgent keeps looping until a StopCondition tells it to stop. The default is stepCountIs(20), so an agent with no stopWhenwill run up to 20 steps:
112
+
113
+
114
+ import { anthropic } from "@ai-sdk/anthropic";
115
+ import { hasToolCall, stepCountIs, type StopCondition } from "ai";
116
+
117
+ // custom stop condition
118
+ const stopAfterAnyToolUse: StopCondition<any, any> = ({ steps }) =>
119
+ steps.some((step) => step.toolCalls.length > 0);
120
+
121
+ const agent = new ToolLoopAgent({
122
+ model: anthropic("claude-sonnet-4-6"),
123
+ stopWhen: [stepCountIs(10), hasToolCall("done"), stopAfterAnyToolUse],
124
+ });
125
+ We can pass an array of conditions, and the loop stops when any one of them is true. stepCountIs(n) caps the step count, hasToolCall(name) stops once the agent uses any tool, and a custom function gets the full steps array so we can stop on anything we can compute from it, like a token budget.
126
+
127
+ By the way, prepareStep runs before every step and lets us change the model, the tools, or the messages for that step:
128
+
129
+
130
+ const agent = new ToolLoopAgent({
131
+ model: anthropic("claude-sonnet-4-6"),
132
+ tools: { research: researchTool, done: doneTool },
133
+ prepareStep: ({ stepNumber }) => ({
134
+ toolChoice: stepNumber > 8 ? { type: "tool", toolName: "done" } : "auto",
135
+ }),
136
+ });
137
+ This one forces the agent toward a done tool as it nears its step limit, instead of letting it stall.
138
+
139
+ The isolation problem
140
+ A subagent invocation starts with a fresh context window every time. The subagents docs call context isolation a feature, and for a single delegated task it is. The subagent doesn't load the parent's full history, and the parent shouldn't know about the subagent's intermediate steps.
141
+
142
+ The isolation goes both ways. But in two cases it kinda gets in the way:
143
+
144
+ Parallel subagents. The main agent runs three research subagents at once and none of them can see what the others found. If two should avoid duplicating work, there's no way for them to coordinate.
145
+ Separate requests. In serverless, each HTTP request can be a cold start. Anything a subagent held in memory on the last request is gone. The orchestrator on the second request doesn't know what the subagents did on the first request.
146
+ Parallel subagents cannot talk to each other.
147
+ Parallel subagents cannot talk to each other.
148
+
149
+ Moving the shared state out of process fixes both problems. The official memory docs point at hosted memory services for this, but for short-lived agent state we use Redis. It works with HTTP and the key expiry handles cleanup automatically.
150
+
151
+ Sharing state across subagents with Redis
152
+ A pattern I really like is a "shared scratchpad". It's one Redis string keyed by the current message id. Each subagent gets two tools: one to read what the others have already written, and one to append its own findings. We pass the same mocked message id to every subagent so they all point at the same key.
153
+
154
+
155
+ import { redis } from "@/lib/redis";
156
+ import { anthropic } from "@ai-sdk/anthropic";
157
+ import { stepCountIs, tool, ToolLoopAgent } from "ai";
158
+ import { z } from "zod";
159
+
160
+ function createNoteTools(messageId: string) {
161
+ return {
162
+ readNotes: tool({
163
+ description: "Read what the other subagents have found so far.",
164
+ inputSchema: z.object({}),
165
+ execute: async () => {
166
+ return (await redis.get<string>(`notes:${messageId}`)) ?? "(empty)";
167
+ },
168
+ }),
169
+ appendToNotes: tool({
170
+ description: "Append your findings to the shared notes.",
171
+ inputSchema: z.object({ findings: z.string() }),
172
+ execute: async ({ findings }) => {
173
+ await redis.append(`notes:${messageId}`, `\n${findings}`);
174
+ return "Appended.";
175
+ },
176
+ }),
177
+ };
178
+ }
179
+
180
+ // this comes from the ai sdk
181
+ const EXAMPLE_MESSAGE_ID = "example-run-001";
182
+
183
+ const researchSubagent = new ToolLoopAgent({
184
+ model: anthropic("claude-sonnet-4-6"),
185
+ instructions: `You are a research subagent. Read your notes to see what others found, then append your research.`,
186
+ tools: createNoteTools(EXAMPLE_MESSAGE_ID),
187
+ stopWhen: stepCountIs(10),
188
+ });
189
+
190
+ const parent = new ToolLoopAgent({
191
+ model: anthropic("claude-sonnet-4-6"),
192
+ instructions: `Start three research subagents in parallel on these topics: 1. Serverless databases 2. Edge computing 3. AI inference costs.`,
193
+ tools: {
194
+ subagent: tool({
195
+ description: "Run a research subagent on a topic.",
196
+ inputSchema: z.object({ topic: z.string() }),
197
+ execute: async ({ topic }, { abortSignal }) => {
198
+ const result = await researchSubagent.generate({
199
+ prompt: `Research this topic: ${topic}`,
200
+ abortSignal,
201
+ });
202
+ return result.text;
203
+ },
204
+ }),
205
+ readNotes: createNoteTools(EXAMPLE_MESSAGE_ID).readNotes,
206
+ },
207
+ stopWhen: stepCountIs(10),
208
+ });
209
+
210
+ const result = await parent.generate({ prompt: "Start the research." });
211
+ Each subagent runs in isolation but writes into the same Redis string. The parent kicks off the three subagents, and once they finish it calls readNotes itself to pull the full notes before synthesizing. Anthropic's orchestrator-workers pattern is the same shape: a central agent splits the work, workers run it, the central agent synthesizes.
212
+
213
+ One note: this works because research subtopics are independent. If subagent B needs what subagent A found, we can't fan them out in parallel. We run them in sequence, or have the parent make a second round of calls after reading the first round's results from Redis.
214
+
215
+ This patterns also allows us to implement a mechanism for the main agent to follow up (e.g. "keep chating") to research subagents. Because they keep their own message history and state, if the main model is unhappy or wants to follow up, we could simply pass the conversation ID into the research agent and it automatically can read and interact with previous notes.
216
+
217
+ Persisting message history across requests
218
+ The second use of Redis is saving message history. The AI SDK's useChat works with UIMessage[]. We save that array to Redis at the end of a request and load it at the start of the next one.
219
+
220
+
221
+ import { Redis } from "@upstash/redis";
222
+ import type { UIMessage } from "ai";
223
+
224
+ const redis = new Redis({
225
+ url: process.env.UPSTASH_REDIS_REST_URL!,
226
+ token: process.env.UPSTASH_REDIS_REST_TOKEN!,
227
+ });
228
+
229
+ async function saveHistory(sessionId: string, messages: UIMessage[]) {
230
+ await redis.set(`chat:${sessionId}`, messages, { ex: 86_400 });
231
+ }
232
+
233
+ async function loadHistory(sessionId: string) {
234
+ const messages = await redis.get<UIMessage[]>(`chat:${sessionId}`);
235
+ return messages ?? [];
236
+ }
237
+ Streaming subagent progress to the UI
238
+ If a subagent runs for a while, we want to show the user it is working instead of "freezing" the UI until it finishes. A tool's execute can be an async generator. Each value it yields becomes a partial tool result that the client can render before the final chunk arrives.
239
+
240
+
241
+ import { readUIMessageStream, tool } from "ai";
242
+ import { z } from "zod";
243
+
244
+ const streamingResearchTool = tool({
245
+ description: "Delegate research to a streaming subagent.",
246
+ inputSchema: z.object({ prompt: z.string() }),
247
+ async *execute({ prompt }, { abortSignal }) {
248
+ const result = await researchSubagent.stream({ prompt, abortSignal });
249
+
250
+ for await (const message of readUIMessageStream({
251
+ stream: result.toUIMessageStream(),
252
+ })) {
253
+ yield message;
254
+ }
255
+ },
256
+ });
257
+ The streamed result exposes a UI message stream. The readUIMessageStream helper turns that into an async iterable, where each value is the full message built up so far. The generator yields each update, and the client can now render the subagent's progress in real time.
258
+
259
+ When to use a subagent and when not to
260
+ Subagents add a layer of complexity. Every level of delegation is another model running its own loop. A single ToolLoopAgent with a good set of tools handles most tasks, and it is cheaper and easier to debug.
261
+
262
+ But on the other hand, I find subagents to be the single most useful tool to avoid context bloat. By splitting my research and code verification into separate subagents for a project I'm building, the main model's output has become significantly better.
263
+
264
+ So I'd add a subagent when one of these is true:
265
+
266
+ Situation Single agent Subagent
267
+ One task, a handful of tools Cheaper, easier to debug. Wins Overkill
268
+ Work that fans out into independent subtasks Context bloat Wins. Run them in parallel, isolate each context.
269
+ One subtask needs a different model or tool set Awkward to switch mid-loop Wins. Each subagent has its own model and tools.
270
+ Exploration that would blow the context window Hits the model's limit or context bloat Wins. toModelOutput keeps the parent's context smal
271
+ Recap
272
+ A subagent is a ToolLoopAgent wrapped in a tool(); the parent calls it like any tool.
273
+ Pass the abortSignal through so cancellation can reach the subagent.
274
+ Subagent contexts are isolated by design
275
+ With a shared Redis string keyed by a mocked message id, we can give parallel subagents a "scratchpad", and save UIMessage[] to Redis to persist message history.
276
+ I'd add subagents when work is parallel, needs isolated context, or needs a different model; otherwise a single agent is the right default.
@@ -1 +1 @@
1
- {"version":3,"file":"automation-planner.agent.d.ts","sourceRoot":"","sources":["../../src/agents/automation-planner.agent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,aAAa,EAAE,MAAM,IAAI,CAAC;AAUhD,wBAAgB,4BAA4B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mBA2B8+uG,CAAC;wBAAsC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gBAAz2S,CAAC;uBAAqC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAD/v8F"}
1
+ {"version":3,"file":"automation-planner.agent.d.ts","sourceRoot":"","sources":["../../src/agents/automation-planner.agent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,aAAa,EAAE,MAAM,IAAI,CAAC;AAUhD,wBAAgB,4BAA4B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mBA2By8uG,CAAC;wBAAsC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gBAAz2S,CAAC;uBAAqC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAD1t8F"}
@@ -80,6 +80,24 @@ export declare function createCodingAgent(runId?: string): ToolLoopAgent<never,
80
80
  stderr: any;
81
81
  command: string;
82
82
  }>;
83
+ executeAndSelfHeal: import("ai").Tool<{
84
+ command: string;
85
+ context?: string | undefined;
86
+ }, {
87
+ success: boolean;
88
+ stdout: string;
89
+ command: string;
90
+ error?: never;
91
+ diagnosisHint?: never;
92
+ context?: never;
93
+ } | {
94
+ success: boolean;
95
+ command: string;
96
+ error: any;
97
+ diagnosisHint: string;
98
+ context: string;
99
+ stdout?: never;
100
+ }>;
83
101
  installDependencies: import("ai").Tool<{
84
102
  command: string;
85
103
  manager?: "npm" | "pnpm" | "yarn" | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"coding.agent.d.ts","sourceRoot":"","sources":["../../src/agents/coding.agent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,aAAa,EAAQ,MAAM,IAAI,CAAC;AA8FtD,wBAAgB,iBAAiB,CAAC,KAAK,SAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAiClD"}
1
+ {"version":3,"file":"coding.agent.d.ts","sourceRoot":"","sources":["../../src/agents/coding.agent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,aAAa,EAAQ,MAAM,IAAI,CAAC;AA8FtD,wBAAgB,iBAAiB,CAAC,KAAK,SAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;UAiClD"}