@standardagents/skill 0.40.12 → 0.41.0
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.
- package/package.json +1 -1
- package/skills/agentbuilder/SKILL.md +284 -142
package/package.json
CHANGED
|
@@ -20,6 +20,7 @@ For deeper reference material that this skill links to, see:
|
|
|
20
20
|
|
|
21
21
|
- `agents/agents/AGENTS.md` — agent definition reference (created by `agents scaffold`)
|
|
22
22
|
- `agents/prompts/AGENTS.md` — prompt and tool config reference
|
|
23
|
+
- `agents/handlers/AGENTS.md` — handler reference (`defineHandler`, deterministic side backings)
|
|
23
24
|
- `agents/tools/AGENTS.md` — tool-writing patterns and `ThreadState` examples
|
|
24
25
|
- `agents/models/AGENTS.md` — recommended model list (authoritative)
|
|
25
26
|
- `agents/hooks/AGENTS.md` — hook reference
|
|
@@ -53,11 +54,11 @@ Most failed Standard Agent projects fail in the same seven ways. Read this secti
|
|
|
53
54
|
**1. Architect the agent graph before picking types.**
|
|
54
55
|
List the domains the system touches. Determine the tree (coordinator → domain agents → sub-domain agents). *Then* decide what each node is. Do not start by writing one agent and bolting tools onto it. → [Architecture & decomposition](#architecture--decomposition)
|
|
55
56
|
|
|
56
|
-
**2.
|
|
57
|
-
|
|
57
|
+
**2. Decide what backs each side. Prompt × prompt is the default worker shape; a human backing is the exception.**
|
|
58
|
+
Every agent has exactly two explicit sides, and each side is backed by exactly one of a human, a prompt, or a handler. A human backing is correct only when the thread itself is the chat surface (chat UI, website widget, AgentBuilder admin, direct API chat). For Slack, email, SMS, Discord, webhooks, polled inboxes, schedulers, or any other mediated channel, the human is just a tool target — back both sides with prompts. The whole graph being prompt × prompt is fine and often cleaner. → [What backs each side?](#what-backs-each-side)
|
|
58
59
|
|
|
59
|
-
**3. Every
|
|
60
|
-
Name its `sessionStop` tool. Name its `sessionFail` tool. Set a finite `maxSessionTurns
|
|
60
|
+
**3. Every prompt × prompt agent must have explicit session boundaries.**
|
|
61
|
+
Name its `sessionStop` tool. Name its `sessionFail` tool. Set a finite `maxSessionTurns` (the spec makes the cap optional; this skill makes it required practice for prompt × prompt). Give side B a real, non-redundant job. If you cannot say in one sentence what tool call ends the session, the agent is not designed yet. → [Session boundary discipline](#session-boundary-discipline)
|
|
61
62
|
|
|
62
63
|
**4. Pick models from `current-models` only.**
|
|
63
64
|
Never invent model strings from memory. Run `pnpm exec agents current-models`, choose by category, then run `pnpm exec agents available-models --provider=<name>` to confirm the exact string. → [Model selection](#model-selection)
|
|
@@ -79,27 +80,27 @@ In the Standard Agent paradigm, agents are the atomic unit of an AI system, and
|
|
|
79
80
|
|
|
80
81
|
A "gmail" agent will outperform a "google apps" agent. A higher-level "communications" coordinator composes the gmail, slack, and SMS agents. A still-higher "personal assistant" coordinator composes communications, research, scheduling, and finance. This is fractal: the same shape scales up to teams, departments, and entire products. These agent-graphs solve real industry problems — progressive tool discovery, model/prompt tuning, context dilution, task resumability, and compaction.
|
|
81
82
|
|
|
82
|
-
Just because subagents *can* compose complex behavior doesn't mean every step needs one. A subagent is a two-sided conversation where
|
|
83
|
+
Just because subagents *can* compose complex behavior doesn't mean every step needs one. A subagent is a two-sided conversation where a prompt-backed side may take multiple steps per turn. Sometimes you only need a feature of a different model — generating an image, rewriting a paragraph in a more eloquent voice. For those, use a **subprompt** (a single LLM step exposed as a tool), not a subagent. Use a **subagent** when you need iteration, QA, reflection, or long-lived addressable behavior.
|
|
83
84
|
|
|
84
85
|
When a subagent does QA on another model's output, prefer a *different provider* on the reviewing side. Same-lab models tend to rate their own output too generously.
|
|
85
86
|
|
|
86
87
|
### Example graph
|
|
87
88
|
|
|
88
89
|
```
|
|
89
|
-
personal_assistant_coordinator (
|
|
90
|
-
├── communications_coordinator (
|
|
91
|
-
│ ├── gmail_agent (
|
|
92
|
-
│ ├── slack_agent (
|
|
93
|
-
│ └── sms_agent (
|
|
94
|
-
├── research_assistant (
|
|
95
|
-
│ ├── browser_use_agent (
|
|
96
|
-
│ ├── google_drive_agent (
|
|
97
|
-
│ └── notes_agent (
|
|
90
|
+
personal_assistant_coordinator (prompt × prompt, top-level reasoning model)
|
|
91
|
+
├── communications_coordinator (prompt × prompt child, explicit parent communication, note: children are explicit because they receive inbound messages and should filter out noise before waking the parent)
|
|
92
|
+
│ ├── gmail_agent (prompt × prompt child, immediate, explicit parent communication)
|
|
93
|
+
│ ├── slack_agent (prompt × prompt child, immediate, explicit parent communication)
|
|
94
|
+
│ └── sms_agent (prompt × prompt child, immediate, explicit parent communication)
|
|
95
|
+
├── research_assistant (prompt × prompt child, explicit parent communication)
|
|
96
|
+
│ ├── browser_use_agent (prompt × prompt child, implicit parent communication)
|
|
97
|
+
│ ├── google_drive_agent (prompt × prompt child, implicit parent communication)
|
|
98
|
+
│ └── notes_agent (prompt × prompt child, implicit parent communication)
|
|
98
99
|
├── scheduling_coordinator (subprompt)
|
|
99
|
-
└── finance_ops_coordinator (
|
|
100
|
+
└── finance_ops_coordinator (prompt × prompt child)
|
|
100
101
|
```
|
|
101
102
|
|
|
102
|
-
Note: no
|
|
103
|
+
Note: no human backing anywhere. The "human" enters via the gmail/slack/sms tool calls. The whole graph is autonomous, and every child is a persistent thread the parent can message again later — persistence is universal.
|
|
103
104
|
|
|
104
105
|
## The Standard Agent stack
|
|
105
106
|
|
|
@@ -107,9 +108,10 @@ The spec defines an agent as a composition of:
|
|
|
107
108
|
|
|
108
109
|
- **Providers** — how to talk to LLM providers and model variants
|
|
109
110
|
- **Models** — named model definitions referencing a provider
|
|
110
|
-
- **Prompts** — system instructions plus the tools
|
|
111
|
+
- **Prompts** — system instructions plus the tools and subprompts available at that step
|
|
111
112
|
- **Tools** — functions the agent can call to interact with the world (and other agents)
|
|
112
|
-
- **
|
|
113
|
+
- **Handlers** — deterministic code that owns a side's turns (defined via `defineHandler` in `agents/handlers/`)
|
|
114
|
+
- **Agents** — bind it all together: two sides, each backed by a human, a prompt, or a handler, plus session bindings and side-owned subagent relationships
|
|
113
115
|
- **Hooks** — custom code that intercepts lifecycle events (history filtering, message injection, tool result transforms)
|
|
114
116
|
- **Effects** — custom code scheduled to run later
|
|
115
117
|
- **Threads** — runtime instances of an agent, each with its own state, message history, and filesystem
|
|
@@ -123,8 +125,8 @@ The spec defines an agent as a composition of:
|
|
|
123
125
|
|
|
124
126
|
1. List the domains the system actually touches.
|
|
125
127
|
2. Draw the tree on paper or in a comment block. Coordinators on top, leaf agents at the bottom.
|
|
126
|
-
3. Pick
|
|
127
|
-
4. Name session boundaries for every
|
|
128
|
+
3. Pick backings for each side of each node. (Default prompt × prompt for workers. A human backing only at chat-surface entry points; a handler backing for deterministic, model-less surfaces.)
|
|
129
|
+
4. Name session boundaries for every prompt × prompt node.
|
|
128
130
|
5. Pick a model category for every node.
|
|
129
131
|
6. List the third-party APIs each leaf needs and queue them for research.
|
|
130
132
|
7. Map each capability to a `ThreadState` primitive before writing any custom plumbing.
|
|
@@ -147,7 +149,7 @@ Other smells that mean "decompose now":
|
|
|
147
149
|
**Wrong** — one mega-agent with tools from unrelated domains:
|
|
148
150
|
|
|
149
151
|
```
|
|
150
|
-
personal_assistant (
|
|
152
|
+
personal_assistant (human × prompt)
|
|
151
153
|
tools: send_gmail, read_gmail, search_ebay_listings, place_ebay_order,
|
|
152
154
|
track_ebay_shipment, create_calendar_event, list_calendar_events,
|
|
153
155
|
post_slack_message, read_slack_channel, query_stock_price,
|
|
@@ -159,7 +161,7 @@ Gmail, eBay, calendar, Slack, stock trading, recipes, image gen — seven unrela
|
|
|
159
161
|
**Right** — coordinator + domain subagents:
|
|
160
162
|
|
|
161
163
|
```
|
|
162
|
-
personal_assistant_coordinator (
|
|
164
|
+
personal_assistant_coordinator (prompt × prompt, reasoning model)
|
|
163
165
|
├── gmail_agent tools: send_gmail, read_gmail
|
|
164
166
|
├── ebay_agent tools: search_ebay_listings, place_ebay_order,
|
|
165
167
|
│ track_ebay_shipment
|
|
@@ -184,81 +186,133 @@ Deeper graphs add latency and create more inter-agent communication that can fai
|
|
|
184
186
|
|
|
185
187
|
---
|
|
186
188
|
|
|
187
|
-
##
|
|
189
|
+
## What backs each side?
|
|
188
190
|
|
|
189
|
-
|
|
191
|
+
Every agent has **exactly two sides** — `sideA` and `sideB` — and each side is **backed by** exactly one of three things:
|
|
190
192
|
|
|
191
|
-
|
|
193
|
+
- `{ human: true }` — a person takes this side's turns
|
|
194
|
+
- `{ prompt: 'name', ... }` — a model, driven by a prompt
|
|
195
|
+
- `{ handler: 'name', ... }` — deterministic code (`defineHandler` in `agents/handlers/`)
|
|
196
|
+
|
|
197
|
+
There is no agent `type` field. Chat surface, composability, and termination are all **derived from the backings**. Both sides are always explicit — if a person takes side B's turns, write `sideB: { human: true }`; there is no implicit default side.
|
|
198
|
+
|
|
199
|
+
> **Prompt × prompt is the default worker shape. A human backing is the exception**, used only when the thread itself is the chat surface — chat UI, website widget, AgentBuilder admin, or an API the user is talking to directly.
|
|
200
|
+
|
|
201
|
+
For **Slack, email, SMS, Discord, webhooks, polled inboxes, schedulers, or any other mediated channel**, the human is just a tool target. Back both sides with prompts. The whole graph being prompt × prompt is fine — often cleaner.
|
|
192
202
|
|
|
193
203
|
### The single decision
|
|
194
204
|
|
|
195
|
-
Ask: **"
|
|
205
|
+
Ask, for each side: **"Who takes this side's turns?"**
|
|
196
206
|
|
|
197
|
-
- **
|
|
198
|
-
- **
|
|
207
|
+
- **A person typing directly into this thread** → `{ human: true }`. (Conventionally the person is side B, but a human on side A is legal.)
|
|
208
|
+
- **A model that must reason** → `{ prompt: '...' }`.
|
|
209
|
+
- **Deterministic code — routing, forwarding, protocol glue, no model judgment** → `{ handler: '...' }`.
|
|
199
210
|
|
|
200
|
-
Do not
|
|
211
|
+
Do not reach for a human backing just because a human is eventually involved. Ask where messages physically arrive. And do not burn a model on a side whose behavior is a five-line function — that's a handler.
|
|
201
212
|
|
|
202
|
-
### The
|
|
213
|
+
### The common shapes
|
|
203
214
|
|
|
204
|
-
**
|
|
215
|
+
**Human × prompt — chat surface:**
|
|
205
216
|
|
|
206
217
|
```
|
|
207
|
-
website_chatbot (
|
|
218
|
+
website_chatbot (human × prompt) ← user types in a chat widget
|
|
208
219
|
tools: search_products, lookup_order, escalate_to_human
|
|
209
220
|
```
|
|
210
221
|
|
|
211
|
-
**
|
|
222
|
+
**Prompt × prompt — autonomous worker/reviewer:**
|
|
212
223
|
|
|
213
224
|
```
|
|
214
|
-
slack_research_assistant (
|
|
215
|
-
├── slack_agent (
|
|
216
|
-
│
|
|
217
|
-
└── research_agent (
|
|
225
|
+
slack_research_assistant (prompt × prompt, reasoning model)
|
|
226
|
+
├── slack_agent (prompt × prompt child) ← messages arrive via tool calls,
|
|
227
|
+
│ not as thread messages
|
|
228
|
+
└── research_agent (prompt × prompt child)
|
|
218
229
|
```
|
|
219
230
|
|
|
220
|
-
|
|
231
|
+
Side A of `slack_research_assistant` plans the work and dispatches subagents. Side B reviews and decides when the work is done. The "user" is a Slack channel reachable through `slack_agent`'s tools.
|
|
232
|
+
|
|
233
|
+
**Handler × human — deterministic, model-less surface:**
|
|
234
|
+
|
|
235
|
+
A person talks to a thread on which **zero model work** happens: a handler forwards every inbound message to a composed child and relays answers back through userland messaging. See the [relay worked example](#worked-example-a-model-less-relay-handler--human) below.
|
|
236
|
+
|
|
237
|
+
### Handlers: deterministic turn owners
|
|
238
|
+
|
|
239
|
+
A handler is a function the runtime calls to take a side's turns — not a hook (hooks decorate turns owned by something else; their failures are non-fatal), not a tool (tools are capabilities a turn-owner chooses to invoke), and not an effect (effects run outside the conversation flow). Handlers live in `agents/handlers/` and take their name from the filename:
|
|
240
|
+
|
|
241
|
+
```ts
|
|
242
|
+
// agents/handlers/relay_pipe.ts
|
|
243
|
+
import { defineHandler } from '@standardagents/spec';
|
|
244
|
+
|
|
245
|
+
export default defineHandler({
|
|
246
|
+
description: 'Forwards inbound user traffic to the assistant child and relays answers back.',
|
|
247
|
+
execute: async (state, message) => {
|
|
248
|
+
// full ThreadState — forward, respond, spawn, stop, read/write KV
|
|
249
|
+
},
|
|
250
|
+
});
|
|
251
|
+
```
|
|
221
252
|
|
|
222
|
-
|
|
253
|
+
Rules to internalize:
|
|
223
254
|
|
|
224
|
-
|
|
255
|
+
- **Delivery is exactly-once per message**, in persistence order, never concurrent per thread. A message that arrives while a handler turn is in flight waits in the queue. A handler that awaits a `blocking` child session holds that delivery slot for the whole child session — relay-style handlers should forward without blocking (`queueMessage` toward the child) and treat the child's replies as ordinary later turns.
|
|
256
|
+
- **A throwing handler is fatal to the turn, and only the turn.** The session continues; the next inbound message gets a fresh invocation. There is no automatic redelivery — retry is a userland decision.
|
|
257
|
+
- **No `maxSteps`, no stop bindings, no session bindings** on handler sides — a handler turn has no steps to cap and ends when `execute` returns. Handlers end sessions via `state.stopSession()`.
|
|
258
|
+
- **Handlers may not appear in prompt `tools` arrays, hook lists, or effect schedules.** They are referenced from exactly one place: the `handler` property of a side.
|
|
259
|
+
- **No auto-injected reply channel.** *The runtime routes inbound; userland decides outbound* — if a handler should speak to a user or an external surface, that is userland code calling the messaging surface it chooses (the `message_user` pattern).
|
|
260
|
+
|
|
261
|
+
### Handoffs
|
|
262
|
+
|
|
263
|
+
When an agent with `exposeAsTool: true` is referenced **directly in a prompt's `tools` array**, the tool call is a *handoff*: the runtime does **not** spawn a new thread — it transfers control of the *same thread* to the target agent, preserving history. The target must have a human side matching the thread's conversational surface. Useful for stepwise human flows: an `onboarding_agent` collects information, then hands off to a `scheduling_agent` that books a meeting. The user keeps talking to the same thread.
|
|
264
|
+
|
|
265
|
+
Composition into a **child thread** is never a `tools` entry — it is a `SubagentRelationship` declared on a side's `subagents` array. Handoffs are the only agents-as-tools references.
|
|
225
266
|
|
|
226
267
|
### Subprompts vs. subagents vs. handoffs
|
|
227
268
|
|
|
228
|
-
- **Subprompt** — one LLM step exposed as a tool. Use to switch models for a focused task: image generation, polished writing, JSON extraction.
|
|
229
|
-
- **Subagent** —
|
|
230
|
-
- **Handoff** — `
|
|
269
|
+
- **Subprompt** — one LLM step exposed as a tool. Use to switch models for a focused task: image generation, polished writing, JSON extraction. Listed in a prompt's `tools` array.
|
|
270
|
+
- **Subagent** — *any* agent composed as a child via a side's `subagents` array. Full child thread with its own `ThreadState`. Use when you need iteration, QA, reflection, or long-lived addressable behavior. Composing a child with a human side puts a person's step inside the composition — the approval-gate pattern; the child parks with status `awaiting_human` until the person responds.
|
|
271
|
+
- **Handoff** — an `exposeAsTool` agent in a prompt's `tools` array; swaps control of the same thread. Use for stepwise human-driven flows.
|
|
231
272
|
|
|
232
|
-
|
|
273
|
+
**Persistence is universal.** Every child instance is a persistent thread — there are no disposable instances and no opt-in persistence wrapper. Session end (stop, fail, error, or turn-cap) never terminates the thread; it transitions registry status, and a later message starts a new session with history intact. Only explicit `terminate()` destroys an instance.
|
|
233
274
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
- **
|
|
237
|
-
- **Non-blocking
|
|
275
|
+
`blocking` is purely a *waiting style* — whether `subagent_create` awaits the child session's close and returns its result — and is orthogonal to persistence:
|
|
276
|
+
|
|
277
|
+
- **Blocking** — the parent waits for the child session to close and receives its result payload (tool-call feel). With `maxInstances: 1` and an existing instance, `subagent_create` behaves as **create-or-continue**: it messages the existing instance and awaits *that* session's close.
|
|
278
|
+
- **Non-blocking** — fire-and-forget spawn; the child's completion arrives later as a message (or not at all, with `parentCommunication: 'explicit'`).
|
|
279
|
+
|
|
280
|
+
Either way the child remains addressable via `subagent_message` afterward.
|
|
238
281
|
|
|
239
282
|
---
|
|
240
283
|
|
|
241
284
|
## Session boundary discipline
|
|
242
285
|
|
|
243
|
-
This section is a hard rule, not a suggestion. **Every
|
|
286
|
+
This section is a hard rule, not a suggestion. **Every composed prompt × prompt agent must explicitly define its session boundaries.** Two prompt-backed sides can ping-pong indefinitely — nothing external stops two models — so the spec keeps the caps voluntary and puts termination discipline on prompt and handler authors. This discipline is what makes whole-graph prompt × prompt safe.
|
|
287
|
+
|
|
288
|
+
### Required practice for every composed prompt × prompt child
|
|
244
289
|
|
|
245
|
-
|
|
290
|
+
- **`sessionStop` binding** (object form: `{ name, messageProperty }`) — names the tool call that ends the session with a result. Side A or side B invokes it when the work is done. Common patterns: `report_findings`, `submit_to_parent`, `final_answer`, `done`. For composed children, the mapped payload is the canonical child → parent result.
|
|
291
|
+
- **`sessionFail` binding** — names the tool call that ends the session with a failure the parent should see. Common patterns: `escalate_blocked`, `report_unresolvable`, `give_up_with_reason`.
|
|
292
|
+
- **`maxSessionTurns`** — optional on *every* agent shape in the spec, with uniform exchange counting across backings (a handler turn counts like a prompt turn). Treat it as required practice for prompt × prompt agents: a finite integer sized to the realistic upper bound for the task. A research subagent might be 20; an asset QA loop might be 6; a tight reflection loop might be 3. It is a voluntary safety valve — the session tools are the *designed* ending; the cap is the backstop.
|
|
293
|
+
- **A real job for side B** — reflection, QA, judging, alternative-perspective driving, devil's advocate. If side B is "another instance of side A," you don't have a prompt × prompt agent — collapse it to a single-side prompt or a coordinator pattern.
|
|
294
|
+
- **Cross-provider QA** — when side B reviews side A's output, pick a model from a *different provider* than side A. Same-lab models systematically over-rate their own work.
|
|
246
295
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
296
|
+
Session bindings are legal **only on prompt-backed sides** — a session binding, `maxSteps`, or stop binding on a human or handler side is a definition-time error.
|
|
297
|
+
|
|
298
|
+
### Handler termination discipline
|
|
299
|
+
|
|
300
|
+
A handler runs only when a message arrives, so it cannot run away on its own — but a handler that *replies to every inbound message* paired with a prompt-backed side livelocks exactly like two models. Handler authors own their termination discipline:
|
|
301
|
+
|
|
302
|
+
- reply conditionally, not unconditionally;
|
|
303
|
+
- or set `maxSessionTurns` on the agent (available on every shape) as a hard safety valve;
|
|
304
|
+
- or end the session explicitly with `state.stopSession()` when the work is done.
|
|
252
305
|
|
|
253
306
|
### The smell test
|
|
254
307
|
|
|
255
|
-
> If you cannot articulate in one sentence what tool call ends the session, the agent is not designed yet.
|
|
308
|
+
> If you cannot articulate in one sentence what tool call (or `stopSession` call) ends the session, the agent is not designed yet.
|
|
256
309
|
|
|
257
310
|
Examples that pass:
|
|
258
311
|
|
|
259
|
-
- "
|
|
260
|
-
- "
|
|
312
|
+
- "Side A calls `submit_video_assets` once side B approves the renders."
|
|
313
|
+
- "Side B calls `report_findings` after the research turn count exceeds 5 or it judges the topic exhausted."
|
|
261
314
|
- "Either side calls `escalate_to_parent` if the customer policy question is unresolvable."
|
|
315
|
+
- "The relay handler calls `state.stopSession()` when the child reports the workflow finished."
|
|
262
316
|
|
|
263
317
|
Examples that fail:
|
|
264
318
|
|
|
@@ -267,7 +321,7 @@ Examples that fail:
|
|
|
267
321
|
|
|
268
322
|
### Why this matters
|
|
269
323
|
|
|
270
|
-
A
|
|
324
|
+
A prompt × prompt agent with no `sessionStop`, no `sessionFail`, and no `maxSessionTurns` will burn turns until it hits a runtime cap, then fail in a way the parent can't interpret. Coding agents that have been bitten by this once will start bolting a human backing onto workers to "feel safer" — and then we're back to mega-agents and chat-surface confusion. Boundary discipline is what keeps prompt × prompt the default worker shape.
|
|
271
325
|
|
|
272
326
|
---
|
|
273
327
|
|
|
@@ -283,9 +337,9 @@ The most common quality failure when a coding agent creates a Standard Agent is
|
|
|
283
337
|
| `description` | One concrete sentence: what it does, for whom, with what. Shown in UIs. | "An agent that helps with email." | "Triages the support inbox, drafts replies for known issues, and escalates unknowns to a human queue." |
|
|
284
338
|
| `toolDescription` | Written for the **calling model**, not a human: when to choose this tool, what the key arguments are, what comes back. Required when `exposeAsTool: true`. | "Research agent." | "Research a topic with web search and source-backed URLs; returns a cited answer after an internal sufficiency review. Use for questions that need current or verifiable facts." |
|
|
285
339
|
| `icon` | Always set one. See below. | omitted | inline SVG data URI or hosted URL |
|
|
286
|
-
| `sideA.label` / `sideB.label` | Always set both on
|
|
340
|
+
| `sideA.label` / `sideB.label` | Always set both on prompt × prompt agents. Name the **roles in the conversation** — they appear in the UI and logs. | "Side A" / "Side B", "AI 1" / "AI 2" | "Researcher" / "Reviewer", "Pilot" / "ATC", "Drafter" / "Editor", "Support" / "Customer" |
|
|
287
341
|
|
|
288
|
-
Side labels are also a design check: if you can't name the two roles,
|
|
342
|
+
Side labels are also a design check: if you can't name the two roles, side B probably doesn't have a real job yet — go back to [Session boundary discipline](#session-boundary-discipline).
|
|
289
343
|
|
|
290
344
|
### Icons
|
|
291
345
|
|
|
@@ -310,7 +364,7 @@ A session-ending tool is not "configured" until it appears in **all three** of t
|
|
|
310
364
|
|
|
311
365
|
1. **`agents/tools/<name>.ts`** — a real `defineTool` whose args schema carries the payload fields (e.g. `final_answer`, `failure_summary`). The tool body can be a simple formatter; its job is to be callable and carry structured args.
|
|
312
366
|
2. **The side's prompt `tools` array** — the model can only call tools listed at the active step. A `sessionStop` binding to a tool the prompt never offers means the session *can never end on purpose*; it burns `maxSessionTurns` and fails opaquely.
|
|
313
|
-
3. **The
|
|
367
|
+
3. **The prompt side's binding** — `sessionStop: { name: 'complete_research', messageProperty: 'final_answer' }`. Bindings are object-form only (bare strings are rejected at definition time), and they are legal only on prompt-backed sides. `messageProperty` (and `attachmentsProperty` when files are returned) define the canonical payload the parent receives from a subagent. A binding with no mapped `messageProperty` returns a vague payload upstream.
|
|
314
368
|
|
|
315
369
|
And a fourth, softer place: **the prompt text must tell the model when to call it** ("When the renders pass review, call `submit_video_assets` with ..."). Models do not infer termination protocol from tool names.
|
|
316
370
|
|
|
@@ -327,9 +381,9 @@ Quick failure-mode table:
|
|
|
327
381
|
|
|
328
382
|
A two-sentence system prompt is a skeleton, not a prompt. Every side's prompt should cover, roughly in this order:
|
|
329
383
|
|
|
330
|
-
1. **Role and counterpart.** Who this side is *and who it is talking to*: "You are the researcher. The other participant is the user who needs the question answered." In
|
|
384
|
+
1. **Role and counterpart.** Who this side is *and who it is talking to*: "You are the researcher. The other participant is the user who needs the question answered." In prompt × prompt, each side must understand the other side's role or the conversation degrades into two assistants being polite to each other.
|
|
331
385
|
2. **Objective and definition of done.** One sentence the model can test against: "Done means every factual claim has a source URL and the reviewer has approved."
|
|
332
|
-
3. **Tool guidance.** When to use which tool — including the session tools: name them, say exactly what triggers calling them, and what belongs in each argument. For
|
|
386
|
+
3. **Tool guidance.** When to use which tool — including the session tools: name them, say exactly what triggers calling them, and what belongs in each argument. For sides that own subagent relationships, distinguish `subagent_create` (new topic/instance) from `subagent_message` (follow-up to an existing instance), and tell the model to check `subagents` before creating so existing instances are reused.
|
|
333
387
|
4. **Edge cases.** This is what separates a thoughtful prompt from a placeholder. Walk the domain and enumerate what realistically goes wrong, with an instruction for each. The recurring ones:
|
|
334
388
|
- *Empty or low-quality results* — retry differently, broaden, or report honestly? How many attempts before giving up?
|
|
335
389
|
- *Auth / permission / quota errors from tools* — never retry blindly; call `sessionFail` with what the operator must fix.
|
|
@@ -348,17 +402,19 @@ Anti-patterns: a prompt that just restates the tool list ("You have tools to sea
|
|
|
348
402
|
|
|
349
403
|
When the new agent is a child in a graph, creating the agent file is half the job. Verify the full wiring:
|
|
350
404
|
|
|
351
|
-
- [ ] Child
|
|
352
|
-
- [ ]
|
|
405
|
+
- [ ] Child has `exposeAsTool: true` and a calling-model-grade `toolDescription` (the relationship's `description` can override it per-composition)
|
|
406
|
+
- [ ] Prompt × prompt children have finite `maxSessionTurns`, and `sessionStop` + `sessionFail` bound on the prompt side(s) that own those decisions
|
|
353
407
|
- [ ] The session tools pass the [three-places check](#session-tools-must-exist-in-three-places)
|
|
354
|
-
- [ ] The **parent
|
|
355
|
-
- [ ]
|
|
356
|
-
- [ ]
|
|
357
|
-
- [ ]
|
|
408
|
+
- [ ] The **owning parent side's `subagents` array** has the `SubagentRelationship` — an agent nobody declares is unreachable, and the declaration is the grant. Never put the relationship in a prompt's `tools` array (definition-time error). Declare it on the side whose turns should receive the child's traffic
|
|
409
|
+
- [ ] `blocking` and `maxInstances` are chosen deliberately ([waiting style vs. lifetime](#subprompts-vs-subagents-vs-handoffs)), and `initUserMessageProperty` (and `initAttachmentsProperty` for files) are mapped so the child starts with real context instead of an empty first turn
|
|
410
|
+
- [ ] The relationship name (`as`, defaulting to `agent`) doesn't collide with any tool name or the reserved lifecycle names (`subagents`, `subagent_create`, `subagent_message`), and appears on only one side
|
|
411
|
+
- [ ] The **parent prompt text** (for prompt-backed owning sides) says when to delegate to this child and explains `subagent_create` vs `subagent_message`
|
|
412
|
+
- [ ] Side B reviewers use a model from a *different provider* than side A ([why](#session-boundary-discipline))
|
|
413
|
+
- [ ] Run the dev server and check the composition view: the parent→child edge should be visible, and creating a test thread should show the child spawn and the completion payload arrive back on the owning side
|
|
358
414
|
|
|
359
415
|
### Worked example: a complete minimal agent
|
|
360
416
|
|
|
361
|
-
This is the shape every new
|
|
417
|
+
This is the shape every new prompt × prompt agent should have on day one — identity, icon, labels, both session tools, wired prompts. (Compressed for space; real prompts should be longer per the rubric above.)
|
|
362
418
|
|
|
363
419
|
```ts
|
|
364
420
|
// agents/tools/complete_audit.ts
|
|
@@ -376,9 +432,10 @@ export default defineTool({
|
|
|
376
432
|
|
|
377
433
|
```ts
|
|
378
434
|
// agents/agents/invoice_audit_agent.ts
|
|
435
|
+
import { defineAgent } from '@standardagents/spec';
|
|
436
|
+
|
|
379
437
|
export default defineAgent({
|
|
380
438
|
name: 'invoice_audit_agent',
|
|
381
|
-
type: 'dual_ai',
|
|
382
439
|
description: 'Audits uploaded invoices against purchase orders and reports discrepancies.',
|
|
383
440
|
icon: invoiceAuditIcon, // inline SVG data URI, see Icons above
|
|
384
441
|
maxSessionTurns: 16,
|
|
@@ -401,7 +458,48 @@ export default defineAgent({
|
|
|
401
458
|
});
|
|
402
459
|
```
|
|
403
460
|
|
|
404
|
-
Both prompts list their session tools in `tools` and the reviewer prompt instructs: *"Approve by calling `complete_audit` with the full findings in `findings` only after every line item has been checked; if documents are unreadable or POs are missing after one clarification round, call `fail_audit` and name exactly what the requester must provide."* The parent
|
|
461
|
+
Both prompts list their session tools in `tools` and the reviewer prompt instructs: *"Approve by calling `complete_audit` with the full findings in `findings` only after every line item has been checked; if documents are unreadable or POs are missing after one clarification round, call `fail_audit` and name exactly what the requester must provide."* The parent declares the composition on its owning side — `subagents: [{ agent: 'invoice_audit_agent', blocking: true, initUserMessageProperty: 'request', initAttachmentsProperty: 'invoice_files' }]` — and the parent prompt text explains when to delegate.
|
|
462
|
+
|
|
463
|
+
### Worked example: a model-less relay (handler × human)
|
|
464
|
+
|
|
465
|
+
When a surface needs routing but no model judgment — a front door that pipes user traffic to a composed assistant and relays answers back — don't fake it with a prompt told to "just forward everything." Back the side with a handler. Zero LLM calls happen on this thread:
|
|
466
|
+
|
|
467
|
+
```ts
|
|
468
|
+
// agents/handlers/relay_pipe.ts
|
|
469
|
+
import { defineHandler } from '@standardagents/spec';
|
|
470
|
+
|
|
471
|
+
export default defineHandler({
|
|
472
|
+
description: 'Pipe user traffic to the assistant child; relay answers outward.',
|
|
473
|
+
execute: async (state, message) => {
|
|
474
|
+
const child = state.children.find(c => c.relationship === 'assistant_agent');
|
|
475
|
+
if (child) {
|
|
476
|
+
const thread = await state.getChildThread(child.reference);
|
|
477
|
+
await thread?.queueMessage({ role: 'user', content: message.content ?? '' });
|
|
478
|
+
}
|
|
479
|
+
},
|
|
480
|
+
});
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
```ts
|
|
484
|
+
// agents/agents/relay_agent.ts
|
|
485
|
+
import { defineAgent } from '@standardagents/spec';
|
|
486
|
+
|
|
487
|
+
export default defineAgent({
|
|
488
|
+
name: 'relay_agent',
|
|
489
|
+
sideA: {
|
|
490
|
+
handler: 'relay_pipe',
|
|
491
|
+
subagents: [{
|
|
492
|
+
agent: 'assistant_agent',
|
|
493
|
+
maxInstances: 1,
|
|
494
|
+
immediate: true, // spawned at thread creation, before any side takes a turn
|
|
495
|
+
parentCommunication: 'explicit',
|
|
496
|
+
}],
|
|
497
|
+
},
|
|
498
|
+
sideB: { human: true },
|
|
499
|
+
});
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
The child's answers arrive as inbound messages on side A — the relationship's owning side — and get their own handler turns, where the handler calls the project's outbound messaging tool. The handler forwards with `queueMessage` rather than a blocking spawn: a handler that awaits a blocking child session holds its thread's one-at-a-time delivery slot for the whole session. Note the handler side declares its child but needs no tool grants — the side's declaration is the grant, and the handler reaches children through `ThreadState` directly.
|
|
405
503
|
|
|
406
504
|
---
|
|
407
505
|
|
|
@@ -426,7 +524,7 @@ If you have multiple configured providers, pass `--provider=<name>` explicitly.
|
|
|
426
524
|
| Domain subagent (gmail, slack, etc.) | `fast_tool_calls` | High volume, narrow tool set, latency matters. |
|
|
427
525
|
| Eloquent text generation | `writing` | Use as a subprompt from a fast-tool-calling agent. |
|
|
428
526
|
| Image generation | `image_generation` | Use as a subprompt. |
|
|
429
|
-
| QA / reviewer
|
|
527
|
+
| QA / reviewer side B | `extra_reasoning` from a *different provider* than side A | Same-lab QA is biased. |
|
|
430
528
|
| Cheap classification, tagging, routing | `tiny` | Where speed and cost dominate quality. |
|
|
431
529
|
|
|
432
530
|
### Fallback strategy
|
|
@@ -594,14 +692,14 @@ By default, `runCode()` executes the `default` export with no args. Use `execute
|
|
|
594
692
|
|
|
595
693
|
### Notes on a few that are easy to misuse
|
|
596
694
|
|
|
597
|
-
- **`state.arguments`** is read-only invocation/creation data.
|
|
695
|
+
- **`state.arguments`** is read-only invocation/creation data. `subagent_create` arguments are validated against the receiving child's side A prompt `requiredSchema`, persist for the life of the child thread, are hydrated for both child sides on every activation, and are also available as prompt variables.
|
|
598
696
|
- **`state.context`** is in-memory scratch data for the *current execution*. It is not durable across thread restarts and should not be used for invocation or subagent creation arguments. For durable structured state, use `state.getValue` / `state.setValue`.
|
|
599
697
|
- **`state.getValue` / `state.setValue`** are durable per-thread JSON storage. Use them for small structured state; use files for larger content or artifacts.
|
|
600
698
|
- **`state.env` / `state.envType` / `state.setEnv`** are for runtime configuration and secrets. `state.env(name)` resolves thread -> account -> instance -> agent -> prompt. `state.envType(name)` returns `'secret'` by default; `'text'` means the value may be shown in tool output. `state.setEnv(name, value, { type: 'text' | 'secret' })` writes thread-scoped env and propagates to active descendants. Omit `type` only when preserving the existing type is intentional; new keys default to secret.
|
|
601
699
|
- **`state.runCode`** runs JavaScript or TypeScript in an isolated Dynamic Worker sandbox. The sandbox does not receive `ThreadState`, env, files, network, or host globals implicitly. Pass exact capabilities through `imports`, `globals`, `modules`, and `execute`. It executes exported values/functions; code such as `console.log(fibonacci(5))` can log, but it must still `export default ...` or export a named function/value for the host to receive a result.
|
|
602
700
|
- **`state.scheduleEffect`** runs a named effect after a delay. It survives restarts. This is your cron, your queue, and your retry timer all in one.
|
|
603
701
|
- **`state.invokeTool` vs `state.queueTool`** — `invokeTool` runs synchronously and returns the result; `queueTool` schedules the call to run later in the normal tool-call flow. Prefer `queueTool` when the model should see the result as a regular tool call.
|
|
604
|
-
- **`state.notifyParent`** — for
|
|
702
|
+
- **`state.notifyParent`** — for children whose relationship sets `parentCommunication: 'explicit'`, this is the main way the child talks to the parent. Use it sparingly; every delivered notification interrupts the parent. If there is no parent, nothing is queued externally.
|
|
605
703
|
- **`state.stopSession`** — terminally ends the active agent session. Use `state.stopSession({ result, notifyParent, status })` when a tool should return a blocking result, optionally notify the parent, and mark the child status without requiring a session tool call.
|
|
606
704
|
- **File attachments** use the path convention `/attachments/{filename}.{ext}`. Always use this path when passing files between agents — the runtime copies them across thread filesystems automatically.
|
|
607
705
|
|
|
@@ -629,11 +727,16 @@ Also tell it what **not** to do: do not read `process.env`, do not call `env()`
|
|
|
629
727
|
|
|
630
728
|
## Tools
|
|
631
729
|
|
|
632
|
-
A "tool" is anything
|
|
730
|
+
A "tool" is anything a prompt can call. There are three kinds of `tools` entries:
|
|
633
731
|
|
|
634
|
-
1. **Callables** — TypeScript functions defined via `defineTool` in `agents/tools/`. The primary way to interface with the outside world: APIs, databases, business logic
|
|
732
|
+
1. **Callables** — TypeScript functions defined via `defineTool` in `agents/tools/`. The primary way to interface with the outside world: APIs, databases, business logic. Each callable receives a `ThreadState`. Do not assume Node APIs are available — your code may run on the edge.
|
|
635
733
|
2. **Subprompts** — prompts exposed as tools via their `toolDescription`. A single-step LLM call. Use for switching models on a focused task (image generation, polished writing, JSON extraction).
|
|
636
|
-
3. **
|
|
734
|
+
3. **Handoff agents** — agents with `exposeAsTool: true` referenced directly by name. The call transfers control of the *same thread* to the target agent ([handoffs](#handoffs)).
|
|
735
|
+
|
|
736
|
+
Two things that are deliberately **not** tools:
|
|
737
|
+
|
|
738
|
+
- **Subagent relationships** are declared on a side's `subagents` array, never in a prompt's `tools` array — a `tools` entry attempting to configure one is a definition-time error. The prompt backing an owning side automatically receives the injected lifecycle tools (`subagents`, `subagent_create`, `subagent_message`) for that side's non-hidden relationships; their names are reserved.
|
|
739
|
+
- **Handlers** own a side's turns and may not be referenced by prompt `tools` arrays, hook lists, or effect schedules.
|
|
637
740
|
|
|
638
741
|
### Provider-visible argument schemas
|
|
639
742
|
|
|
@@ -661,8 +764,10 @@ PromptDefinition
|
|
|
661
764
|
toolChoice 'auto' | 'none' | 'required' tool calling strategy (default 'auto')
|
|
662
765
|
requiredSchema ZodSchema validate args when called as a tool
|
|
663
766
|
variables VariableDefinition[] declared text/secret variables
|
|
664
|
-
tools (string | SubpromptConfig | PromptToolConfig
|
|
665
|
-
tools available at this step
|
|
767
|
+
tools (string | SubpromptConfig | PromptToolConfig)[]
|
|
768
|
+
tools available at this step: callables,
|
|
769
|
+
subprompts, handoff agents — never subagent
|
|
770
|
+
relationships (those live on sides) or handlers
|
|
666
771
|
env Record<string, string> prompt-level env values (lowest precedence)
|
|
667
772
|
reasoning ReasoningConfig extended thinking config (for models that support it)
|
|
668
773
|
recentImageThreshold number (default 10) how many recent messages keep real images
|
|
@@ -717,97 +822,134 @@ Use `{ type: 'env', property: 'PRODUCT_INVENTORY' }` parts to inject runtime val
|
|
|
717
822
|
|
|
718
823
|
### `AgentDefinition` cheat sheet
|
|
719
824
|
|
|
720
|
-
The agent definition binds
|
|
825
|
+
The agent definition binds two backed sides together. Set on each file in `agents/agents/`. For full signatures, see `agents/agents/AGENTS.md`.
|
|
721
826
|
|
|
722
827
|
```
|
|
723
828
|
AgentDefinition
|
|
724
829
|
name string unique snake_case identifier
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
toolDescription string required if exposeAsTool: true — write it for the calling model
|
|
830
|
+
sideA SideConfig REQUIRED — HumanSide | PromptSide | HandlerSide
|
|
831
|
+
sideB SideConfig REQUIRED — HumanSide | PromptSide | HandlerSide
|
|
832
|
+
maxSessionTurns number optional on every shape; uniform exchange counting
|
|
833
|
+
across backings — required practice for prompt × prompt
|
|
834
|
+
title string human-readable label
|
|
731
835
|
description string brief human description for UIs — always set on new agents
|
|
732
836
|
icon string URL or inline SVG data URI — always set on new agents
|
|
837
|
+
exposeAsTool boolean (default false) composability contract: handoff target and default
|
|
838
|
+
description source when composed as a child
|
|
839
|
+
toolDescription string required if exposeAsTool: true — write it for the calling model
|
|
733
840
|
env Record<string, string> agent-level env values
|
|
734
841
|
hooks HookId[] agent-scoped hooks (when prompts don't specify their own)
|
|
842
|
+
parentHooks ParentHookRequirement[] required hooks auto-contributed to a direct parent
|
|
843
|
+
composition; legal on any agent with exposeAsTool: true
|
|
735
844
|
```
|
|
736
845
|
|
|
737
|
-
|
|
846
|
+
Each side is discriminated by its backing — exactly one of `human`, `prompt`, or `handler`:
|
|
738
847
|
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
848
|
+
```
|
|
849
|
+
HumanSide
|
|
850
|
+
human true required discriminator
|
|
851
|
+
label string role name shown in the UI and logs
|
|
852
|
+
|
|
853
|
+
PromptSide
|
|
854
|
+
prompt string the prompt this side runs (agents/prompts/<name>)
|
|
855
|
+
label string role name ("Researcher", "Reviewer") — always set on
|
|
856
|
+
prompt × prompt sides
|
|
857
|
+
subagents SubagentRelationship[] side-owned child relationships (see cheat sheet below)
|
|
858
|
+
stopOnResponse boolean (default true) stop side turn on text response without tool calls
|
|
859
|
+
stopTool string stop side turn when this tool is called
|
|
860
|
+
stopToolResponseProperty string extracted property for stop tool outcomes
|
|
861
|
+
maxSteps number per-side step cap within one turn
|
|
862
|
+
sessionStop { name, messageProperty?, attachmentsProperty? } ends session with a result
|
|
863
|
+
sessionFail { name, messageProperty?, attachmentsProperty? } ends session with a failure
|
|
864
|
+
sessionStatus { name, messageProperty?, attachmentsProperty? } publishes status without ending
|
|
865
|
+
|
|
866
|
+
HandlerSide
|
|
867
|
+
handler string the handler backing this side (agents/handlers/<name>)
|
|
868
|
+
label string role name shown in the UI and logs
|
|
869
|
+
subagents SubagentRelationship[] side-owned child relationships
|
|
870
|
+
```
|
|
745
871
|
|
|
746
|
-
|
|
872
|
+
Session bindings are **object form only** and are the bindings [Session boundary discipline](#session-boundary-discipline) refers to; every session tool must pass the [three-places check](#session-tools-must-exist-in-three-places). Handler sides get no `maxSteps`, stop bindings, or session bindings — a handler turn is a single invocation, and handlers end sessions via `state.stopSession()`.
|
|
747
873
|
|
|
748
874
|
Packaging fields (`packageName`, `version`, `author`, `license`) exist for the agent packing system; ignore them unless you're publishing.
|
|
749
875
|
|
|
750
|
-
|
|
876
|
+
Definition-time errors to know (old shapes fail to load with migration-pointing errors — there is no compatibility window): both sides human (reserved); `subagents` on a human side; a side with zero or multiple backings; an undefined prompt/handler reference; duplicate, colliding, or reserved relationship names; session bindings, `maxSteps`, or stop bindings on a non-prompt side; a non-object session binding.
|
|
751
877
|
|
|
752
|
-
|
|
878
|
+
### `SubagentRelationship` cheat sheet
|
|
879
|
+
|
|
880
|
+
This is where parent/child architecture is actually configured — it lives on the **owning side's `subagents` array** (prompt- or handler-backed; never a human side, never a prompt's `tools`). A relationship belongs to the side that declares it, and the side's declaration is the grant. Knowing every field exists is essential; full signatures live in the spec types (`node_modules/@standardagents/spec/dist/`, or `packages/spec/src/` on GitHub).
|
|
753
881
|
|
|
754
882
|
```
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
883
|
+
SubagentRelationship — entry in a side's `subagents` array
|
|
884
|
+
agent string agent definition to instantiate — ANY agent is
|
|
885
|
+
eligible; exposeAsTool: true marks it as designed
|
|
886
|
+
for composition and supplies the default description
|
|
887
|
+
as string relationship name — tool-surface name, registry
|
|
888
|
+
identity, reference name; defaults to `agent`.
|
|
889
|
+
Two relationships may target the same agent under
|
|
890
|
+
different `as` names (aliases), each with its own
|
|
891
|
+
config — e.g. work_calendar / personal_calendar
|
|
892
|
+
with different scopedEnv
|
|
893
|
+
description string model-facing override; falls back to the child's
|
|
894
|
+
toolDescription
|
|
895
|
+
maxInstances number cap on simultaneous instances (unset = unbounded);
|
|
896
|
+
at the cap, creation errors and suggests messaging
|
|
897
|
+
an existing instance
|
|
898
|
+
immediate bool | object spawn at THREAD CREATION, before any side takes a
|
|
899
|
+
turn; recursive through the child's own immediates.
|
|
759
900
|
object form: { nameEnv, descriptionEnv, scopedEnv }
|
|
760
|
-
scopedEnv
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
initAttachmentsProperty schema field name tool arg holding /attachments/* paths to copy to child
|
|
777
|
-
initAgentNameProperty schema field name tool arg used to tag the child thread (UI label)
|
|
778
|
-
optional string (env name) subagent disabled unless this env resolves truthy
|
|
779
|
-
parentCommunication 'implicit' | 'explicit' top-level form for NON-resumable subagents; explicit
|
|
780
|
-
means completion is not auto-queued to the parent
|
|
781
|
-
(so the parent is not woken). Use for background
|
|
782
|
-
children that deliver results via parent thread KV.
|
|
783
|
-
resumable.parentCommunication takes precedence.
|
|
901
|
+
— scopedEnv stays runtime-only; only nameEnv /
|
|
902
|
+
descriptionEnv may be model-visible at bootstrap
|
|
903
|
+
blocking boolean subagent_create awaits the child session's close and
|
|
904
|
+
returns its result. blocking + maxInstances: 1 with
|
|
905
|
+
an existing instance = create-or-continue
|
|
906
|
+
parentCommunication 'implicit' | 'explicit' implicit (default) auto-queues completion/failure to
|
|
907
|
+
the parent on the owning side; explicit means the
|
|
908
|
+
child's finish does NOT wake the parent — escalation
|
|
909
|
+
is deliberate (notifyParent / setStatus /
|
|
910
|
+
stopSession), results delivered via e.g. parent KV
|
|
911
|
+
scopedEnv string[] env values copied into the child thread; runtime-only
|
|
912
|
+
initUserMessageProperty schema field name creation arg used as child's first user message
|
|
913
|
+
initAttachmentsProperty schema field name creation arg holding /attachments/* paths to copy
|
|
914
|
+
initAgentNameProperty schema field name creation arg used to tag the child thread (UI label)
|
|
915
|
+
optional string (env name) relationship enabled only when this env flag resolves
|
|
916
|
+
truthy (true/1/yes)
|
|
784
917
|
hidden boolean relationship stays declared and spawnable by runtime
|
|
785
|
-
code (invokeTool/queueTool from hooks), but
|
|
786
|
-
|
|
918
|
+
code (invokeTool/queueTool from hooks), but is
|
|
919
|
+
excluded from every LLM request — the model can
|
|
787
920
|
never call it (infrastructure children, e.g. a
|
|
788
921
|
background compaction agent)
|
|
789
922
|
```
|
|
790
923
|
|
|
791
|
-
|
|
924
|
+
Rules that follow from ownership:
|
|
925
|
+
|
|
926
|
+
- **The owning side is where child traffic lands.** `notifyParent` content, implicit completion/failure messages, and status traffic from the relationship's instances are delivered on the owning side's perspective — queued in the role that prompts the owning side's next turn. Declare the relationship on the side whose turns should react to the child.
|
|
927
|
+
- **A relationship name may appear on at most one side**, must not collide with any tool name, and must not use the reserved lifecycle names `subagents`, `subagent_create`, `subagent_message`. All violations are definition-time errors.
|
|
928
|
+
- **Prompt-backed owning sides get the lifecycle tools auto-injected** for their non-hidden relationships: `subagents` (pull-based registry snapshot — call it before creating so existing instances are reused), `subagent_create` (requires a non-empty instance `name`; its `agent` argument enumerates **relationship names, not agent names**), and `subagent_message` (queue a message to an existing instance by reference).
|
|
929
|
+
- **Handler-backed owning sides need no tool grant** — the declaration establishes existence and lifecycle, and the handler reaches children through `ThreadState` directly.
|
|
930
|
+
|
|
931
|
+
`subagent_create` arguments are shaped by the `requiredSchema` of the receiving child's **side A prompt** (parent traffic enters the child in the side B perspective, `role: 'user'`, so side A acts on it). The runtime validates and persists those values for the life of the child thread, hydrates them into `ThreadState.arguments` and prompt variables for both child sides on every activation, and rejects creation with a structured error when required arguments are missing.
|
|
792
932
|
|
|
793
|
-
|
|
933
|
+
Tool-config forms still exist in prompt `tools` for plain callables (`{ name, env, options }`) and subprompts (`{ name, includeTextResponse, includeToolCalls, includeErrors, initUserMessageProperty, initAttachmentsProperty }`). Both are documented in full at `agents/prompts/AGENTS.md`.
|
|
794
934
|
|
|
795
935
|
### Inter-agent communication rules
|
|
796
936
|
|
|
797
937
|
These are correct as written in the spec — internalize them.
|
|
798
938
|
|
|
799
939
|
1. **Parents always create children.**
|
|
800
|
-
- Explicitly via the built-in `subagent_create` tool, which requires a non-empty `name` for the child instance
|
|
801
|
-
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
940
|
+
- Explicitly via the built-in `subagent_create` tool, which requires a non-empty `name` for the child instance and whose `agent` argument enumerates **relationship names, not agent names**.
|
|
941
|
+
- From runtime code (`invokeTool` / `queueTool` on the lifecycle surface) in tools, hooks, and handlers.
|
|
942
|
+
- Implicitly via `immediate` — the child spawns at **thread creation**, before any side takes a turn (recursive through the child's own immediate relationships; works even when the owning side is a handler with no prompt to activate).
|
|
943
|
+
2. **Routing is structural in both directions.** Parent→child traffic enters the child in the **side B perspective** (`role: 'user'`), so the child's side A acts on it exactly as for an external caller. Child→parent delivery — `notifyParent` content, implicit completion/failure, status traffic — lands on the **relationship's owning side's** perspective, queued in the role that prompts that side's next turn. The runtime routes inbound; userland decides outbound.
|
|
944
|
+
3. **Child completion has two flavors**, chosen by `parentCommunication`:
|
|
945
|
+
- **Implicit** (default): the runtime auto-queues a silent completion/failure message to the parent when the child session ends (via `sessionStop`, `sessionFail`, or a turn cap), which triggers a parent turn.
|
|
946
|
+
- **Explicit**: the child's finish does **not** wake the parent at all. Escalation is deliberate — `state.notifyParent()`, `state.setStatus()`, or `state.stopSession({ result, notifyParent, status })` — and results are delivered through another channel (e.g., the parent's thread KV), which the parent picks up on its next natural turn. Use for high-traffic children (a Slack monitor) where you control when the parent is interrupted, or background workers (a compaction agent) whose completion should not wake the parent.
|
|
947
|
+
4. **Every child stays messageable.** Persistence is universal: session end never terminates the thread, so `subagent_message` (or `queueMessage` from code) can always continue an existing instance with history intact — until explicit `terminate()`.
|
|
948
|
+
5. **When a child sends back to the parent, it MUST indicate whether it needs a response** — in plain English, e.g. "After researching, you must respond so I can continue with the email." A research subagent providing FYI info doesn't need a response; a gmail subagent waiting on guidance does.
|
|
949
|
+
6. **File attachments use path strings.** `/attachments/{filename}.{ext}`. Use this convention everywhere — the runtime copies attachments across thread filesystems automatically when listed in `initAttachmentsProperty`, and rewrites them to destination-local paths.
|
|
950
|
+
7. **Cross-thread traffic arrives as "silent" messages with provenance metadata.** The runtime sets `metadata.subagent_id` (which child instance a message relates to) and `metadata.forwarded_from` (origin of forwarded content) — use these conventions instead of ad-hoc bracket-tag prefixes in relay code. The receiving side decides whether to respond or just absorb the information.
|
|
951
|
+
8. **Messages to busy agents are queued** and delivered in order when the agent is free.
|
|
952
|
+
9. **Prompt language must distinguish the lifecycle tools.** When writing prompts for sides that own relationships, explicitly tell the model: *"Call `subagents` to see existing instances. To research a new topic, use `subagent_create`. To follow up on a topic an existing instance is already researching, use `subagent_message`."* Without this guidance, models will pick wrong and duplicate children.
|
|
811
953
|
|
|
812
954
|
---
|
|
813
955
|
|
|
@@ -936,7 +1078,7 @@ All three are gated by the `logs` permission on hosted deployments.
|
|
|
936
1078
|
|
|
937
1079
|
## Implementation checking
|
|
938
1080
|
|
|
939
|
-
When you edit `agents/`, `prompts/`, `models/`, `tools/`, `hooks/`, or `worker/`, validate before claiming the change is done.
|
|
1081
|
+
When you edit `agents/`, `prompts/`, `handlers/`, `models/`, `tools/`, `hooks/`, or `worker/`, validate before claiming the change is done.
|
|
940
1082
|
|
|
941
1083
|
Validation order:
|
|
942
1084
|
|