Agents
Define two-sided agents, including subagent lifecycle behavior
What Are Agents?
Agents define conversation orchestration:
An agent has two explicit sides. Each side is backed by a human, a prompt, or a handler.
- A prompt-backed side and a human-backed side form a chat agent. The prompt side responds to the person's messages.
- Two prompt-backed sides alternate until a stop condition is met.
Agents reference prompts, define stop semantics, and can be exposed as callable tools.
Basic Example
This chat agent has a prompt on side A and a person on side B. The diagram is drawn from the three files under it.
import { defineAgent } from '@standardagents/spec';
export default defineAgent({
name: 'assistant',
sideA: {
prompt: 'assistant',
stopOnResponse: true,
},
sideB: { human: true },
});import { definePrompt } from '@standardagents/spec';
export default definePrompt({
name: 'assistant',
toolDescription: 'General-purpose assistant',
model: 'conversational',
prompt: 'You are a helpful assistant. Be concise and accurate.',
includeChat: true,
});import { defineModel } from '@standardagents/spec';
import { openrouter } from '@standardagents/openrouter';
export default defineModel({
name: 'conversational',
provider: openrouter,
model: 'anthropic/claude-sonnet-5.5',
});Side Configuration
Both sideA and sideB are required for every agent. Both sides cannot be human-backed. Each side declares exactly one backing:
{ human: true }: a person takes this side's turns{ prompt: 'name' }: a model takes this side's turns{ handler: 'name' }: a handler function takes this side's turns
Prompt-backed side fields:
promptlabelsubagentsstopOnResponsestopToolstopToolResponsePropertymaxSteps
stopOnResponse defaults to true. stopTool ends the current side's turn. maxSteps caps model requests within that turn.
Session lifecycle fields:
sessionStopsessionFailsessionStatus
These fields apply to prompt-backed sides. sessionStop and sessionFail close a session. The thread remains available for later messages. sessionStatus updates the child's status in its parent registry.
sessionStop, sessionFail, and sessionStatus require object bindings. Use { name: 'tool_name' }. Add messageProperty or attachmentsProperty when the lifecycle tool supplies those payloads. For a composed child, the mapped payload is the result or failure that the parent receives.
Exposing Agents as Tools
defineAgent({
name: 'billing_specialist',
exposeAsTool: true,
toolDescription: 'Handle billing and payment escalations.',
sideA: {
prompt: 'billing_prompt',
stopOnResponse: true,
},
sideB: { human: true },
});A direct agent reference in a prompt's tools array performs a same-thread handoff when the target has a human side matching the thread's conversational surface. Declare a side-owned subagent relationship to instantiate an isolated child thread.
Agents as subagents
Any agent can run as a child thread. The parent agent declares the relationship in the subagents array of the side that owns it:
defineAgent({
name: 'orchestrator',
sideA: {
prompt: 'orchestrator_prompt',
subagents: [{ agent: 'asset_subagent', blocking: false }],
},
sideB: { human: true },
});Each child thread has:
- isolated message history/logs/filesystem
- parent/child linkage and child registry
- blocking/non-blocking creation
- a persistent thread that accepts later messages after its session ends
The runtime injects the lifecycle tools (subagents, subagent_create, subagent_message) for a prompt-backed owning side. They are not user-defined files in agents/tools.
The child's side A prompt requiredSchema defines subagent_create.arguments. The validated arguments persist with the child thread as ThreadState.arguments. Both child sides can read them and use their top-level keys as prompt variables.
Example: Worker + Reviewer
defineAgent({
name: 'asset_subagent',
maxSessionTurns: 40,
exposeAsTool: true,
toolDescription: 'Generate and QA top-down assets.',
sideA: {
label: 'Worker',
prompt: 'asset_worker',
stopOnResponse: true,
sessionFail: {
name: 'fail_asset',
messageProperty: 'summary',
attachmentsProperty: 'attachments',
},
},
sideB: {
label: 'Reviewer',
prompt: 'asset_reviewer',
stopOnResponse: false,
sessionStop: {
name: 'approve_asset',
messageProperty: 'summary',
attachmentsProperty: 'attachments',
},
sessionStatus: {
name: 'asset_status',
messageProperty: 'status',
},
},
});Best Practices
- Set
maxSessionTurnswhen both sides are prompt-backed - Use
sessionStop/sessionFailfor explicit terminal outcomes - Map
messagePropertyandattachmentsPropertywhen the parent needs the payload - Keep side prompts specialized (
side_aproduction,side_bQA)