> ## Documentation Index
> Fetch the complete documentation index at: https://docs.omnara.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Approvals & questions

> Respond when an agent needs approval or an answer

Interactions pause an agent when it needs a person to approve an action or answer a question. The agent resumes when someone responds; if the current work is canceled, the interaction is canceled too. There are two kinds:

| `interaction_kind` | Created when | Typical form |
| - | - | - |
| `permission` | A tool with `always_ask` [permission](/tools/permissions) is called | "Permission requested for run\_command" with Allow/Deny |
| `question` | The agent uses the `ask_question` built-in tool | Whatever the agent wants to know, with options it composed |

Both ride on the same tool-call machinery: the parent tool call waits while the interaction is `open`. An interaction ends in one of two states — `resolved` (someone answered) or `canceled` (the work it guarded was canceled, superseded, or archived).

```bash theme={null}
export BASE="$OMNARA_API/orgs/$ORG/projects/$PROJ"
export AGENT="agt_v4qtrwz3jehcyd5n6a2bfgik7m"
```

## Find open interactions

**API**

The examples assume the client, `$ORG`/`orgID`, and `$PROJ`/`projectID` setup from the [quickstart](/quickstart), plus the `$AGENT`/`agentID` value above. The CLI has no interactions commands — respond from the dashboard or the API.

```bash REST theme={null}
curl "$BASE/agents/$AGENT/interactions?state=open" \
  -H "Authorization: Bearer $OMNARA_TOKEN"
```

```typescript SDK theme={null}
const interactions = await sdk.listAgentInteractions({
  client,
  path: { orgID, projectID, agentID },
  query: { state: 'open' },
})
console.log(interactions.data)
```

```json theme={null}
{
  "data": [
    {
      "id": "int_rwz3jehcyd5n6a2bfgik7mv4qt",
      "org_id": "org_k7mv4qtrwz3jehcyd5n6a2bfgi",
      "project_id": "proj_7mv4qtrwz3jehcyd5n6a2bfgik",
      "agent_id": "agt_v4qtrwz3jehcyd5n6a2bfgik7m",
      "interaction_kind": "permission",
      "state": "open",
      "request": {
        "title": "Permission requested for run_command",
        "context": [
          { "label": "Command", "value": "rm -rf ./build && npm run build" },
          { "label": "Machine", "value": "mch_aaaaaaaaaaaaaaaaaaaaaaaaae" },
          { "label": "Shell", "value": "bash" }
        ],
        "questions": [
          {
            "prompt": "Allow this tool call?",
            "options": [
              { "label": "Allow" },
              { "label": "Deny", "allows_text": true }
            ]
          }
        ]
      },
      "created_at": "2026-08-03T19:13:20Z"
    }
  ],
  "next_cursor": null
}
```

Filter by `state` (`open`, `resolved`, `canceled`) or omit it for everything; results are cursor-paginated, oldest first. Open interactions announce themselves through `tool_call_update` frames on the [event stream](/events/streaming) and [configured webhooks](/events/webhooks); use this endpoint to fetch the form to render.

> [!NOTE]
>
> Full schema and playground: [List agent interactions](/api-reference/endpoints/interactions/list-agent-interactions).

**Dashboard**

Open interactions appear as cards at the bottom of the agent's conversation. A permission card is titled **Permission requested for** followed by the tool name; a question card is titled with the agent's own wording. Each shows the form's context and options ready to answer. Nothing to poll or refresh — they show up live.

## Render the form

Every interaction's `request` is the same small form structure, designed to render without special-casing:

* `title` — one line saying what's being asked.
* `context` — optional label/value pairs to display verbatim (the command about to run, the machine it targets). Show all of them; they're the approver's evidence.
* `questions` — one or more, in order. Each has a `prompt` and at least one option. `multiple: true` means the answer may select several options; an option with `allows_text: true` invites free text alongside the selection (in permission forms, that's the "Deny" option — the text becomes the reason the model sees).

Permission forms always have exactly one question with `Allow` at index 0 and `Deny` at index 1. Question forms are composed by the agent, so treat them generically.

## Resolve it

**API**

Answer with one entry per question, in order. `option_indices` are zero-based positions into that question's `options`. The response is the interaction in its final state:

```bash REST theme={null}
curl "$BASE/agents/$AGENT/interactions/int_rwz3jehcyd5n6a2bfgik7mv4qt/resolve" \
  -H "Authorization: Bearer $OMNARA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "answers": [ { "option_indices": [0] } ] }'
```

```typescript SDK theme={null}
const resolved = await sdk.resolveAgentInteraction({
  client,
  path: { orgID, projectID, agentID, interactionID: 'int_rwz3jehcyd5n6a2bfgik7mv4qt' },
  body: { answers: [{ option_indices: [0] }] },
})
console.log(resolved.data)
```

```json theme={null}
{
  "id": "int_rwz3jehcyd5n6a2bfgik7mv4qt",
  "interaction_kind": "permission",
  "state": "resolved",
  "request": { "title": "Permission requested for run_command", "...": "..." },
  "resolution": { "answers": [ { "option_indices": [0] } ] },
  "resolved_by_input_id": "ain_fgik7mv4qtrwz3jehcyd5n6a2b",
  "resolved_at": "2026-08-03T19:14:01Z",
  "...": "..."
}
```

To deny with a reason, select the Deny option and attach text:

```bash REST theme={null}
curl "$BASE/agents/$AGENT/interactions/int_rwz3jehcyd5n6a2bfgik7mv4qt/resolve" \
  -H "Authorization: Bearer $OMNARA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "answers": [ { "option_indices": [1], "text": "Not on the build server — use a pool machine." } ] }'
```

```typescript SDK theme={null}
await sdk.resolveAgentInteraction({
  client,
  path: { orgID, projectID, agentID, interactionID: 'int_rwz3jehcyd5n6a2bfgik7mv4qt' },
  body: {
    answers: [{ option_indices: [1], text: 'Not on the build server — use a pool machine.' }],
  },
})
```

**Dashboard**

A permission card answers in one click: click **Allow** or **Deny** and the agent resumes immediately. To explain a denial, click **Add a reason** first, type in the **Reason for denying (optional)** field, then click **Deny** or press Enter; the model sees that text as the reason.

On a question, click the agent's options. Where a selected option invites free text, a **Details (optional)** field appears next to the options. Click **Submit**, or press Enter in the text field, and the agent resumes immediately.

Two operational notes:

* **Resolution is first-writer-wins.** Re-sending the *same* answers replays idempotently — `200` with the already-resolved interaction. Sending *different* answers, or resolving a canceled interaction, returns `409` with code `idempotency_key_conflict`. Whoever answers first, wins; build your approval UI to handle the race gracefully.
* **Attribution works like inputs.** Services relaying human decisions pass `actor` alongside `answers` so `resolved_by_input_id` traces to the real person — see [actors](/events/sending-input#who-said-that-actors-and-attribution).

> [!NOTE]
>
> Full schema and playground: [Resolve agent interaction](/api-reference/endpoints/interactions/resolve-agent-interaction).

## When interactions cancel

You never delete an interaction; it cancels when the work it guarded goes away:

* Sending input with [`cancel_open_interactions: true`](/events/sending-input#queued-vs-steering) — your new message supersedes the pending prompts; their tool calls complete as `canceled`.
* [Stopping the agent](/agents/overview#stop-current-work) or [archiving it](/agents/overview#archive-an-agent) — everything in flight, interactions included, is canceled.

Canceled interactions keep their `request` and stay listable, so audit trails show what was asked even when nobody answered.

## Present interactions in your own app

Your application decides where to display questions and approvals. Read the
interaction's `request`, present the form, and resolve it through the ordinary
project-authorized API. The dashboard remains available if delivery fails.
Reconcile the interaction list on reconnect and periodically while connected;
stream hints alone are insufficient.

For an interaction routed to an integration handler, list and resolve responses include
its captured `destination`. Its `integration_id` and `integration_target_id`
are public IDs; `handler_key` identifies the handler and `conversation` contains
the provider fields, using the same format as integration subscriptions.
This snapshot stays fixed when the agent's current
destination changes. Optional `presentation_receipt` contains recorded provider
message metadata. These fields describe integration delivery; your application can
present interactions without them. See [Integrations](/integrations/overview) for handler
configuration and integration-specific behavior, or
[Custom integrations](/integrations/custom-integrations#present-and-resolve-interactions)
for presenting interactions through your own service.

## Next

> **[Artifacts](/events/artifacts)**
>
> Download the files agents produce and reference

> **[Agent configuration](/agents/configuration)**
>
> Decide which tools require approval in the first place


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.