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

# Ambient agents: proactive outreach from setup to audit

> Bind an agent to a CDP segment, shape the question it answers per contact, verify the send-once guarantee, and read the audit trail — a complete walkthrough with a worked win-back example.

# Ambient agents: segment-driven proactive outreach

An ambient agent decides, contact by contact, whether to reach out when someone enters a CDP segment — and records every decision, sent or not, in an audit trail you can review. This guide walks the full operator loop: the mental model, setup, the send-once guarantee, the audit readback, and a worked invoice-reminder example you can run in the sandbox before going live. The capability reference at [Ambient agents](/agents/ambient-outreach) documents each field; this guide is the task-oriented path through it.

Every control here is **tenant-owned**: the enable flag, the bindings, the prompt, and the policy gates that decide whether a drafted message actually sends all live in your organization's settings. Nothing fires until you turn it on.

**Base paths:** `/agents` → **Proactive Outreach** tab (the `/agents/ambient-outreach` route redirects to that tab); organization settings under `agents.ambient_outreach`.

**Authentication:** Clerk session or API key. Setup writes require an owner, admin, or developer role; the audit API is readable by any authenticated tenant user.

***

## 1. The mental model

One trigger, one question, one audit row:

1. A contact **enters** a CDP segment and produces a `segment.entered` event.
2. Orbit asks the bound agent a single question per crossing: *should I reach out to this contact?*
3. The agent reads the contact's context and answers in one of two ways:
   * **Send** — it drafts one outbound message on the bound channel.
   * **No** — it declines, and its reasoning is recorded in the audit row.

Either answer writes exactly one audit row keyed by **contact + segment**. That row is both the idempotency record (step 4 below) and the audit trail (step 5). Tenant policy gates — consent, quiet hours, suppression, and your frequency caps — still apply between "agent said send" and "message went out"; a gated send lands in the audit as *Blocked* with the gate that stopped it.

Ambient outreach answers a different question than a campaign: a campaign sends the same message to everyone in an audience on your schedule; an ambient agent is asked per contact and may conclude *this one, not that one* — with the reasoning on file.

## 2. Where it lives

* **Dashboard:** open **Agents → Proactive Outreach**. The route `/agents/ambient-outreach` exists solely to redirect into that tab inside `/agents` — it hosts no separate page.
* **Organization settings:** the enable flag and bindings live under `agents.ambient_outreach` and are written with `PUT /api/v1/organization`, merging into `settings` (the enablement move is shown in step 3).
* **Audit API:** `GET /api/v1/agents/ambient-outreach/audit` — the same rows the tab renders, for pull-based reporting (step 5).

## 3. Set up a binding

Prerequisites: the agent exists (see [Creating agents](/agents/creating-agents)), the segment exists in the CDP, and you hold an owner, admin, or developer role.

1. **Create the segment.** Any CDP segment works — its entry event is the trigger. Keep the definition narrow enough that entering it means "worth an evaluation" (for example "invoice opened, no payment in 14 days"), not "everyone."
2. **Write the agent's prompt for proactive work.** Tell it three things explicitly: who to reach out to, when to decline, and what a good single opening message looks like. A prompt that only describes what the agent *can* do will send more than you intend; give it the decline criteria.
3. **Bind segment → agent → channel** with `PUT /api/v1/organization`, merging this block into `settings`:

```json theme={null}
{
  "agents": {
    "ambient_outreach": {
      "enabled": true,
      "bindings": [
        {
          "segment_id": "seg_invoice_missed_14d",
          "agent_id": "agent_invoice_reminder",
          "channel": "sms"
        }
      ]
    }
  }
}
```

The channel is `sms`, `whatsapp`, or `email`. Outbound voice is not an ambient channel. One binding maps one segment to one agent on one channel; add more `bindings` entries for more segments.

The flag resolves defensively: a missing block, `enabled: false`, or a segment with no well-formed binding all resolve to "do nothing," and flipping `enabled` to `false` stops new evaluations immediately while bindings stay saved. See [Ambient agents](/agents/ambient-outreach) for the full settings contract.

## 4. The send-once guarantee

Every evaluation writes one audit row keyed by **contact + segment**. The guarantee that row buys:

* The same contact entering the same segment is evaluated **once** — the agent is asked once, so a contact never receives two proactive messages for the same crossing.
* Retried or duplicated background jobs short-circuit against the row instead of asking again; a worker crash-and-retry cannot double-send.
* Segment re-entry does **not** retrigger an evaluation. If a contact pays, leaves the segment, and later re-enters, the contact+segment row from the first crossing already exists and wins — the agent is not re-asked. Re-run outreach for a new circumstance with a new segment (for example a "30 days missed" segment beside your "14 days" one), not by re-entering the old one.

Treat the pair as the idempotency key when you design segments: two segments, two independent decisions.

## 5. Read the audit trail

The audit is where "no" answers become visible — an ambient agent that declines everything is working as configured, and the trail is how you confirm it.

**Dashboard:** **Agents → Proactive Outreach** shows one row per evaluation with the segment that fired, the agent asked, its decision, and the outcome — *Sent*, *Agent declined*, or *Blocked* with the policy reason (consent, quiet hours, suppression, or frequency cap). Filter by decision to narrow to **Sent** or **Not sent**. The summary block at the top rolls up sent / agent-declined / blocked counts per agent over the recent-activity window, sorted by sends, so a binding that produces nothing but declines shows up immediately.

**API:** pull the same rows for your own reporting. This is the audit surface for this feature — the general human-in-the-loop endpoints documented under `/agents/oversight` cover interactive oversight actions, not ambient evaluations.

```bash theme={null}
curl "https://api.orbit.devotel.io/api/v1/agents/ambient-outreach/audit?agent_id=agent_invoice_reminder&decision=sent&limit=50" \
  -H "X-API-Key: dv_live_sk_..."
```

| Parameter  | Values                    | Default |
| ---------- | ------------------------- | ------- |
| `agent_id` | Filter to one bound agent | none    |
| `decision` | `sent` or `skip`          | none    |
| `limit`    | 1–200 entries             | 50      |

The response carries `entries` (newest first, each with contact, segment, agent, channel, decision, outcome, `messageId`, `costUsd`, and `createdAt`), `count`, and a per-agent `summary` — all computed over the same bounded window of recent decisions, so the list and the rollup can never disagree. A tenant with no ambient activity yet gets empty lists, not an error.

Audit-readback pseudocode — alert when a binding sends nothing for a day:

```
function checkAmbientHealth(apiKey, agentId):
    audit = GET /api/v1/agents/ambient-outreach/audit
        ? agent_id = agentId & limit = 200
    summaries = audit.summary where agentId == agent_id
    if summaries is empty:
        alert "binding produced zero evaluations — check segment membership and enabled flag"
        return
    s = summaries[0]
    if s.sentCount == 0 and s.agentDeclinedCount > 0:
        alert "agent declined every evaluation — revisit the decline criteria in its prompt"
    if s.blockedCount > s.sentCount:
        alert "policy gates blocked the majority — review consent/quiet-hours/frequency-cap posture"
```

Adapt the thresholds to your expectations; the point is that all three failure shapes (no evals, all declines, mostly blocked) are readable from one response.

## 6. Compliance — tenant-owned controls

Every outbound draft the agent produces carries two things in its audit row: the agent's decision (and, for a decline, its reasoning) and the outcome of the tenant policy gates between "drafted" and "sent." Ambient sends travel the standard messaging pipeline tagged as marketing traffic, so your own consent model, quiet hours, suppression list, and frequency caps gate them exactly as they gate a campaign — including the fail-closed federal voice dialing-window guard where it applies.

Nothing here is a platform-mandated compliance promise: Orbit exposes tenant-owned controls and records how each one resolved, per the docs norm; your organization decides the consent posture, the caps, and the prompts. If a draft is stopped by one of your gates, the audit says *Blocked* and names the gate — that row is your evidence the control fired.

## 7. Worked example: remind a customer who opened but never paid

Goal: a customer opened an invoice but has not paid 14 days later. One reminder, on SMS, only when the agent judges it worthwhile — and a record either way.

**Step 1 — the segment.** Create a CDP segment `seg_invoice_missed_14d` with the rule "invoice event `opened` > 14 days ago AND no `paid` event since." Verify membership against a known test contact before binding anything (see [Sandbox test mode](/guides/sandbox-test-mode)).

**Step 2 — the prompt template.** Create the agent with a prompt shaped for proactive work:

```
You are a billing outreach agent for <company>. A contact entered the segment
"invoice opened, unpaid after 14 days."

Reach out when ALL of these hold:
  - the contact has no open support conversation about billing, and
  - the unpaid invoice is not already in a payment plan, and
  - you can draft a reminder that is accurate about THIS invoice.

Decline when ANY of these hold:
  - the contact asked not to be contacted about billing,
  - the invoice amount or due date in context looks inconsistent,
  - you have already been asked about this contact and segment (you will
    be asked at most once per contact, so a duplicate ask means decline).

A good opening message is under 320 characters, names the invoice amount and
due date, offers the payment link, and reads as one reminder — not a demand.
Never threaten, never imply legal action, never mention other invoices.
```

Version-pin the prompt (see [Agent versions](/agents/agent-versions)) so the reminder copy your agent learned against is the copy in production.

**Step 3 — bind and enroll.** Merge into `settings` with `PUT /api/v1/organization`:

```json theme={null}
{
  "agents": {
    "ambient_outreach": {
      "enabled": true,
      "bindings": [
        { "segment_id": "seg_invoice_missed_14d", "agent_id": "agent_invoice_reminder", "channel": "sms" }
      ]
    }
  }
}
```

**Step 4 — sanity-test the trigger in the sandbox.** Before production traffic:

1. In the sandbox workspace, enroll a magic-number contact and emit the invoice events so the contact enters `seg_invoice_missed_14d` — the segment-entry check from step 1 tells you the trigger will fire (see [Sandbox test mode](/guides/sandbox-test-mode) and [Go-live checklist](/guides/go-live-checklist)).
2. Confirm one `segment.entered` evaluation lands in **Agents → Proactive Outreach** for the test contact.
3. Drive the same events again for the same contact — confirm **no second row** appears (the contact+segment idempotency row wins). Then drive the contact through a *different* segment and confirm a new evaluation does fire for that pair.
4. Set the test contact to opted-out / inside quiet hours and re-run the first crossing on a fresh contact — the row must land as *Blocked* with the gate named, proving the policy gates sit between "agent said send" and "message sent."

**Step 5 — turn it on and read the first week.** Enable in production, then read the per-agent summary after the first hundred crossings. A healthy binding shows a mix of sent and agent-declined; every-declined means the prompt's decline criteria swallow the segment, every-sent means they are not doing any work. Tune the prompt, not the segment, first.

## Reference

* [Ambient agents](/agents/ambient-outreach) — capability reference: settings contract, trigger flow, audit API schema, known limits
* [Creating agents](/agents/creating-agents) — build the bound agent
* [Agent versions](/agents/agent-versions) — pin the prompt revision before enrollment
* [Human-in-the-loop oversight](/agents/human-in-the-loop-oversight) — the oversight endpoints for interactive agent actions
* [Sandbox test mode](/guides/sandbox-test-mode) — run the trigger sanity test before production
* [Go-live checklist](/guides/go-live-checklist) — pre-production launch checks
