Skip to content

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.

Agent diagram: assistantThe agent assistant has two sides. Side B is a human. Side A runs the assistant prompt on the conversational model from OpenRouter. Its turn stops when it sends a text reply.SIDE BHumanSIDE Amessagereplystops on responseAGENTassistantOpenRouterassistantconversational
agents/agents/assistant.ts
import { defineAgent } from '@standardagents/spec';

export default defineAgent({
  name: 'assistant',
  sideA: {
    prompt: 'assistant',
    stopOnResponse: true,
  },
  sideB: { human: true },
});
agents/prompts/assistant.ts
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,
});
agents/models/conversational.ts
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:

  • prompt
  • label
  • subagents
  • stopOnResponse
  • stopTool
  • stopToolResponseProperty
  • maxSteps

stopOnResponse defaults to true. stopTool ends the current side's turn. maxSteps caps model requests within that turn.

Session lifecycle fields:

  • sessionStop
  • sessionFail
  • sessionStatus

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 maxSessionTurns when both sides are prompt-backed
  • Use sessionStop / sessionFail for explicit terminal outcomes
  • Map messageProperty and attachmentsProperty when the parent needs the payload
  • Keep side prompts specialized (side_a production, side_b QA)