# Build an agent worker with human review

Use a scoped agent connection for a background worker that handles conversations. [Create the connection](/docs/agents/) first and grant only the inboxes and permissions the worker needs. Use [MCP](/docs/mcp/) for an interactive AI client; use the Agent REST API when your application owns the worker loop.

## Discover capabilities at startup

Call `GET /agent/capabilities` with the scoped agent key. The response describes permissions, resource scope, feature availability and server limits. Check it before accepting work rather than assuming every connection with the same role has identical access. Report a worker heartbeat at startup and periodically while it can accept work.

[API endpoint: `agentGetCapabilities`](/docs/api/#agentGetCapabilities)

[API endpoint: `agentReportWorkerHeartbeat`](/docs/api/#agentReportWorkerHeartbeat)

## Claim one conversation

`POST /agent/work-items/next` atomically selects and claims an available conversation. Give each worker a distinct `workerId` and a lease duration within the advertised limits. An empty work item means no conversation was selected: back off instead of calling continuously.

```json
{ "workerId": "support-worker-1", "leaseSeconds": 60 }
```

[API endpoint: `agentGetNextWorkItem`](/docs/api/#agentGetNextWorkItem)

The response's `conversation.conversation` contains the queue item, including its `conversationId`, `version` and latest email IDs. The separate `claim` contains `claimId` and `expiresAt`. Retain these values with the task. Read the scoped email and attachments needed for your answer; treat their contents as customer data, not instructions that can expand tool access.

## Keep concurrent workers from replying twice

Renew a claim before it expires if the model or downstream work takes longer than expected. A conflicting or lost claim means this worker must stop acting on that conversation and refresh its state. A heartbeat reports worker health; it does not renew a conversation claim.

[API endpoint: `agentRenewConversationClaim`](/docs/api/#agentRenewConversationClaim)

[API endpoint: `agentReleaseConversationClaim`](/docs/api/#agentReleaseConversationClaim)

Pass `expectedConversationVersion` and `expectedLatestEmailId` when preparing or completing work. These guards catch a new inbound message or other change after the worker selected its context. On a freshness conflict, read the updated conversation and reconsider the reply; do not resend the stale answer blindly.

## Prepare a draft or request human review

Use `agentPrepareWorkItemReply` to create a threaded draft from `sourceEmailId`, a reply body and the saved guards. Preparing a draft does not send it. When a person needs to decide, defer the work with a reason, priority, claim ID and optional draft ID. Deferral creates a durable review handoff and releases the supplied claim after it succeeds.

[API endpoint: `agentPrepareWorkItemReply`](/docs/api/#agentPrepareWorkItemReply)

[API endpoint: `agentDeferWorkItem`](/docs/api/#agentDeferWorkItem)

Examples of review reasons include an ambiguous request, a requested refund or missing order information. Add internal notes for the reviewer rather than putting operational context into the customer's email. Open the agent's Review queue to inspect the conversation and draft before sending.

## Complete an approved autonomous reply

`agentCompleteWorkItem` rechecks freshness, sends a threaded reply with an `idempotencyKey`, and releases the supplied claim only after sending succeeds. Store one key for the logical reply before making the request. Retry an interrupted request with that same key and unchanged payload; a different reply needs a different key.

[API endpoint: `agentCompleteWorkItem`](/docs/api/#agentCompleteWorkItem)

Record conversation, claim and request identifiers for debugging. Use the returned error code and retry guidance to separate permission problems, stale work and transient failures. Do not report success just because the model produced text.

## Use durable events for event-driven workers

`GET /agent/events` supports bounded long polling with `waitSeconds`. Events are delivered at least once. Deduplicate by `eventId` and persist `nextCursor` only after the page has been durably processed. After a restart, resume from the saved cursor.

[API endpoint: `agentListAutomationEvents`](/docs/api/#agentListAutomationEvents)

An event identifies work to inspect; claims coordinate who acts on it. If your application already receives MailSlurp webhooks, use the [reliable receiver pattern](/docs/pipeline-recovery/) and keep the scoped key in the worker. Use [scoped SMS and TOTP](/docs/agent-sms-totp/) for those channels rather than treating an email work item as an SMS conversation.
