# 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.

Diagram: The 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.

```ts agents/agents/assistant.ts
import { defineAgent } from '@standardagents/spec';

export default defineAgent({
  name: 'assistant',
  sideA: {
    prompt: 'assistant',
    stopOnResponse: true,
  },
  sideB: { human: true },
});
```

```ts 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,
});
```

```ts agents/models/conversational.ts
import { defineModel } from '@standardagents/spec';
import { openrouter } from '@standardagents/openrouter';

export default defineModel({
  name: 'conversational',
  provider: openrouter,
  model: 'google/gemma-4-31b-it',
});
```

## 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](/docs/core-concepts/handlers) 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

```typescript
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:

```typescript
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

```typescript
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)

## Related Docs

- [Subagents](/docs/core-concepts/subagents)
- [Prompts](/docs/core-concepts/prompts)
- [defineAgent](/docs/api-reference/define/agent)
