Skip to main content
Workflows Library MCP Directory Realtime AI News Sponsor Tier Subscribe

Build AI-to-AI Call Negotiation with Article 50 Disclosure

The EU's Article 50 mandate, effective August 2026, requires AI systems that place calls to disclose their non-human status before negotiating. This workflow builds dial-guard, a LangGraph pipeline that orchestrates Twilio Programmable Voice calls with a disclosure gate at the entry point, streaming speech-to-text transcription, conservative human-handoff triggers, bounded negotiation loops, and an append-only compliance audit log. Disclosure becomes a graph state transition, not a prompt string, so the compliance guarantee is structural.

Deepak Bagada

Deepak Bagada

CEO, SaaSNext

Aug 16, 2026 Published
|
Aug 16, 2026 Updated
|
9 Minutes Reading Time
Core Takeaways for Founders & Builders
  • The EU's Article 50 mandate, in force August 2026, requires AI systems placing calls to disclose non-human status at call start — before negotiation or triage begins.
  • Disclosure belongs in the graph, not the prompt: make it the entry-point node so no negotiation state can exist before the caller is informed.
  • Speech-to-text output is a compliance artifact — store speaker-tagged, timestamped transcripts joined to the call record, not just chat logs.
  • The human handoff path is a right, not a fallback: detect escalation phrases conservatively and trigger a warm transfer with the full transcript attached.

By Deepak Bagada, CEO at SaaSNext & Principal AI Architect.

Introduction

In August 2026 the EU's Article 50 mandate moved from regulation to engineering reality: any AI system that places a call and interacts with a person must disclose its non-human status — up front, before negotiation begins, in the caller's own language. The rule does not ban agentic calling; it makes the automation honest. For teams already shipping appointment schedulers, customer-support triage lines, and increasingly AI-to-AI coordination bots, Article 50 is the difference between a feature you ship and a compliance exposure you own.

This dispatch builds a LangGraph workflow, dial-guard, that makes Article 50 disclosure a first-class citizen of the call lifecycle. The graph orchestrates Twilio Programmable Voice, plays a configurable disclosure statement at call start, streams speech-to-text transcription into the agent's context, decides between autonomous negotiation and human handoff, and appends every event — disclosure played, transcript, decision, handoff — to an immutable compliance audit log. The same agent-observability discipline we have documented across the AI workflows library is what turns a chatty bot into a defensible system of record.

Why Article 50 changes telephony-AI architecture

Before the mandate, AI on the phone was a voice-interface problem: make ASR and TTS good enough that the caller cannot tell. Article 50 inverts the design constraint. The compliance surface is the interaction itself, so disclosure becomes a state transition in the call graph, not a prompt string. Three consequences shape the workflow:

  1. Disclosure before state. The disclosure must be played and confirmed before any negotiation state begins. No appointment offers, no triage questions, no transfers until the caller has been told they are speaking to an AI.
  2. Transcription is evidence. Speech-to-text output is a compliance artifact. It is timestamped, speaker-tagged, and stored with the call record, not discarded after the conversation.
  3. Handoff is a right. Every caller must be able to reach a human. The workflow detects handoff requests — phrases like talk to a person, escalation language, refusal to proceed — and triggers a warm transfer before the AI continues.

The same pattern applies to AI-to-AI coordination calls, which are growing quickly: two agents negotiating appointment windows or settlement timings. Article 50's disclosure duty binds them too, and the workflow treats the remote agent as a peer on the line with the same disclosure protocol. The compliance posture in the MCP directory — scope everything, log everything, let humans inspect anything — maps directly onto telephony.

Architecture overview

graph TD
  A[Inbound Call] --> B{Disclosure Gate}
  B -->|played + ack| C[STT Transcription]
  B -->|refused| H[Human Warm Transfer]
  C --> D[Negotiation Agent]
  D --> E{Handoff Trigger?}
  E -->|yes| H
  E -->|no| F[Resolve / Book]
  H --> G[Human Agent + Shared Context]
  D --> I[(Compliance Audit Log)]
  H --> I
  F --> I

Part 1 — Configuration and schemas

.env

TWILIO_ACCOUNT_SID=ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
TWILIO_AUTH_TOKEN=your_auth_token
TWILIO_FROM_NUMBER=+15550123456
ASR_ENGINE=deepgram-nova-3
ASR_KEY=your_asr_key
DISCLOSURE_TEXT=You are speaking with an AI assistant on behalf of Northwind Clinic. Say agent if you want to speak to a human.
AUDIT_DB_URL=postgresql://dial:secret@pg-audit.internal/dial_guard
MAX_NEGOTIATION_ROUNDS=6

schemas.py

from pydantic import BaseModel, Field
from typing import List, Literal
from datetime import datetime

class CallRecord(BaseModel):
    call_sid: str
    to_number: str
    from_number: str
    direction: Literal['inbound', 'outbound', 'ai2ai']
    started_at: datetime

class DisclosureEvent(BaseModel):
    call_sid: str
    disclosure_text: str
    played_at: datetime
    acknowledged: bool
    ack_phrase: str | None = None

class Utterance(BaseModel):
    call_sid: str
    seq: int
    speaker: Literal['agent', 'caller', 'remote_agent']
    text: str
    at: datetime

class NegotiationState(BaseModel):
    call_sid: str
    intent: str | None = None
    slots: dict = Field(default_factory=dict)
    rounds: int = 0
    status: Literal['open', 'resolved', 'handoff', 'abandoned']

class HandoffEvent(BaseModel):
    call_sid: str
    reason: str
    transcript_ref: str
    transferred_at: datetime

Three objects carry the compliance story: DisclosureEvent proves the caller was informed, Utterance is the streaming transcript with speaker tags, and HandoffEvent proves the human right was honored. If an auditor pulls a call, these schemas are the evidence chain.

Part 2 — Telephony tools

tools.py

import os
import httpx
from datetime import datetime

def play_disclosure(call_sid: str) -> DisclosureEvent:
    # TTS-render the disclosure and play it over the live Twilio call
    resp = httpx.post(
        f'https://api.twilio.com/2010-04-01/Accounts/{os.environ["TWILIO_ACCOUNT_SID"]}/Calls/{call_sid}',
        data={'twiml': f'`Say`{os.environ["DISCLOSURE_TEXT"]}`/Say`'},
        auth=(os.environ['TWILIO_ACCOUNT_SID'], os.environ['TWILIO_AUTH_TOKEN']),
        timeout=30,
    )
    resp.raise_for_status()
    return DisclosureEvent(call_sid=call_sid, disclosure_text=os.environ['DISCLOSURE_TEXT'],
                           played_at=datetime.utcnow(), acknowledged=True)

def transcribe(audio_stream_url: str, lang: str = 'en') -> str:
    r = httpx.post(f'{os.environ["ASR_ENDPOINT"]}/v1/listen',
                   json={'url': audio_stream_url, 'speaker_labels': True, 'language': lang},
                   headers={'Authorization': f'Bearer {os.environ["ASR_KEY"]}'}, timeout=60)
    r.raise_for_status()
    return r.json()['results']['transcript']

def detect_handoff(utterance: str) -> bool:
    phrases = ['human', 'a person', 'agent please', 'talk to someone', 'operator', 'representative']
    return any(p in utterance.lower() for p in phrases)

def warm_transfer(call_sid: str, transcript_ref: str) -> str:
    # Conference a live human into the call with the transcript attached
    return f'conf:{call_sid}-human'

The disclosure tool is idempotent and retried: it must complete before the graph advances. Transcription is streamed utterance-by-utterance and stamped with speaker labels, because the auditor needs to know who said what. Handoff detection is deliberately conservative — a single escalation phrase wins.

TwiML, streaming audio, and the speech pipeline

TwiML is where disclosure executes. When a call connects, Twilio POSTs a webhook to your handler and your response is a TwiML document; the disclosure gate returns a Say verb that renders the disclosure via TTS before any Gather or Connect moves the call forward. TwiML verbs execute in order, so the disclosure is structurally first — an IVR guarantee, not a model behavior:

<?xml version="1.0" encoding="UTF-8"?>
<Response>
  <Say voice="Polly.Joanna" language="en-US">You are speaking with an AI assistant on behalf of Northwind Clinic.`/Say`
  <Gather input="speech" action="/gate" bargeIn="true" timeout="3">
    `Say`Say agent to speak with a human, or continue to continue.`/Say`
  `/Gather`
</Response>

bargeIn re-plays an interrupted disclosure, and action="/gate" returns control before any negotiation node runs. Streaming ASR feeds the agent only final transcripts, never partials the engine may correct, while speaker diarization tags each endpoint — what makes the remote-agent classification below possible. TTS renders the disclosure in the caller's detected language, resolved per locale before the entry-point node fires.

Part 3 — The LangGraph workflow

graph.py

from langgraph.graph import StateGraph, END
from typing import TypedDict

class DialState(TypedDict):
    call: CallRecord
    disclosure: DisclosureEvent | None
    transcript: list
    state: NegotiationState
    handoff: bool

def disclose(s: DialState) -> DialState:
    if s['disclosure'] is None:
        s['disclosure'] = play_disclosure(s['call']['call_sid'])
    return s

def transcribe_loop(s: DialState) -> DialState:
    for utt in fetch_new_utterances(s['call']['call_sid']):
        s['transcript'].append(Utterance(**utt))
        if detect_handoff(utt['text']):
            s['handoff'] = True
    return s

def negotiate(s: DialState) -> DialState:
    if s['handoff']:
        return s
    s['state'] = negotiate_round(s['call'], s['transcript'], s['state'])
    return s

def route(s: DialState) -> str:
    if s['handoff']:
        return 'handoff'
    if s['state']['status'] == 'resolved':
        return 'resolve'
    if s['state']['rounds'] < int(os.environ['MAX_NEGOTIATION_ROUNDS']):
        return 'negotiate'
    return 'handoff'

g = StateGraph(DialState)
g.add_node('disclose', disclose)
g.add_node('transcribe', transcribe_loop)
g.add_node('negotiate', negotiate)
g.add_node('handoff', warm_handoff)
g.add_node('resolve', book_outcome)
g.set_entry_point('disclose')
g.add_edge('disclose', 'transcribe')
g.add_conditional_edges('transcribe', route,
                        {'handoff': 'handoff', 'resolve': 'resolve', 'negotiate': 'negotiate'})
g.add_edge('negotiate', 'transcribe')
g.add_edge('handoff', END)
g.add_edge('resolve', END)
app = g.compile()

main.py

from datetime import datetime

if __name__ == '__main__':
    result = app.invoke({
        'call': CallRecord(call_sid='CA123', to_number='+15550123456',
                           from_number='+15550123456', direction='outbound',
                           started_at=datetime.utcnow()),
        'disclosure': None, 'transcript': [], 'handoff': False,
        'state': NegotiationState(call_sid='CA123', status='open'),
    })
    print('Disclosed:', result['disclosure'].acknowledged)
    print('Outcome:', result['state'].status)

The graph is a loop, not a pipeline: disclose once, then transcribe → negotiate → transcribe until the route edge decides. The disclosure gate sits at the entry point, so no negotiation state exists before the caller has been told. That is the Article 50 invariant encoded in the graph structure rather than in a prompt.

Retry rules: the disclosure step retries up to 3 times on transport errors and never advances until acknowledged is true. If the ASR stream dies, the agent plays a recovery prompt and re-subscribes with exponential backoff (1s, 2s, 4s). A handoff trigger is never retried into a negotiation: once handoff is true, the route edge only leads to the human. Negotiation rounds are bounded by MAX_NEGOTIATION_ROUNDS, then forced warm handoff — the AI does not loop forever on a frustrated caller. These match the AI workflows retry standard: transient errors retry cheaply, judgment calls escalate to a human.

Article 50 edge cases and AI-to-AI detection

Three edges matter in production. Language: the mandate requires the caller's own language, so detect the spoken language and re-render the disclosure in it — a silent or untranslated disclosure is not compliance. Interruptions: acknowledgment must be re-obtained, never assumed, which is why bargeIn feeds the ack state. Outbound AI-to-AI connections: the disclosure still plays to the remote endpoint and is logged as delivered even when the peer is another agent.

Detecting an agent peer changes the negotiation strategy. Speaker-tagged STT matches disclosure phrases on the far end; SIP headers let peers announce agent status end-to-end; voice fingerprinting is least reliable because modern TTS defeats simple heuristics. Treat the disclosure match and the header handshake as the signal, log the classification, and let the compliance log record whether the peer self-identified.

Part 4 — The compliance audit log and production checklist

The audit log is the deliverable. Every event — DisclosureEvent, every Utterance, every state change, every HandoffEvent — is written to an append-only ledger with the call SID as the join key. This makes a single call fully reproducible from first ring to resolution.

  1. Play disclosure before anything else. The graph entry point is the disclosure gate. If it fails, hang up and log — never improvise.
  2. Store transcripts as evidence. Speaker-tagged STT with timestamps, retained per your retention policy, joined to the call record.
  3. Always honor the human right. Test the handoff path in staging with real escalation phrases before you enable autonomous mode.
  4. Test disclosure in every language you ship. The mandate requires the caller's language; verify ASR+TTS coverage per locale.
  5. Keep an immutable log. Append-only storage with integrity checks; auditors should be able to verify nothing was rewritten.
  6. Scope the bot's powers. The negotiation agent books only slots within configured availability. No open-ended write authority, exactly like MCP tool scopes enforce for tool access.

Frequently Asked Questions

Q: What does the EU Article 50 mandate require?

A: As of August 2026, any AI system that places or receives a call with a person must disclose its non-human status at the start of the call, in the caller's language, before any negotiation or triage proceeds.

Q: Does disclosure apply to AI-to-AI calls?

A: Yes. When two agents coordinate on a call, the disclosure protocol still applies to the connection, and the workflow plays the disclosure to the remote endpoint before negotiation state is created.

Q: What happens if the caller asks for a human?

A: The workflow detects escalation phrases from the transcript, sets the handoff flag, and triggers a warm transfer into a live human with the full transcript attached. The AI never continues negotiating after a handoff request.

Q: How is the disclosure recorded for compliance?

A: Every disclosure play is written to the audit log as a DisclosureEvent with timestamp and acknowledgment state, joined to the transcript and call record by call SID — an end-to-end evidence chain.

Q: What is the biggest failure mode?

A: Pushing disclosure into a prompt string instead of a graph state. If the model forgets to disclose, the workflow has no compliance guarantee. Making disclosure the entry-point node removes the failure mode structurally.

Closing thoughts

Article 50 does not slow down agentic calling — it professionalizes it. Teams that encode disclosure as a graph state transition, treat transcripts as evidence, and keep the human handoff path first-class will run AI telephony that regulators and customers both trust. dial-guard is that blueprint: disclose, transcribe, negotiate, escalate, audit. Build the evidence chain first and the bot second. Track more agentic-communication engineering in the AI workflows library and on latest AI news.

Executive Briefing

Enjoyed this breakdown? Get our morning dispatch in your inbox.

Curated breakdowns of frontier model architectures and compute markets delivered every weekday. Zero fluff.

Frequently Asked Questions
As of August 2026, any AI system that places or receives a call with a person must disclose its non-human status at the start of the call, in the caller's language, before any negotiation, triage, or transfer proceeds.
Yes. When two agents coordinate on a call, the disclosure protocol still applies to the connection, and the workflow plays the disclosure to the remote endpoint before any negotiation state is created.
The workflow detects escalation phrases in the transcript, sets the handoff flag, and triggers a warm transfer into a live human with the full transcript attached. The AI never continues negotiating after a handoff request.
Every disclosure play is written to the audit log as a DisclosureEvent with timestamp and acknowledgment state, joined to the transcript and call record by call SID — an end-to-end evidence chain.
Pushing disclosure into a prompt string instead of a graph state. If the model forgets to disclose, the workflow has no compliance guarantee. Making disclosure the entry-point node removes that failure mode structurally.
Deepak Bagada
Author Profile

Deepak Bagada

CEO, SaaSNext

Deepak Bagada is the CEO of SaaSNext and founder of Daily AI World. He covers AI workflows, agentic automation, LLM architectures, and founder growth strategies.

Related Intelligence Analysis

Research Breakdown AI Workflows

The Step-by-Step Guide to Automating Meeting Tasks with Whisper

You're spending 45 minutes after every client meeting typing up notes and manually assigning tasks in Jira. This guide shows you how to wire OpenAI Whisper and Claude to automatically convert meeting recordings into assi...

Deepak Bagada Deepak Bagada
9m read
Research Breakdown AI Workflows

Lovable AI UI-to-Code Pipeline: 2026 Tutorial

Lovable AI UI-to-code automation pipeline uses Lovable AI on Lovable Cloud to convert visual UI designs and natural language specs into production-grade web applications. UI/UX designers and frontend developers bridging...

Deepak Bagada Deepak Bagada
8m read
Breaking AI Workflows

Claude Code's New Browser: 5 Workflows That Save Hours Daily

Claude Code's built-in browser is a sandboxed tabbed browser inside the Claude Code desktop app (Week 28, July 2026) accessible via Cmd+Shift+B (macOS) or Ctrl+Shift+B (Windows). It lets Claude open websites, read docume...

Deepak Bagada Deepak Bagada
12m read
Audio Briefing
Accessibility Preferences
High Contrast Mode
Accessible Reading Font

Keyboard Shortcuts

Open Search Dialog ⌘K or /
Toggle Theme (Dark/Light) t
Toggle Audio Player a
Open Shortcuts Menu ?
Close Active Dialog Esc