Agent Outbox gives software agents a durable place to ask a person for a decision. Your agent sends a structured review request, continues other work, and returns for the answer when it is ready. No request stays open while a person thinks.
This guide sends an approval request, checks for a decision, reads it, and acknowledges that your application handled it safely.
Before you start#
You need a connected caller. A caller is one agent or service identity with its own display-once API credential.
Install the public CLI with Homebrew:
brew install --cask conn-castle/tap/agent-outbox
Or install the latest macOS or Linux release directly without Homebrew or
sudo:
curl -fsSL https://agent-outbox.dev/install.sh | sh
Connect a caller:
agent-outbox caller connect my-agent
my-agent is a local label you choose. The connection flow opens Agent Outbox
and asks a signed-in person to approve the caller. It then displays the
credential once and stores it in the CLI's owner-only local credentials file.
If you already have a connected caller, the raw HTTP examples below are fully
usable: substitute its display-once value where <caller_api_key> appears.
Never put the key in source control, request bodies, logs, or prompts.
1. Send a review request#
Give the work item an id that stays stable across retries. Action value fields
are also caller-owned protocol values: your code receives them back unchanged
when a person decides.
curl https://app.agent-outbox.dev/api/input/send \
--request POST \
--header "Authorization: Bearer <caller_api_key>" \
--header "Content-Type: application/json" \
--data '{
"caller_item_id": "email:thread_123",
"priority": "high",
"row_type": { "display": "Email draft", "icon": "mail" },
"title": "Reply to Acme Corp",
"subtitle": "A customer response is ready for review.",
"summary": "Approve the prepared response before it is sent.",
"link_buttons": [],
"actions": [
{
"display": "Approve to send",
"icon": "send",
"value": "approve_send",
"overflow": false,
"tone": "success",
"style": "solid",
"popup": { "kind": "none" }
}
]
}'
The server derives the account and caller from the bearer credential. Never put
account_id or caller_id in an input body.
An accepted response confirms whether this call created the item or repeated an equivalent pending request:
{
"ok": true,
"request_id": "req_123",
"correlation_id": "corr_123",
"data": {
"caller_item_id": "email:thread_123",
"status": "pending",
"revision": 1,
"created": true,
"duplicate": false
}
}
2. Continue other work#
The human review happens asynchronously. Persist enough caller-side state to
associate caller_item_id with the work that should resume. Do not keep a
network request, process, or model turn open while waiting.
3. Check for a decision#
Check readiness when your caller is prepared to resume:
curl "https://app.agent-outbox.dev/api/output/check?limit=25" \
--header "Authorization: Bearer <caller_api_key>" \
--header "Accept: application/json"
Checking is deliberately non-mutating. It returns result ids and timing, but no answer content, and it does not disable the person’s ability to undo.
{
"ok": true,
"request_id": "req_124",
"correlation_id": "corr_124",
"data": {
"items": [
{
"output_result_id": "out_123",
"caller_item_id": "email:thread_123",
"answered_at": "2026-06-30T20:00:00Z"
}
],
"ready_count": 1,
"has_more": false,
"next_cursor": null,
"returned_count": 1,
"page_limit": 25
}
}
Follow the opaque next_cursor while has_more is true. ready_count is the
total number of live results awaiting acknowledgement, including results you
already read; it is not an unread count.
4. Read the decision#
curl https://app.agent-outbox.dev/api/output/out_123/read \
--request POST \
--header "Authorization: Bearer <caller_api_key>" \
--header "Accept: application/json"
The result includes the selected action’s stable value, a response shaped by
its popup kind, and raw_input: the canonical accepted submission Agent Outbox
stored for that live item. The first successful read disables human undo. The
result remains available, and can be delivered again, until you acknowledge it.
{
"ok": true,
"request_id": "req_125",
"correlation_id": "corr_125",
"data": {
"output_result_id": "out_123",
"caller_id": "caller_123",
"caller_item_id": "email:thread_123",
"action_value": "approve_send",
"response": { "kind": "none" },
"answered_at": "2026-06-30T20:00:00Z",
"answered_by": "user_123",
"raw_input": {
"caller_item_id": "email:thread_123",
"priority": "high",
"row_type": { "display": "Email draft", "icon": "mail" },
"row_accent_color": null,
"title": "Reply to Acme Corp",
"subtitle": "A customer response is ready for review.",
"corner": null,
"summary": "Approve the prepared response before it is sent.",
"details": null,
"link_buttons": [],
"card_visual": null,
"skip_disabled": false,
"actions": [
{
"display": "Approve to send",
"icon": "send",
"value": "approve_send",
"overflow": false,
"tone": "success",
"style": "solid",
"popup": { "kind": "none" }
}
]
}
}
}
Treat output_result_id as the idempotency key for downstream work.
5. Acknowledge durable handling#
Only acknowledge after your application has durably recorded the decision or completed its side effect.
curl https://app.agent-outbox.dev/api/output/out_123/ack \
--request POST \
--header "Authorization: Bearer <caller_api_key>" \
--header "Accept: application/json"
Acknowledgement is idempotent. It removes the live input/output pair and any attached response files.
If a request fails, branch on the stable error.code, not its message or status
alone. The
reliability guide
explains recovery, and the generated reference lists every public code.
Where to go next#
- Understand the lifecycle before designing retries or worker state.
- Choose a review interaction for free text, selections, dates, files, and visual context.
- Build a UI integration with generated types and a trusted credential boundary.
- Build for reliable delivery before shipping a production integration.
- Use the generated API reference for exact routes, schemas, response shapes, and error recovery.