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

# AI Disclosure Notice

> Show people they are talking to an AI (EU AI Act Art. 50(1)) in your channel — with the channel client in one line, or from the raw conversation API.

# AI Disclosure Notice (EU AI Act Art. 50)

Article 50(1) of the EU AI Act requires that people interacting with an AI system are told so. When an agent's **Transparency** settings turn the disclosure on, the Swarmd relay attaches a notice to the reply on every person-facing leg — platform chat, Teams, and **your channel**. The relay cannot draw your channel's UI, so showing the notice is the one thing your integration has to do.

<Warning>
  If your channel only renders the agent's reply text, the person never sees the notice and the disclosure is not met. Every channel integration should handle it — it is two lines with the channel client.
</Warning>

## Where the notice is configured

In the Swarmd dashboard open the agent → **Governance** → **Transparency (Art. 50)**:

| Setting                                                | Effect on your channel                                                                     |
| ------------------------------------------------------ | ------------------------------------------------------------------------------------------ |
| **Disclosure** on/off                                  | Whether the relay attaches a notice at all                                                 |
| **When**: *Once, at the first reply of a conversation* | The notice arrives with the first reply of each conversation (`contextId`) and never again |
| **When**: *Every reply*                                | The notice arrives with every reply                                                        |
| **Scope**                                              | Every channel and human conversation, or only selected channels                            |
| **Text**                                               | The exact sentence the relay sends — show it as-is                                         |

The same settings can be set through the API with `PUT /registry/v1/agents/{agentId}/governance/transparency`.

### Which agent's settings apply

The notice comes from the **agent your channel talks to directly** — the first hop after the channel. When that agent delegates to other agents, those hops are agent-to-agent, not person-facing, and their transparency settings play no part: a sub-agent's disclosure never bubbles up, and the front agent's disclosure is not affected by what sits behind it.

| Disclosure enabled on…                 | What the person sees                              |
| -------------------------------------- | ------------------------------------------------- |
| The agent the channel is subscribed to | The notice, with that agent's text and mode       |
| Only a sub-agent it delegates to       | Nothing                                           |
| Both                                   | The notice once, from the front agent's text only |

This follows Art. 50(1): the obligation attaches to the system the person is interacting with, and its owner configures the text and the mode. It also means a person-facing agent with disclosure **off** stays silent no matter what its sub-agents declare — check the front agent's Transparency settings, not the chain's. The channel scope (*selected channels*) is evaluated against your channel on that same first hop. Synthetic content **marking** (Art. 50(2)) is different: it applies on every hop, so a sub-agent's marking still reaches you.

## What the relay sends

The notice is **never merged into the agent's reply**. It travels beside it, so `extractReply()` (or `status.message`) stays the agent's own words and you can render the two as separate elements:

```json theme={null}
{
  "aggregateState": "COMPLETED",
  "latestTask": {
    "id": "task-weather",
    "status": {
      "state": "completed",
      "message": { "role": "agent", "parts": [{ "kind": "text", "text": "The weather in Bangkok is rainy, 18:00." }] }
    },
    "metadata": {
      "ai_disclosure": {
        "message_id": "ai-disclosure-7c1e…",
        "article": "Art. 50(1)",
        "text": "You are talking to an AI assistant. Its answers are generated automatically; a person can be asked to review any decision it takes."
      }
    }
  },
  "messages": [
    { "messageId": "7c1e…", "role": "user", "parts": [{ "kind": "text", "text": "What's the weather in Bangkok?" }] },
    { "messageId": "ai-disclosure-7c1e…", "role": "agent", "metadata": { "ai_disclosure": true, "article": "Art. 50(1)" },
      "parts": [{ "kind": "text", "text": "You are talking to an AI assistant. …" }] },
    { "messageId": "m-agent-1", "role": "agent", "parts": [{ "kind": "text", "text": "The weather in Bangkok is rainy, 18:00." }] }
  ]
}
```

* `latestTask.metadata.ai_disclosure` — present only on turns that carry a notice. This is what you render live.
* `messages[]` — the conversation history. The notice is stored once, **before** the reply it introduces, flagged with `metadata.ai_disclosure: true`. This is what you render when a person reopens a conversation.
* The channel JSON-RPC endpoint (`/relay/v1/channels/{channelId}/agents/{agentId}/a2a/0.3.0`) carries the same `metadata.ai_disclosure` on the `result`, with the notice message in `result.history[]`.

## With the channel client

### One message per turn (Slack, SMS, email, simple widgets)

`extractReplyWithDisclosure` returns one string — the notice first, then the agent's words — or just the reply when no notice was sent:

```typescript theme={null}
import { extractReplyWithDisclosure } from '@swarmd.ai/channel-client';

const state = await swarmd.startAndSend(agentId, text);
await postToChannel(extractReplyWithDisclosure(state));
// "You are talking to an AI assistant. …
//
//  The weather in Bangkok is rainy, 18:00."
```

Pass `{ separator }` to change what sits between the two (default: a blank line).

### Notice as its own element (chat widgets, web UIs)

The `completed` event carries `disclosure` next to `reply`; both land on the **same** event, so render them in the same pass — the person is told they are talking to an AI as they read its answer:

```typescript theme={null}
await swarmd.startAndSend(agentId, text, {
  onEvent: event => {
    if (event.type !== 'completed') return;
    if (event.disclosure) {
      addBubble({ kind: 'notice', label: event.disclosure.article, text: event.disclosure.text });
    }
    addBubble({ kind: 'agent', text: event.reply });
  },
});
```

`extractDisclosure(state)` returns the same `{ text, messageId?, article? }` from any `ConversationState`.

### Reopening a conversation

When you rebuild a thread from `state.messages` (or `GET …/conversations/{contextId}/messages`), render messages whose `metadata.ai_disclosure === true` as the notice element and everything else as normal turns. Because the stored notice keeps the same `messageId` as `disclosure.messageId`, a live turn and a reload produce the same thread.

<Tip>
  A complete, running example is the PrismForce demo in the Swarmd repository (`ui/demo-prismforce`): `src/lib/swarmdRelay.ts` wraps the channel client for five channels and `src/components/ChatWidget.tsx` renders the notice as its own bubble before the reply, including when the reply is released from a human review.
</Tip>

## Without the channel client

If you call the conversation REST API directly, read the same two fields:

```typescript theme={null}
const state = await relay.sendMessage(contextId, message); // POST …/conversations/{contextId}/messages
const disclosure = state.latestTask?.metadata?.ai_disclosure?.text;
const reply = firstTextPart(state.latestTask?.status?.message) ?? firstArtifactText(state.latestTask);

render(disclosure ? [{ notice: disclosure }, { agent: reply }] : [{ agent: reply }]);
```

When the relay hands back `202` with `aggregateState: WORKING`, keep polling `GET …/conversations/{contextId}/state`; the notice appears on the state that goes `COMPLETED`, together with the reply.

## Checklist

* Show the notice **before** the reply, in the **same** render — not on the next poll, not behind a click.
* Show the relay's text **unchanged**; the wording is part of the tenant's compliance record and is what the audit trail logs.
* Show it **once per conversation** in the default mode: don't repeat it on later turns, and don't drop it when the person reopens the thread.
* Keep `reply` for the agent's words. If your channel has a single text slot, use `extractReplyWithDisclosure`.
