# Outbound actions

# Outbound actions

Consumer-facing guide to `actions.invoke` and the action catalog — the surface an agent (or an API caller) uses to make something leave the workspace: an email to teammates, a push into your Notion. For the agent-session model see [Agents](agents.md); for delegation see [Delegating to sub-agents](agents/delegate.md).

## What an action is

An **action** is an outbound connector fired deliberately. Reads and analysis never need one; actions exist for the moments an agent's work should reach a person or an external system. Two ship today:

| Action         | What it does                                                                             | Availability                                                                      |
| -------------- | ---------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
| `email_member` | Email one or more **current workspace members**. External addresses are always rejected. | Always available.                                                                 |
| `notion_sync`  | Push one knowledge-base document group into your connected Notion workspace **now**.     | Only when an outbound Notion sync connection is configured, enabled, provisioned. |

The ambient Notion mirror is unaffected: knowledge-base writes and promotions keep syncing to Notion on their own whenever the connection is configured. The `notion_sync` action is the **explicit** push — an on-demand sync, a backfill, or bringing back a document that was previously removed from Notion.

## The catalog: check before you send

`GET /actions` (or the `action://list` resource) returns each action with live per-workspace state:

```json
{
  "actions": [
    {
      "id": "email_member",
      "name": "Email workspace member",
      "available": true,
      "caps": { "sent_last_hour": 3, "cap_per_hour": 100, "cap_per_run": 20 }
    },
    {
      "id": "notion_sync",
      "name": "Sync document to Notion",
      "available": false,
      "reason": "No outbound Notion sync connection is configured for this workspace."
    }
  ]
}
```

Agents are taught to read this **before** invoking, so a missing connection or an exhausted cap is discovered as a cheap read instead of a rejected send.

## Invoking

```bash
curl -X POST https://app.amdahl.co/api/platform/v1/actions/invoke \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "email_member",
    "params": {
      "recipient_emails": ["taylor@yourco.com"],
      "subject": "Pipeline digest",
      "body": "Three deals moved stage this week..."
    },
    "idempotency_key": "digest-2026-07-10"
  }'
```

- `action` — `email_member` or `notion_sync`.
- `params` — passed to the underlying engine verbatim. For `email_member`: `recipient_emails`, `subject`, `body`, optional `cc_emails`. For `notion_sync`: `document_group_id`.
- `idempotency_key` — optional dedupe key. A repeat call with the same key returns the prior outcome without resending.

All the guardrails of the underlying engines apply unchanged: email recipients are validated against workspace membership, sends are capped per workspace per hour (and per run), and the Notion push is idempotent by construction (an unchanged document is a no-op).

## The allowlist: actions are opt-in per run

An **agent-initiated** invoke is only permitted when the action appears in the run's `actions_allowed` list — a policy knob persisted on the run when it starts. The default is the **empty list**: a run that was not explicitly granted an action cannot fire it, no matter what the prompt says, and gets a structured `action_not_allowed` rejection. The agent then surfaces the send as a _proposal_ for you instead.

Sub-agents can never invoke actions at all — they run with investigation-only permissions and put "this would be worth emailing out" in their suggestions for the main agent (and ultimately you) to decide.

A **human** API caller with the `actions:execute` scope is not subject to the allowlist — there is no run configuration to consult; your credential authorizes you directly.

## Failure handling

A failed action is **never retried automatically**. A cap rejection, a non-member recipient, or an unconfigured Notion connection comes back as a structured error, verbatim from the engine, and the agent adapts — reporting the failure or proposing an alternative. When a retry is genuinely wanted, pass an `idempotency_key` so even a deliberate repeat can never double-send.
