# Agent Workflows

Compose agents through PURISTA boundaries or wrap harness workflow definitions.

---
Canonical: /handbook/blocks/agent-pattern/agent-workflows/
Source: web/src/content/handbook-cards/blocks/agent-pattern/agent-workflows.mdx
Format: Markdown for agents
---

Agent workflows can be modeled at two levels:

| Level | Use when |
|---|---|
| **PURISTA level** | Each agent needs its own queue, retry policy, model binding, sandbox, ownership boundary, or deployment surface. |
| **Harness level** | Several reasoning steps should run inside one `@purista/harness` workflow definition with shared session and sandbox semantics. |

Prefer PURISTA-level orchestration when the workflow crosses business boundaries. Use harness-level workflow definitions for tightly coupled inner reasoning steps.

## PURISTA-level orchestration

Use `canInvokeAgent(...)` to allow one agent to call another attached PURISTA agent:

```typescript [reviewCoordinatorAgentBuilder.ts]
export const reviewCoordinatorAgentBuilder = projectV1ServiceBuilder
  .getAgentQueueBuilder('reviewCoordinator', 'Coordinates project review agents')
  .addPayloadSchema(reviewInputSchema)
  .addOutputSchema(reviewOutputSchema)
  .addModel('primary', {
    model: 'gpt-4.1-mini',
    capabilities: ['object'],
  })
  .canInvokeAgent('requirementsReview', '1', {
    payloadSchema: reviewInputSchema,
    outputSchema: reviewFindingSchema,
  })
  .canInvokeAgent('securityReview', '1', {
    payloadSchema: reviewInputSchema,
    outputSchema: reviewFindingSchema,
  })
  .setRunFunction(async context => {
    const [requirements, security] = await Promise.all([
      context.invoke.agents['requirementsReview.1'].run(context.payload),
      context.invoke.agents['securityReview.1'].run(context.payload),
    ])

    return reviewOutputSchema.parse({
      findings: [...requirements.findings, ...security.findings],
    })
  })
```

Each child agent keeps its own generated queue, worker, command, stream, execution policy, and runtime binding.

## Harness-level workflows

Use `setHarnessWorkflow(...)` only when you already have a workflow definition from `@purista/harness` and want to expose it as one PURISTA agent:

```typescript [incidentWorkflowAgentBuilder.ts]
import { incidentReviewWorkflow } from './incidentReviewWorkflow.js'

export const incidentWorkflowAgentBuilder = incidentV1ServiceBuilder
  .getAgentQueueBuilder('incidentReview', 'Reviews incident evidence')
  .addPayloadSchema(incidentReviewWorkflow.input)
  .addOutputSchema(incidentReviewWorkflow.output)
  .addModel('primary', {
    model: 'gpt-4.1-mini',
    capabilities: ['object', 'tool_use'],
  })
  .setHarnessWorkflow(incidentReviewWorkflow)
```

In this shape, PURISTA owns the outer queue, command, stream, HTTP exposure, and runtime wiring. The harness workflow owns the inner reasoning loop.

## Streaming workflow output

The HTTP response shape is still selected with `streamingMode`:

```typescript
.exposeAsHttpEndpoint('POST', 'agents/incident-review', {
  streamingMode: 'stream',
})
```

Use `aggregate` for a final JSON response and `stream` for server-sent events while the workflow runs.
