# Welcome to the Home of the vCons

vCon is the open container for conversations. Signed, consent aware, and portable, it lets organizations record what was said, by whom that both machines and auditors can read and trust.

## A trust layer for every conversation, human or agent

Every business runs on conversations. Calls, chats, video meetings, agent dialog. Until now there has been no shared way to record what was said, on whose behalf, and under what authority, in a form that survives moving between systems.

vCon (virtual conversation) is that container. It is an open IETF standard for packaging the parties, the dialog, the recording or transcript, the consent, and the analysis into one signed, portable JSON object. The Conserver is the open source platform that creates and manages vCons at scale.

{% columns %}
{% column %}
If you read one page, read this:

{% content-ref url="/pages/tWoe8nJgLTVfXkTlN9d7" %}
[A vCon Primer](/vcons/a-vcon-primer)
{% endcontent-ref %}
{% endcolumn %}

{% column %}
If you watch one video, watch this:

{% embed url="<https://www.youtube.com/watch?v=YDh0phRx0bM>" %}
{% endcolumn %}
{% endcolumns %}

{% hint style="info" %}
**Provenance over policy.** vCon does not ask you to trust a vendor that says the right things. It puts cryptographic provenance, parties, and consent inside the file itself, so the next system in line can verify them on its own.
{% endhint %}

## Find your path

{% tabs %}
{% tab title="Architects and CTOs" %}
You want to know where vCon fits in your stack and whether it is real.

* [vCons are...](/vcons/vcons-are...) — the one paragraph mental model
* [Why vCons need a file](/vcons/why-vcons) — conversations as durable digital artifacts in the AI era
* [Conserver Introduction](/conserver/conserver-introduction) — the reference platform that produces and processes vCons
* [Day in the Life of a vCon](/conserver/day-in-the-life-of-a-vcon) — end to end flow
  {% endtab %}

{% tab title="Business and compliance" %}
You care about consent, regulatory exposure, and AI governance.

* [A vCon Primer](/vcons/a-vcon-primer) — the full story, written for non specialists
* [Privacy Primer](/vcons/privacy-primer) — how vCon handles PII, deletion and lawful basis
* [Lawful Basis extension](/extensions/lawful-basis) — consent that travels with the data
* [PII Compliance use case](/use-cases-studies/pii-compliance) — what this looks like in practice
  {% endtab %}

{% tab title="Developers and engineers" %}
You want to read code and get something running.

* [Conserver Quick Start](/conserver/conserver-quick-start) — a vCon pipeline on your machine
* [vCon Library (Python) Quickstart](/vcon-library/quickstart)
* [vCon JS Library Quickstart](/vcon-js-library/quickstart)
* [vCon Adapters Quick Start](/vcon-adapters/quick-start-from-template) — wire your platform into the ecosystem
* [MCP Server](/mcp-server) — expose vCons to AI agents safely
  {% endtab %}

{% tab title="Standards and policy" %}
You are tracking the working group and the surrounding standards.

* [IETF VCON working group](https://datatracker.ietf.org/group/vcon/about/)
* [Core draft `draft-ietf-vcon-vcon-core`](https://datatracker.ietf.org/doc/draft-ietf-vcon-vcon-core/)
* [SCITT and vCon together](/deep-dives/scitt-supply-chain-integrity-transparency-and-trust)
* [IETF sessions, talks and press](/talks-articles-press)
  {% endtab %}
  {% endtabs %}

## What is in the box

{% tabs %}
{% tab title="The vCon object" %}

<figure><img src="/files/YCoLQzjr0NYhWeTnjcZS" alt=""><figcaption><p>A signed JSON object that carries parties, dialog (recording or transcript), attachments, analyses, consent, and a tamper evident history. The same format works for a phone call, a chat session, a video meeting, or a human to agent conversation.</p></figcaption></figure>
{% endtab %}

{% tab title="The Conserver" %}

<figure><img src="/files/lhyNgQmJOTAL6Vv4dsoO" alt=""><figcaption><p>The open source platform that creates vCons from business systems, runs them through pipelines of links (transcribe, redact, analyze, store, forward), and emits archive copies plus projections into the tools the business already uses.</p></figcaption></figure>
{% endtab %}

{% tab title="MCP server and adapters" %}

<figure><img src="/files/FbuIGvyxi2ja89qScRx2" alt=""><figcaption><p>Applications consume vCons through the Conserver API, the MCP server, or the language libraries. Adapters let CPaaS providers, contact centers, and recording vendors plug their platforms into the ecosystem without inventing a new format.</p></figcaption></figure>
{% endtab %}
{% endtabs %}

## Open, by design

vCon is developed in the open at the IETF, the same standards body that produced the protocols the internet runs on, from TCP/IP and DNS to HTTP, TLS, and SIP. IETF work is rough consensus, running code, and a public record. Anyone can read the drafts, join the list, and challenge a design decision in writing.

The format itself carries no patent encumbrance, by design, the same way PDF and vCard do not. The working group brings together telecom regulators, carriers, hyperscale platforms, and human rights organizations, and public statements from those constituencies appear in [Talks, Articles and Press](/talks-articles-press).

{% embed url="<https://github.com/vcon-dev/vcon>" %}
Open source repository for vCon and the Conserver
{% endembed %}

{% embed url="<https://datatracker.ietf.org/group/vcon/about/>" %}
IETF VCON working group
{% endembed %}

## Keep reading

{% content-ref url="/pages/tWoe8nJgLTVfXkTlN9d7" %}
[A vCon Primer](/vcons/a-vcon-primer)
{% endcontent-ref %}

{% content-ref url="/pages/6OgLwHqRfuxrhLio8dcV" %}
[Why Conversations Need a File](/vcons/why-vcons)
{% endcontent-ref %}

{% content-ref url="/pages/6H3AFSf4P59nbElEqiDz" %}
[Concepts](/vcons/concepts)
{% endcontent-ref %}

{% content-ref url="/pages/txamxOwfZO2dc4BXOLsq" %}
[Conserver Quick Start](/conserver/conserver-quick-start)
{% endcontent-ref %}

{% content-ref url="/pages/SLtbUqEILdKCyibWOsSe" %}
[Overview](/use-cases-studies/overview)
{% endcontent-ref %}


# vCons

Conversations are the last big category of business content that never got a file format. Pictures got JPEG. Documents got PDF. Spreadsheets got XLSX. Contacts got vCard. Conversations got vCon.

A vCon (Virtualized Conversation) is a portable, verifiable container for a single conversation. It is a JSON object that carries the parties, the dialog (text, audio, video, messaging), the recording or transcript, the consent that was given, and the analysis that has been run on it. It can be signed, encrypted, attached to an email, stored to disk, and sent across a network. It is developed as an open standard under the IETF VCON working group, with reference implementations in Python and JavaScript and a growing ecosystem of adapters, stores, and tools.

Two properties of the format are worth highlighting up front. First, consent travels inside the file. When a customer withdraws consent, every downstream copy of the conversation is governed by that withdrawal, not just the system that originally captured it. This is the difference between a consent management system that reports on policy and one that enforces it. Second, vCons can be data reduced. A derived vCon can be produced that contains only the fields you want to share, and the new file points back to the original so the recipient can verify it is real without seeing everything.

The reason this matters now is that agentic AI is moving from demo to production. Agents are starting to talk to customers and to each other, and there is currently no shared way to record what an agent said, on whose behalf, or under what authority. At the same time the line between authentic and synthetic media is collapsing: a recorded conversation injected into an AI pipeline is the conversational equivalent of malware injected into a software supply chain. vCon plus its companion lifecycle work in SCITT gives the industry a verifiable, signed, consent-aware container for conversations so that the trust questions have a place to live.

vCon is past the pilot stage. Implementations are running at hundreds of thousands of conversations per month at the BPO that incubated the technology, at millions per day at a large financial institution that is now also the first production deployment of real-time vCons, and as a routing prototype at a United Way 211 center. Around thirty to forty companies are actively building with vCons today. Telecom and call-center vendors are leaning in first, which is the usual pattern for an open standard.

## Start here

* [💬 A vCon Primer](/vcons/a-vcon-primer) — the longer-form introduction
* [🧠 Why vCons need a file](/vcons/why-vcons) — conversations as durable digital artifacts in the AI era
* [🌎 vCons are...](/vcons/vcons-are...) — six short framings
* [💡 Concepts](/vcons/concepts) — the vocabulary you will see in the spec
* [🔒 Privacy primer](/vcons/privacy-primer) — GDPR, CCPA, consent vocabulary
* [✨ More Information](/vcons/more-information) — drafts, talks, articles, podcasts

## Authoritative links

* [IETF VCON working group](https://datatracker.ietf.org/group/vcon/about/)
* [vCon GitHub repository](https://github.com/vcon-dev/vcon)
* [The Pulver vCon Report](https://thejeffpulver.substack.com/) — the ongoing industry-side commentary
* [A Comprehensive Guide to vCon in Communications](https://www.cavell.com/a-comprehensive-guide-to-vcon-in-communications/) — Cavell analyst overview
* [vCon: The Power of a Definition](https://cpaasaa.com/vcon-the-power-of-a-definition/) — CPaaSAA, on why the file format matters


# A vCon Primer

Thomas McCarthy-Howe, CTO, Strolid.

A [vCon](https://datatracker.ietf.org/group/vcon/about/) is a portable, verifiable container for a conversation.  It is currently on the standards track at the premier Internet standards organization, the [IETF](https://www.ietf.org).   The three core specifications are in preparation for working group last call, after over two years of work from dozens of leading engineers, privacy advocates, regulators and operators. \
\
This page explains what one is, why the idea matters now, where vCons are already in production, what they look like inside, and what they let you do that other formats do not. If you read one piece of vCon documentation, read this.

## What a vCon is

A vCon (virtual conversation) is to a conversation what a PDF is to a document, or a vCard is to a business card. Think of it as a sealed folder for a conversation, carrying who was on the call (parties), what was said (the dialog, as recording or transcript), what they agreed to (consent), the notes added since (analysis), and a stamped record of every set of hands it has passed through (a tamper evident history). Inside the cover it is a signed JSON object. The same shape works for a phone call, a chat session, a video meeting, or a human to agent conversation.

The name traces to a casual remark by Brian Galvin, past CTO of both Genesys and Nuance, asking why there was no vCard equivalent for conversations. vCon is the answer. The technical definition lives in the IETF VCON working group, with the spec target [`draft-ietf-vcon-vcon-core`](https://datatracker.ietf.org/doc/draft-ietf-vcon-vcon-core/) and syntax parameter `"vcon": "0.4.0"` (1).

Like PDF and vCard, vCon is open and carries no intellectual property encumbrance. Data formats cannot be patented in most jurisdictions, and vCon was designed that way on purpose.

## Why this matters now

The original use case was contact center recording. Since 2024 the stakes have widened. Four forces are pushing the same direction at once.

**Agentic AI is moving into production.** Agents are starting to talk to customers and to each other. There is no shared record of what an agent said, on whose behalf, or under what authority. Without that record, there is nothing for a regulator, a customer, or a downstream system to verify against.

**Authentic and synthetic are getting harder to tell apart.** A deepfake injected into an AI pipeline is the conversational analog of malware injected into a software supply chain. vCon pairs with SCITT, the IETF effort for Supply Chain Integrity, Transparency and Trust, so creation, sharing, analysis, and deletion are recorded in an append only ledger that cannot be altered after the fact.

**Consent does not travel today.** Consent typically lives in a privacy policy, a recording disclosure, or a screenshot, separate from the conversation it covers. vCon carries consent inside the file itself, scoped by purpose and time. That is the difference between reporting on a privacy policy and enforcing one.

**The silo model is doubling down.** Proprietary contact center, recording, and AI stacks are extending deeper, on architectures that do not interoperate. Without an open container, every enterprise rebuilds the same data prison in a new color every five years.

vCon is built in the open at the IETF, the standards body responsible for TCP/IP, DNS, HTTP, TLS, and SIP. The process is rough consensus, running code, and a public record. Anyone can read the drafts, join the mailing list, and challenge a design decision in writing.

## Where vCon is running today

vCon is past the pilot stage.

The [BPO that incubated the technology](https://www.strolid.com) runs roughly a quarter million vCon formatted conversations per month through its production pipeline, and that volume has roughly doubled over the past year.

A large financial institution is live with millions of vCon productions per day on a path to a million per hour. That deployment is also the first production instance of real-time vCons, where applications follow a conversation as it happens rather than waiting for the recording.

A [prototype is running at a United Way 211 center](https://frontline.group/frontline-group-launches-vcon-pilot/) where the system listens for context that should change routing. A food-banking question and a sexual-abuse disclosure should not sit in the same queue, and they should not have to wait for tomorrow's manager review to be distinguished.

Dozens of [companies are actively building with vCons today](https://www.pulver.com/members). Telecom, contact center, and CPaaS vendors are leaning in first, which is the usual pattern for an open standard. SIP gave service providers recording. vCon gives them a portable answer for what to do with the recording next.

## Inside a vCon

<div align="right"><figure><img src="/files/YCoLQzjr0NYhWeTnjcZS" alt=""><figcaption><p>The insides of a vCon</p></figcaption></figure></div>

A vCon has five things inside it.

**Dialogues** are the recorded media: audio, video, text, messaging. A vCon can be packed (media inline) for emailing or shipping as one file, or unpacked (media by reference) when the recordings are large enough to live on their own storage.

**Parties** identify who was in the conversation, and who verified their identity. Identity verification is a first class concept, not an afterthought.

**Consent** is carried inside the file, scoped by purpose and duration. When consent is withdrawn or expires, the systems holding the vCon can act on it without consulting an external policy.

**Analysis** holds commentary derived from the dialog: transcription, sentiment, redaction, summarization, model outputs. It is stored as JSON, attachable in layers, and tied to the dialog it refers to.

**Attachments** carry the context the conversation depended on. A sales lead, a CRM record, an inbound form, an authentication challenge, anything that explains why the conversation happened in the first place.

## The hard part

Conversations are simultaneously the most valuable and the most sensitive data a business holds. The value is obvious: every renewal, complaint, sales objection, support edge case, agent error, and customer insight lives in conversation long before it shows up in structured data. Modern ML, agentic AI, compliance audits, and revenue operations all want this material in volume. The sensitivity is just as obvious. Voices and faces are biometric identifiers a customer cannot change. The disclosures inside a conversation routinely include health, finances, family circumstances, and named third parties who never consented to be in the room at all. Treating one side without the other is the trap. Lock the conversations down and the business loses the most important signal it produces. Open them up and the next breach is catastrophic and unrecoverable.

vCon is built to hold both sides at once:

* **Consent** rides inside the file, scoped by purpose and duration, so each downstream system can see what it is allowed to do and refuse to act outside that scope.&#x20;
* **Redaction** is a first class operation, recorded in the analysis layer, so a redacted projection can be produced for one audience while the unredacted original remains controlled and inspectable for another.&#x20;
* **Provenance and integrity** are cryptographic, not procedural, so any version of a vCon can be traced back to who signed it, what was changed, and when.&#x20;
* **SCITT**, the IETF Supply Chain Integrity, Transparency and Trust ledger, records every lifecycle event (creation, sharing, analysis, deletion) in an append only log that downstream auditors can verify on their own without trusting the operator.&#x20;

**None of these mechanisms eliminate the tension**. **They make it manageable, auditable, and verifiable in software, which is the difference between a privacy policy and a privacy posture**.

## What it gets you

* **Privacy and deletion you can actually execute.** vCons make "what data did we capture, and where is it" answerable in a structured way. GDPR style deletion stops being a project and starts being an API call.
* **Audit and provenance.** Every lifecycle event (creation, sharing, analysis, deletion) can be recorded in a SCITT ledger that downstream auditors can verify on their own. The conversation, and what was done to it, are inspectable separately.
* **ML lifecycle hygiene.** When a customer revokes consent or asks for deletion, you need to know which models trained on which conversations. vCons make that traceable, which makes the retraining cost bounded.
* **Consent that travels.** Consent moves with the file. Any system that opens a vCon can see the scope and the expiry, and refuse to act outside them, without phoning home.
* **An interoperable ecosystem.** Because the format is open, independent vendors can supply redaction, validation, transcription, and analytics tools that all read and write the same object. The buyer is not locked into a single stack.

## GDPR, in practice

Every data subject right GDPR grants becomes operable rather than aspirational when conversations live in vCons:

* The **right to be informed** is met by the disclosure stored at capture.&#x20;
* The **right of access** becomes a query against a structured object instead of a hunt across systems.&#x20;
* **Rectification** lands as an additional analysis entry with provenance rather than an overwrite.
* **Erasure** can be issued by the Conserver across every storage location holding the vCon, with the deletion event recorded in SCITT.&#x20;
* **Restriction of processing** follows the consent scope inside the file, which downstream systems can read directly and refuse to act outside of.&#x20;
* **Portability** is the format's defining trait, so a subject access request can return the conversations themselves rather than a flat export.&#x20;
* **The right to object** travels with the file, since revocation of consent propagates rather than waiting on a separate policy.&#x20;
* And for the **rights around automated decision making**, the analysis layer records every model that touched the conversation, so the subject can be told which decisions used their data.

## What to read next

* [vCons are...](/vcons/vcons-are...) for the one paragraph mental model
* [Concepts](/vcons/concepts) for the deeper vocabulary
* [Privacy Primer](/vcons/privacy-primer) for the lawful basis and PII story
* [Conserver Quick Start](/conserver/conserver-quick-start) to run a vCon pipeline on your machine
* [IETF VCON working group](https://datatracker.ietf.org/group/vcon/about/) for the primary spec record

## Foot Notes

1. The IETF VCON working group page is at [datatracker.ietf.org/group/vcon/about/](https://datatracker.ietf.org/group/vcon/about/). The core spec target is [`draft-ietf-vcon-vcon-core`](https://datatracker.ietf.org/doc/draft-ietf-vcon-vcon-core/) with syntax parameter `"vcon": "0.4.0"`.
2. In GDPR terminology, a "natural person" is an individual human being, as opposed to a legal person such as a corporation.


# vCons are...

A Brief Introduction to vCons and the Conserver

## vCons are "PDFs" for Conversations

A vCon describes a conversation that involves a "natural" person. As a simple example, a vCon could be created from the last conversation you had with a customer service agent. Just like PDFs allow you to create and share any written document; vCons allow you to create and share any human conversation. A vCon identifies the people in the call, recordings and transcripts, analysis and supporting attachments, like documents and logs. Implemented in easy to manage JSON, vCons are both encryptable and tamper proof.

## vCons are an Essential Tool in the Management of Personal Data

By defining the contents and participants of a conversation, vCons enable software tools that are able assert the presence, the absence and the authenticity of personal and biometric information. By tracking and consolidating the interactions with external systems with personal data, vCons enable responsible management of "Right to be Forgotten" regulations. vCons are best understood as a functional toolset for fine-grained control of personal information, authenticity, context and ethical treatment of personal data.

## vCons are Open

vCons are both an open source project, it is also an open standards effort supported by the IETF. vCons enable the Internet to work better by defining the interchange standard for our most personal information: the sounds of of voice, and the images of our face. vCons enable the responsible management of personal data, enabling true compliance to customer data protections such as "Right to Know".

## vCons are After the Fact or Real Time

vCons started as an after-the-fact format: an expression of a conversation that already happened, packaged for storage, analysis, and compliance. That is still the most common shape. Increasingly, vCons are also produced and updated in real time, while a conversation is still in progress, so that applications can act on a call as it happens rather than waiting for the recording. A large financial-institution deployment is now running real-time vCons in production at millions per day; the format supports both modes without changing shape.

## vCons Carry Their Own Consent

Consent and lawful basis travel inside the vCon. Sometimes the basis is consent. Sometimes it is a regulatory requirement, such as a stockbroker being required to record a trade call. Either way, the basis is part of the conversation record. When a customer withdraws consent, every downstream copy of the vCon is governed by that withdrawal. This is the difference between a consent management system that reports on policy and one that enforces it.

## vCons are the Trust Layer for AI Conversations

When a conversation feeds an AI pipeline, the pipeline is only as trustworthy as the conversation. A deepfake injected into an AI pipeline is the conversational analog of malware injected into a software supply chain. vCons answer that with signatures and lifecycle:

* vCons can be signed and encrypted, so a recipient can cryptographically verify the sender and detect tampering.
* vCons track external processing, including AI, transcription, redaction, and analysis, supporting an authoritative answer to "what systems have seen this conversation?"
* Combined with [SCITT](/deep-dives/scitt-supply-chain-integrity-transparency-and-trust), vCons get an append-only lifecycle record: every creation, share, analysis, and deletion event is recorded in a way that cannot be quietly altered later.
* vCons and the conserver allow conversations to move across security boundaries while tracking shared data and supporting remote deletion, which lines up with "Right to Know" and "Right to be Forgotten" under GDPR and CCPA.


# Why Conversations Need a File

Conversations have always existed. The durable digital artifact that represents them has not, unlike every other digitized asset like images or documents. That is what vCon is for.

For most of the digital age, conversations have occupied a strange place in technology. They are among the most important things people and organizations create, carrying decisions, relationships, negotiations, explanations, commitments, and trust, yet technically they have rarely existed as coherent digital objects. We built systems to transport conversations, record them, transcribe them, summarize them, analyze them, and increasingly feed them into AI systems, but **we never really built a universal way for the conversation itself to exist as a durable, portable artifact**.

Instead, conversations became fragmented across infrastructure. A recording might live in one platform, transcripts in another, metadata somewhere else, consent records detached, analytics generated independently, and AI summaries copied into CRMs or workflow systems. **Every tool sees a fragment. Very few see the whole thing**. For years this fragmentation was mostly tolerated because extracting value from conversations was still constrained by human attention. Managers reviewed a handful of calls. Compliance teams sampled recordings. Analysts listened selectively. The gaps between systems were inefficient, but survivable.

AI changes that equation completely. Suddenly every conversation becomes potentially searchable, analyzable, governable, and actionable at scale. Organizations are no longer asking software merely to transport conversations; they are asking models and agents to summarize, classify, escalate, recommend, supervise, negotiate, and automate based on them. At that point the absence of a true conversational artifact stops being an inconvenience and starts becoming a structural weakness.

**The problem is not fundamentally that AI hallucinates. The deeper problem is that AI systems are often operating on incomplete reconstructions of conversations rather than the conversations themselves**. Models reason over summaries derived from transcripts disconnected from recordings, metadata detached from provenance, and fragmented context spread across vendors and trust boundaries. When the underlying artifact is broken apart, systems compensate through inference. Sometimes they infer correctly. Sometimes they do not. Increasingly, organizations are automating critical decisions against partial conversational shadows rather than against a shared source of truth.

Other categories of information crossed this threshold decades ago. **Documents became portable** because we standardized document formats. **Calendars became interoperable** because of iCal. **Contacts became exchangeable** because of vCard. Once those things became durable digital artifacts, ecosystems formed around them. Applications could interoperate, organizations could exchange information cleanly, and innovation accelerated because systems no longer needed to reinvent representation every time they communicated. **Conversations never fully made that transition.**

**That is the core idea behind vCon**. vCon proposes that a conversation should exist as a portable, governable, verifiable digital object capable of carrying not only media and transcripts, but also participants, timestamps, metadata, analysis, consent records, signatures, provenance, lifecycle history, and derived intelligence. The goal is not simply to create another container format. It is to define the conversation itself as a first-class digital artifact that can persist across systems without losing its essential meaning.

Once a shared conversational artifact exists, systems begin behaving differently. AI platforms, analytics tools, compliance systems, archives, CRMs, and autonomous agents can all operate on the same underlying object instead of continuously recreating disconnected interpretations of it. That does not eliminate disagreement or analysis, but it grounds those processes in a common conversational record. The conversation stops being operational exhaust scattered across infrastructure and starts becoming durable organizational memory.

**Conserver emerged from that realization. It is not fundamentally a communications platform so much as infrastructure for conversational artifacts**. Once conversations become portable digital objects, organizations need systems capable of ingesting them, enriching them, governing them, routing them, securing them, redacting them, searching them, and feeding them responsibly into AI workflows. In that sense, Conserver represents a shift from communications infrastructure toward conversational infrastructure.

Historically, communications technology optimized transport. SIP established sessions. CPaaS platforms made it easy to initiate calls and messages. Contact center systems optimized routing and recording. But once the interaction ended, the resulting conversation was usually fragmented across proprietary systems and disconnected representations. The industry optimized the connection itself, not the conversational memory created by the connection.

**vCon and Conserver shift the focus toward the conversation as the durable unit**. That shift becomes increasingly important as conversations become one of the primary substrates of AI systems. The effectiveness of AI will not depend only on better models. It will depend on better artifacts: complete context, trustworthy provenance, lifecycle governance, shared structure, and durable memory. Without those things, organizations risk automating against partial context, brittle integrations, detached summaries, and unverifiable interpretations.

This pattern is familiar in the history of infrastructure. First innovation emerges, then standardization follows, and finally ecosystems form around the standard. Electricity existed before standardized plugs. Messaging existed before interoperable internet messaging protocols. Documents existed before universal file formats. In each case, fragmentation limited scale until a portable interface or artifact allowed systems to cooperate across boundaries.

Conversations appear to be entering that same transition now. The industry spent decades building ways for people and systems to talk. AI is forcing the next question: what is the durable artifact produced by all that talking?

**vCon is an attempt to answer that question. Conserver is infrastructure beginning to form around the answer**.


# Concepts

Important Ideas for vCons and the Conserver

## vCon

A vCon is the container for data and information relating to a real-time, human conversation. It is analogous to a \[[vCard](https://datatracker.ietf.org/doc/html/rfc6350)] which enables the definition, interchange and storage of an individual's various points of contact. The current specification is [`draft-ietf-vcon-vcon-core-02`](https://datatracker.ietf.org/doc/draft-ietf-vcon-vcon-core/), with syntax parameter `"vcon": "0.4.0"`. The data contained in a vCon may be derived from any multimedia session, traditional phone call, video conference, SMS or MMS message exchange, webchat or email thread. The data in the container relating to the conversation may include Call Detail Records (CDR), call meta data, participant identity information (e.g. STIR PASSporT), the actual conversational data exchanged (e.g. audio, video, text), realtime or post conversational analysis and attachments of files exchanged during the conversation. A standardized conversation container enables many applications, establishes a common method of storage and interchange, and supports identity, privacy and security efforts

## Conserver

The conserver is a data platform designed to extract conversations from business phone systems, transform them into actionable insights, and send that data into common business tools such as spreadsheets, Salesforce and no code toolsets. An open core product, the conserver enables data engineering teams to supply a reliable source of information for AI, ML and operational software in cloud, premise and hybrid contexts. The core for many of the business cases enabled by the conserver is the smart capture, redaction and lifecycle management of recorded customer conversations and customer journeys, recently accelerated by FTC and GDPR regulations and by increasing investments into AI and ML.

From a system perspective, shown above, the Conserver attaches to information systems like Web Chat and call center queues, and extracts information from them after conversations are ended. This information is then cleaned and transformed into actionable data. For instance, a distributed call center might extract conversations from a group of sales agents, convert them into text, then filter those conversations looking for times when customers objected to a sale. These objections are then pushed into database tables and Google Sheets as a data self-service option for any business team. The conserver supports multiple data pipelines, each one extracting data from a number of systems, performing transformations such as translations, transcriptions and redactions, and then pushing the prepared data into applications to be used.

In contrast to other data platforms, the Conserver is dedicated to managing the particular complexities of real time conversational sources. For instance, the amount of bandwidth and storage required to manage an hour long audio recording is an order of magnitude larger than managing a typical business object like a PDF. However, even this is just a start. Video is a few orders of magnitude greater than that, and the data creation for service providers such as Zoom and Skype are magnitudes of order still greater. From a legal perspective, regulatory compliance for customer data protections are particular for recorded conversations, and require support for tracking data’s use by automations, and for tracking deletion from a “Right to be Forgotten” request.

## Parties

The parties section of the vCon is an array that refers to the people or systems in the conversation. Each "dialog" provides an index into the parties array to identify the people. Each party identifies the

* Network identifier of the party, currently held in the 'tel" field
* The mail adress
* The name of the party
* The role of the party, represented by a string (we use labels like customer, agent)
* A validation field, allowing for evidence of third party validation of the identities of the parties
* Other information, such as civic address, timezone or jCard.

## Dialogs

The dialogs section of the vCon is an array of transcripts and recordings that represent the media of the conversation itself. Each dialog contains an identification of:

* The type of the dialog (recording, transcript)
* The start time
* Duration
* The parties in the conversation
* The originating party of the conversation
* The mimetype of the recording
* Any associated filename

The content of the dialog comes in two flavors: packed and unpacked. Packed data is included in the vCon itself in the body field. Unpacked data is not included in the vCon, but is instead referenced by URL, not necessarily publicly accessible. For both cases, the media of the vCon can be signed to prevent tampering or modification after the vCon is constructed.

## Analysis

The analysis section of the vCon is an array of objects that represents third party analysis of the vCon itself. Examples of Analysis includes:

* Sentiment analysis of the conversation itself
* A list of promises made by the people on the call
* A summary of the conversation

Each analysis captures the vendor, schema and details of the analysis itself. In addition to the value that the analysis provides, this also becomes an accounting of the times and places this conversation has been processed by third parties. This list is critical in compliance to data regulations as it allows data controllers to fulfill their obligations to reporting and removing personal data on demand of people and regulators.

## Attachments

The attachments section of the vCon is an array of objects that are documents, traces and other pieces of data that provides the context of a conversation. For instance, a sales organization may store the lead information in the attachment; a conference call may include the powerpoint that was discussed. Links in the conserver use attachments to store tracing information, such as the raw responses from external systems or the original source of the vCon.

## Encryption and Signing

vCons support encryption of the parent object, and each of the analysis sections can carry encrypted bodies. In addition, external URLs and the vCon itself are signed, allowing for tamper detection of the contents.

## Data Projection

Since the vCon is a nested document, sometimes it is more convenient to format the data in a vCon in other formats, particularly for relational data storage. For instance, a call log link may need to provide a single database row for each vCon into a spreadsheet. A data projection picks and chooses the data inside the vCon to create the spreadsheet row. Normally, data is lost through a projection.

## Privacy and Consent in vCon

Privacy and consent are first-class concerns in vCon: the lawful basis for processing a conversation, and any consent that was given, travel inside the vCon itself, not in a separate system that points at it from the outside. When a customer withdraws consent, every downstream copy of the vCon is governed by that withdrawal.

For the vocabulary behind that work, including GDPR, CCPA, data minimization, sensitive personal data, and the mechanics of consent, see the [Privacy Primer](/vcons/privacy-primer). For how consent and lawful basis are expressed inside a vCon, see the [Lawful Basis extension](/extensions/lawful-basis). For the append-only lifecycle record of what has happened to a vCon, see the [Lifecycle extension](/extensions/lifecycle).


# Privacy Primer

A short orientation to data privacy and communications privacy for people building or reviewing vCon-based systems.

{% hint style="info" %}
**Read the full primer in the IETF draft.** This page is a short orientation. The authoritative version, with full treatment of data subjects' rights, sensitive data, deidentification, communications privacy, AI considerations, and security considerations, lives in [`draft-ietf-vcon-privacy-primer`](https://datatracker.ietf.org/doc/draft-ietf-vcon-privacy-primer/). If you are designing or reviewing a system that handles vCons, read the draft.
{% endhint %}

## Why a primer

The democratization of technology has produced a wave of new entrants in the market for personal data, driven by motives that range from commerce and regulation to fraud prevention and charitable causes. More and more of them touch conversational data as it crosses network boundaries. vCon is one of the artifacts that makes that crossing possible, by giving conversational data a structure that can be processed and shared ethically.

Many of those entrants do not arrive with a working understanding of data minimization, lawful basis for processing, redaction, the right to know, or the right to erasure. The vCon design decisions are a direct response to those concerns: encryption, signing for change detection, redacted versions that retain a verifiable trail back to the original. None of that does any good if the people building and operating the systems do not share a baseline vocabulary. That is what this primer, and the IETF draft behind it, are for.

## Who this is for

The draft is written for three audiences who tend to share the same room at IETF and around vCon work:

* **Engineers and technologists** who live in implementation detail and benefit from seeing the ethical and legal frame their designs sit inside.
* **Regulators, lawyers, and policy staff** who arrive with a constituency in mind and want to understand which of their concerns the vCon framework addresses and which it does not.
* **NGOs**, particularly those working on privacy, security, and human rights, who sit at the intersection of policy and technology and are reasonably skeptical of both commercial and government framings.

If you recognize yourself in one of those groups, the draft is written for you.

## What it covers

The primer is informational, not normative. Its goals are modest:

* Educate a growing audience on responsible handling of personal data, including biometric content inside audio and video.
* Build a shared understanding of what makes that handling hard.
* Be honest about what the vCon framework addresses and what it leaves to law, policy, and operational practice.
* Encourage thoughtful design and review of systems that process personal data.

It is a primer, not a panacea. Much like the distinction between HTTP and HTTPS, the vCon framework gives well-intentioned actors something to build on, while leaving the question of bad actors to the legal system.

## Privacy and vCon — in general

Privacy is sometimes summarized as "the right to be let alone." It helps to think of it in four aspects:

1. Personal information (data) privacy
2. Bodily privacy
3. Territorial privacy
4. Communications privacy

vCon concentrates on the first and the fourth: **data privacy** and **communications privacy**. A recorded conversation is, by construction, both. A voice recording is biometric data. The content can carry health, religious, or political information. The participants are identifiable natural persons. That is why vCon treats encryption, signing, redaction, and lifecycle tracking as first-class concerns rather than optional add-ons.

IETF standards already address privacy in Internet communications, including data minimization ([RFC 7258](https://www.rfc-editor.org/rfc/rfc7258)). They generally do not address the privacy of individuals' data with respect to the organizations that collect, process, and disclose it. The privacy primer extends to that second question.

## Where to go next

* [`draft-ietf-vcon-privacy-primer`](https://datatracker.ietf.org/doc/draft-ietf-vcon-privacy-primer/) — the full primer, including data subjects' rights, what counts as protected and sensitive data, deidentification and anonymization, communications privacy, AI-specific considerations, and security considerations.
* [Lawful Basis extension](/extensions/lawful-basis) — how consent and other legal bases for processing are captured inside a vCon.
* [Lifecycle extension](/extensions/lifecycle) — the append-only record of what has happened to a vCon, and how revocation propagates.


# The Journey of a vCon

An interactive walkthrough of how a vCon moves through the Conserver pipeline — from creation at an adapter through transcription, analysis, and storage.

{% embed url="<https://vcon-dev.github.io/infographics/conveyor.html>" %}


# More Information

Learning More

## Read the current pitch

These three pieces are the cleanest current articulations of what vCon is and why it matters now. Read one and you have the framing.

* [vCon: The Power of a Definition](https://cpaasaa.com/vcon-the-power-of-a-definition/) — Thomas Howe, CPaaSAA, Aug 2025. The "magic of vCons", reality collapse, and SCITT as a global digital notary.
* [The vCon Reality Check: Moving Beyond Generative Hype to Actual Conversational Architecture](https://aheadcrm.medium.com/the-vcon-reality-check-moving-beyond-generative-hype-to-actual-conversational-architecture-41197017fb9b) — Thomas Wieberneit, Medium, April 2026. Analyst framing for why enterprises should care.
* [The Pulver vCon Report](https://thejeffpulver.substack.com/) — Jeff Pulver's ongoing Substack series. Best single subscription for tracking industry adoption.

## Official Documents

Read the IETF working-group draft: [`draft-ietf-vcon-vcon-core-02`](https://datatracker.ietf.org/doc/draft-ietf-vcon-vcon-core/) — the current spec target, with syntax parameter `"vcon": "0.4.0"`.

Track our [group's progress at the IETF](https://datatracker.ietf.org/doc/charter-ietf-vcon/) (charter, meetings, and active drafts).

### Extension drafts

vCon's extension mechanism (`extensions[]` + `must_understand[]` / `critical`) is documented in the core draft. Active extension drafts:

* [`draft-howe-vcon-lawful-basis`](https://datatracker.ietf.org/doc/draft-howe-vcon-lawful-basis/) — lawful basis for processing (GDPR / privacy compliance)
* [`draft-howe-vcon-lifecycle`](https://datatracker.ietf.org/doc/draft-howe-vcon-lifecycle/) — SCITT-based lifecycle and audit trail
* [`draft-howe-vcon-wtf`](https://datatracker.ietf.org/doc/draft-howe-vcon-wtf/) — World Transcription Format (multi-vendor transcription)
* [`draft-howe-vcon-agent-session`](https://datatracker.ietf.org/doc/draft-howe-vcon-agent-session/) — verifiable agent-conversation session trace
* [`draft-howe-vcon-sip-signaling`](https://datatracker.ietf.org/doc/draft-howe-vcon-sip-signaling/) — SIP telephony signaling metadata
* [`draft-howe-vcon-mcp-session`](https://datatracker.ietf.org/doc/draft-howe-vcon-mcp-session/) — MCP / LLM session attachment

See the **Extensions** section of this site for a per-extension overview.

> **Historical note:** The pre-WG draft was `draft-petrie-vcon-01` with syntax `"0.0.2"`. Both have been superseded by the working-group draft above. If you find content elsewhere referencing the old draft or version string, treat it as historical.

## vCon Software and Implementations

Visit our [open source library for the vCon and the Conserver](https://github.com/vcon-dev/vcon). (Give us a star!)

[The Repo for the py-vcon and py-vcon-server Projects](https://github.com/dgpetrie/py-vcon#the-repo-for-the-py-vcon-and-py-vcon-server-projects)

## Videos and Presentations

See the [Keynote at TAD Summit](https://youtu.be/TVq7Y1SoGo4?si=Led6pdqP6rmvynkW), Paris, October, 2023

See the [TADSummit Podcast](https://youtu.be/Ijmvras0DFE?si=5Qs-3VtDK8agW-Ud) with Alan Quayle, September, 2023

See the [Birds of a Feather session at IETF 116, Yokohama](https://youtu.be/EF2OMbo6Qj4), March, 2023

See the [presentation at TADSummit](https://youtu.be/ZBRJ6FcVblc), Portugal, Nov 2022

See the [presentation at IETF 115, London, Nov 2022](https://youtu.be/dJsPzZITr_g?t=243)

See the [presentation at IIT](https://youtu.be/s-pjgpBOQqc), Chicago, Oct 2022

See the [key note proposal for vCons](https://blog.tadsummit.com/2021/12/08/strolid-keynote-vcons/).

## White Papers

Read the [white paper](https://docs.google.com/document/d/1TV8j29knVoOJcZvMHVFDaan0OVfraH_-nrS5gW4-DEA/edit?usp=sharing)

## Ecosystem

A non-exhaustive set of companies, products, and communities building on vCon today. See [Talks, Articles & Press](/talks-articles-press) for the full curated list of writing and recordings.

* [Vconic](https://vconic.com) — Strolid's commercial vCon platform, real-time vCon processing and proof-of-value applications.
* [Strolid: IETF for vCons](https://strolid.com/ietf-for-vcons/) and [Strolid AI Conserver](https://strolid.ai/vcon-conservers/) — the BPO that incubated vCon, running roughly a quarter million conversations per month through the format.
* [MindMaking](https://mindmaking.com/) — vCon app store for service providers, turning calls into structured vCons with transcript, summary, action items, and sentiment.
* [Nimble Ape](https://nimblea.pe/) — Dan Jenkins / CommCon, the open RTC community that hosts early implementer conversations.
* [py-vcon](https://github.com/py-vcon/py-vcon) — Dan Petrie's reference Python implementation, including `py-vcon-server`.
* [vCon Foundation](https://thejeffpulver.substack.com/) — Jeff Pulver's foundation and the rolling commentary on adoption.
* [CPaaS Acceleration Alliance](https://cpaasaa.com/tag/vcon/) — the alliance's ongoing vCon coverage, including the CASA Amsterdam events.
* [Cavell](https://www.cavell.com/a-comprehensive-guide-to-vcon-in-communications/) — analyst overview for the CX / CCaaS audience.
* [Telecom Reseller podcasts](https://telecomreseller.com/category/podcasts/) — Doug Green's running podcast series, including the Pulver, CarrierX, and healthcare-AI episodes.


# vCon Library (Python)

## vCon Python Library

> **Current version:** `vcon` **0.9.4** · Install with `pip install vcon` · [GitHub: vcon-dev/vcon-lib](https://github.com/vcon-dev/vcon-lib) · Targets [`draft-ietf-vcon-vcon-core-02`](https://datatracker.ietf.org/doc/draft-ietf-vcon-vcon-core/), syntax `"0.4.0"`.

### About the Library

The vCon (Virtual Conversation) library is a powerful Python tool designed to capture, structure, and manage conversation data in a standardized format. It provides a robust set of features for creating, manipulating, and analyzing digital representations of conversations, making it particularly useful for applications in customer service, call centers, chat systems, and any scenario where structured conversation data is valuable.

At its core, the vCon library allows you to create vCon objects, which serve as containers for all elements of a conversation. These objects can include multiple parties (participants in the conversation), a series of dialogs (individual messages or utterances), metadata (such as tags for easy categorization), attachments (like transcripts or other related files), and even analysis data (such as sentiment analysis results).

A vCon is the container for data and information relating to a real- time, human conversation. It is analogous to a \[vCard] which enables the definition, interchange and storage of an individual's various points of contact. The data contained in a vCon may be derived from any multimedia session, traditional phone call, video conference, SMS or MMS message exchange, webchat or email thread. The data in the container relating to the conversation may include Call Detail Records (CDR), call meta data, participant identity information (e.g. STIR PASSporT), the actual conversational data exchanged (e.g. audio, video, text), realtime or post conversational analysis and attachments of files exchanged during the conversation. A standardized conversation container enables many applications, establishes a common method of storage and interchange, and supports identity, privacy and security efforts.

Key capabilities of the vCon library include:

1. Creating and managing vCon objects with a flexible, extensible structure.
2. Adding and retrieving conversation participants (parties) with various attributes.
3. Recording and organizing dialog entries with timestamps, content, and sender information.
4. Attaching metadata and tags for easy categorization and searching.
5. Including file attachments related to the conversation.
6. Incorporating analysis data from various sources (e.g., sentiment analysis, topic classification).
7. Signing and verifying vCon objects for data integrity and authenticity.
8. Serializing vCon objects to and from JSON for easy storage and transmission.

The library is designed with extensibility in mind, allowing for easy integration with various analysis tools and systems. It also includes built-in support for handling different types of conversation data, including text, audio, and video.

By providing a standardized way to structure and manage conversation data, the vCon library enables powerful applications in areas such as conversation analytics, quality assurance, compliance monitoring, and machine learning model training for natural language processing tasks.

Whether you're building a customer service platform, a conversation analysis tool, or any application that deals with structured dialog data, the vCon library offers a comprehensive solution for capturing, storing, and working with conversation information in a consistent and powerful way.

### Features

* Create and manipulate vCon objects
* Add parties, dialogs, attachments, and analysis to vCons
* Sign and verify vCons using JWS (JSON Web Signature)
* Generate UUID8 identifiers
* Pack and unpack dialogs
* Add and retrieve tags

### IETF vCon Working Group

The vCon (Virtual Conversation) format is being developed as an open standard through the Internet Engineering Task Force (IETF). The vCon Working Group is focused on creating a standardized format for representing digital conversations across various platforms and use cases.

#### Participating in the Working Group

1. **Join the Mailing List**: Subscribe to the vCon working group mailing list at <vcon@ietf.org>
2. **Review Documents**:
   * Working group documents and drafts can be found at: <https://datatracker.ietf.org/wg/vcon/documents/>
   * The current Internet-Draft can be found at: <https://datatracker.ietf.org/doc/draft-ietf-vcon-vcon-core/>
3. **Attend Meetings**:
   * The working group meets virtually during IETF meetings
   * Meeting schedules and connection details are announced on the mailing list
   * Past meeting materials and recordings are available on the IETF datatracker
4. **Contribute**:
   * Submit comments and suggestions on the mailing list
   * Propose changes through GitHub pull requests
   * Participate in working group discussions
   * Help with implementations and interoperability testing

For more information about the IETF standardization process and how to participate, visit: <https://www.ietf.org/about/participate/>


# Quickstart

The Python vCon library

## vCon Library

A Python library for working with vCon (Virtual Conversation) objects according to the vCon specification.

> **Current version:** `vcon` **0.9.4** (May 2026). Install with `pip install vcon`.
>
> **Spec target:** [`draft-ietf-vcon-vcon-core-02`](https://datatracker.ietf.org/doc/draft-ietf-vcon-vcon-core/) · syntax parameter `"vcon": "0.4.0"`.

### Recent releases

**0.9.4** — Security: dependency bumps for `authlib`, `requests`, `pydash`, `python-dateutil`, `mutagen`, `pypdf`, `pillow`, `uuid6`.

**0.9.3** — Fixed: `Vcon.add_tag()` now correctly includes `party: 0` and `dialog: 0` on the created `tags`-purpose attachment. Previously the attachment was missing these spec-required indices.

**0.9.2** — Spec compliance pass:

* **`build_new()` is now spec-correct out of the box.** Emits `"vcon": "0.4.0"` automatically and no longer initializes empty `group: []` / `redacted: {}` blocks. Older versions required manual fixups.
* **`add_wtf_transcription_analysis()` added** — sibling to `add_wtf_transcription_attachment()` that places the WTF payload into `analysis[]` (the canonical location per the speckit) with `type: "transcription"`, `vendor`, `product`, `schema`, `encoding: "json"`, JSON-stringified body.
* **`add_attachment(encoding="json", ...)` now works.** Earlier reports that JSON-bodied attachments were rejected are stale.
* **`add_wtf_transcription_attachment()` helper** for the World Transcription Format payload. See [WTF Transcription extension](/extensions/wtf-transcription) for placement guidance — the recommended location is `analysis[]` (use `_analysis`), not `attachments[]`.
* **Spec-correct field names.** Pre-0.9.1 code used `appended` and `must_support`; the current spec uses `amended` and `critical` (the latter typically appears as `must_understand[]` at the top level). The library now uses the new names. Stored vCons with old names remain parseable for compatibility; new code should use the spec-correct names.

### Overview

The vCon library provides a complete implementation of the vCon format for representing conversations and related metadata. It supports all features defined in the current vCon specification including:

* **Conversation Management**: Parties, dialogs, attachments, and analysis
* **Contact Information**: Multiple contact methods (tel, email, SIP, DID)
* **Media Support**: Audio, video, text, and image formats
* **Security**: Digital signatures and content hashing
* **Extensibility**: Extensions and `must_understand` (a.k.a. `critical`) fields
* **Location Data**: Civic address information (GEOPRIV)
* **Event Tracking**: Party history with join/drop/hold/mute events
* **Privacy Compliance**: [Lawful Basis](/extensions/lawful-basis) extension for GDPR compliance
* **Transcription Support**: [WTF (World Transcription Format)](/extensions/wtf-transcription) extension for standardized speech-to-text

### Quick Start

#### Basic vCon Creation

```python
from vcon import Vcon
from vcon.party import Party
from vcon.dialog import Dialog
from datetime import datetime, timezone

# Create a new vCon
vcon = Vcon.build_new()

# Add parties
caller = Party(tel="+1234567890", name="Alice", role="caller")
agent = Party(tel="+1987654321", name="Bob", role="agent")
vcon.add_party(caller)
vcon.add_party(agent)

# Add dialog
dialog = Dialog(
    type="text",
    start=datetime.now(timezone.utc),
    parties=[0, 1],
    body="Hello, I need help with my account."
)
vcon.add_dialog(dialog)

# Save to file
vcon.save_to_file("conversation.vcon.json")
print(f"Created vCon: {vcon.uuid}")
```

#### Privacy Compliance with Lawful Basis Extension

```python
from datetime import timedelta

# Add lawful basis for consent
vcon.add_lawful_basis_attachment(
    lawful_basis="consent",
    expiration=(datetime.now(timezone.utc) + timedelta(days=365)).isoformat(),
    purpose_grants=[
        {
            "purpose": "recording",
            "granted": True,
            "granted_at": datetime.now(timezone.utc).isoformat()
        },
        {
            "purpose": "transcription",
            "granted": True,
            "granted_at": datetime.now(timezone.utc).isoformat()
        }
    ],
    party_index=0
)

# Check permissions
can_record = vcon.check_lawful_basis_permission("recording", party_index=0)
print(f"Can record: {can_record}")
```

#### Transcription with WTF Extension

```python
# Add transcription
vcon.add_wtf_transcription_attachment(
    transcript={
        "text": "Hello, I need help with my account.",
        "language": "en",
        "duration": 4.2,
        "confidence": 0.92
    },
    segments=[
        {
            "id": 0,
            "start": 0.0,
            "end": 4.2,
            "text": "Hello, I need help with my account.",
            "confidence": 0.92,
            "speaker": 0
        }
    ],
    metadata={
        "created_at": datetime.now(timezone.utc).isoformat(),
        "processed_at": datetime.now(timezone.utc).isoformat(),
        "provider": "whisper",
        "model": "whisper-1"
    },
    party_index=0,
    dialog_index=0
)

# Export to SRT format
attachments = vcon.find_wtf_attachments(party_index=0)
if attachments:
    from vcon.extensions.wtf import WTFAttachment
    wtf_attachment = WTFAttachment.from_dict(attachments[0]["body"])
    srt_content = wtf_attachment.export_to_srt()
    print("SRT Export:")
    print(srt_content)
```

#### Complete Example with Extensions

```python
# Create comprehensive vCon with extensions
vcon = Vcon.build_new()

# Add parties
caller = Party(tel="+1234567890", name="Alice", role="caller")
agent = Party(tel="+1987654321", name="Bob", role="agent")
vcon.add_party(caller)
vcon.add_party(agent)

# Add recording dialog
dialog = Dialog(
    type="recording",
    start=datetime.now(timezone.utc),
    parties=[0, 1],
    mimetype="audio/mp3"
)
vcon.add_dialog(dialog)

# Add lawful basis for consent
vcon.add_lawful_basis_attachment(
    lawful_basis="consent",
    expiration=(datetime.now(timezone.utc) + timedelta(days=365)).isoformat(),
    purpose_grants=[
        {
            "purpose": "recording",
            "granted": True,
            "granted_at": datetime.now(timezone.utc).isoformat()
        },
        {
            "purpose": "transcription",
            "granted": True,
            "granted_at": datetime.now(timezone.utc).isoformat()
        }
    ],
    party_index=0
)

# Add transcription
vcon.add_wtf_transcription_attachment(
    transcript={
        "text": "Hello, I need help with my account.",
        "language": "en",
        "duration": 4.2,
        "confidence": 0.92
    },
    segments=[
        {
            "id": 0,
            "start": 0.0,
            "end": 4.2,
            "text": "Hello, I need help with my account.",
            "confidence": 0.92,
            "speaker": 0
        }
    ],
    metadata={
        "created_at": datetime.now(timezone.utc).isoformat(),
        "processed_at": datetime.now(timezone.utc).isoformat(),
        "provider": "whisper",
        "model": "whisper-1"
    },
    party_index=0,
    dialog_index=0
)

# Add extensions
vcon.add_extension("lawful_basis")
vcon.add_extension("wtf_transcription")

# Validate extensions
validation_results = vcon.validate_extensions()
print("Extension validation:", validation_results)

# Save vCon
vcon.save_to_file("complete_conversation.vcon.json")
print(f"Created complete vCon with extensions: {vcon.uuid}")
```

### Key Features

This library implements the latest vCon specification with the following features:

#### Enhanced Party Information

```python
from vcon import Vcon, Party

# Create a party with enhanced contact information
party = Party(
    tel="+1234567890",
    name="John Doe",
    sip="sip:john@example.com",
    did="did:example:123456789abcdef",
    jCard={
        "fn": "John Doe",
        "tel": "+1234567890",
        "email": "john@example.com"
    },
    timezone="America/New_York"
)
```

#### Extensions and Must-Support

```python
vcon = Vcon.build_new()

# Add extensions used in this vCon
vcon.add_extension("video")
vcon.add_extension("encryption")

# Add extensions that must be supported
vcon.add_must_support("encryption")

print(vcon.get_extensions())  # ['video', 'encryption']
print(vcon.get_must_support())  # ['encryption']
```

#### Enhanced Dialog Support

```python
from vcon import Dialog
from datetime import datetime

# Create dialog with new fields
dialog = Dialog(
    type="text",
    start=datetime.now(),
    parties=[0, 1],
    session_id="session-12345",
    content_hash="c8d3d67f662a787e96e74ccb0a77803138c0f13495a186ccbde495c57c385608",
    application="chat-app",
    message_id="<message-id@example.com>"
)
```

#### Party History Events

```python
from vcon import PartyHistory
from datetime import datetime

# Track party events
history = [
    PartyHistory(0, "join", datetime.now()),
    PartyHistory(1, "join", datetime.now()),
    PartyHistory(0, "hold", datetime.now()),
    PartyHistory(0, "unhold", datetime.now()),
    PartyHistory(1, "drop", datetime.now())
]
```

#### Disposition Values for Incomplete Dialogs

```python
# Create incomplete dialog with proper disposition
incomplete_dialog = Dialog(
    type="incomplete",
    start=datetime.now(),
    parties=[0],
    disposition="no-answer"  # Valid: no-answer, congestion, failed, busy, hung-up, voicemail-no-message
)
```

#### Civic Address Support

```python
from vcon import CivicAddress

# Create civic address with GEOPRIV fields
address = CivicAddress(
    country="US",
    a1="CA",
    a3="San Francisco",
    sts="Market Street",
    hno="123",
    pc="94102"
)

party = Party(name="Jane", civicaddress=address)
```

#### Lawful Basis Extension (Privacy Compliance)

```python
from datetime import datetime, timezone, timedelta

# Add lawful basis for GDPR compliance
vcon.add_lawful_basis_attachment(
    lawful_basis="consent",
    expiration=(datetime.now(timezone.utc) + timedelta(days=365)).isoformat(),
    purpose_grants=[
        {
            "purpose": "recording",
            "granted": True,
            "granted_at": datetime.now(timezone.utc).isoformat()
        },
        {
            "purpose": "analysis",
            "granted": True,
            "granted_at": datetime.now(timezone.utc).isoformat(),
            "conditions": ["anonymized_data_only"]
        }
    ],
    party_index=0
)

# Check permissions
can_record = vcon.check_lawful_basis_permission("recording", party_index=0)
can_analyze = vcon.check_lawful_basis_permission("analysis", party_index=0)

# Find lawful basis attachments
attachments = vcon.find_lawful_basis_attachments(party_index=0)
```

#### WTF Extension (Transcription Support)

```python
# Add standardized transcription
vcon.add_wtf_transcription_attachment(
    transcript={
        "text": "Hello, this is a test transcription.",
        "language": "en",
        "duration": 3.5,
        "confidence": 0.95
    },
    segments=[
        {
            "id": 0,
            "start": 0.0,
            "end": 1.5,
            "text": "Hello, this is",
            "confidence": 0.95,
            "speaker": 0
        },
        {
            "id": 1,
            "start": 1.5,
            "end": 3.5,
            "text": "a test transcription.",
            "confidence": 0.94,
            "speaker": 0
        }
    ],
    metadata={
        "created_at": datetime.now(timezone.utc).isoformat(),
        "processed_at": datetime.now(timezone.utc).isoformat(),
        "provider": "whisper",
        "model": "whisper-1"
    },
    party_index=0,
    dialog_index=0
)

# Export to subtitle formats
attachments = vcon.find_wtf_attachments(party_index=0)
if attachments:
    from vcon.extensions.wtf import WTFAttachment
    wtf_attachment = WTFAttachment.from_dict(attachments[0]["body"])
    
    # Export to SRT format
    srt_content = wtf_attachment.export_to_srt()
    
    # Export to WebVTT format
    vtt_content = wtf_attachment.export_to_vtt()
```

#### Provider Data Conversion

```python
from vcon.extensions.wtf import WhisperAdapter, DeepgramAdapter

# Convert Whisper data to WTF format
whisper_data = {
    "text": "Hello world from Whisper",
    "segments": [
        {
            "start": 0.0,
            "end": 2.0,
            "text": "Hello world from Whisper"
        }
    ]
}

whisper_adapter = WhisperAdapter()
wtf_attachment = whisper_adapter.convert(whisper_data)

# Add to vCon
vcon.add_wtf_transcription_attachment(
    transcript=wtf_attachment.transcript.to_dict(),
    segments=[segment.to_dict() for segment in wtf_attachment.segments],
    metadata=wtf_attachment.metadata.to_dict()
)
```

### Installation

```bash
pip install vcon
```

### Basic Usage

#### Creating a vCon

```python
from vcon import Vcon, Party, Dialog
from datetime import datetime

# Create a new vCon
vcon = Vcon.build_new()

# Add parties
alice = Party(tel="+1234567890", name="Alice", role="caller")
bob = Party(tel="+1987654321", name="Bob", role="agent")

vcon.add_party(alice)
vcon.add_party(bob)

# Add dialog
dialog = Dialog(
    type="text",
    start=datetime.now(),
    parties=[0, 1],
    body="Hello, this is a test message!"
)

vcon.add_dialog(dialog)

# Save to file
vcon.save_to_file("conversation.vcon.json")
```

#### Loading a vCon

```python
# Load from file
vcon = Vcon.load("conversation.vcon.json")

# Load from URL
vcon = Vcon.load("https://example.com/conversation.vcon.json")
```

#### Validation

```python
# Validate a vCon
is_valid, errors = vcon.is_valid()

if is_valid:
    print("vCon is valid")
else:
    print("Validation errors:", errors)

# Validate from file
is_valid, errors = Vcon.validate_file("conversation.vcon.json")
```

### Media Support

#### Audio and Video

```python
# Add audio recording
audio_dialog = Dialog(
    type="recording",
    start=datetime.now(),
    parties=[0, 1],
    url="https://example.com/recording.wav",
    mimetype="audio/x-wav"
)

# Add video with metadata
video_dialog = Dialog(
    type="video",
    start=datetime.now(),
    parties=[0, 1],
    url="https://example.com/video.mp4",
    mimetype="video/mp4",
    resolution="1920x1080",
    frame_rate=30.0,
    codec="H.264"
)
```

#### Supported Media Types

**Audio**: `audio/x-wav`, `audio/x-mp3`, `audio/x-mp4`, `audio/ogg` **Video**: `video/x-mp4`, `video/ogg` **Text**: `text/plain` **Multipart**: `multipart/mixed`

### Security Features

#### Digital Signatures

```python
from cryptography.hazmat.primitives import serialization

# Generate key pair
private_key, public_key = Vcon.generate_key_pair()

# Sign the vCon
vcon.sign(private_key)

# Verify signature
is_valid = vcon.verify(public_key)
```

#### Content Hashing

```python
# Calculate content hash for external files
content_hash = dialog.calculate_content_hash("sha256")

# Verify content integrity
is_valid = dialog.verify_content_hash(expected_hash, "sha256")
```

### Advanced Features

#### Property Handling

```python
# Strict mode - only allow standard properties
vcon = Vcon.load("file.json", property_handling="strict")

# Meta mode - move non-standard properties to meta object
vcon = Vcon.load("file.json", property_handling="meta")

# Default mode - keep all properties
vcon = Vcon.load("file.json", property_handling="default")
```

#### Transfer Dialogs

```python
# Create transfer dialog
transfer_data = {
    "transferee": 0,
    "transferor": 1,
    "transfer_target": 2,
    "original": 0,
    "target_dialog": 1
}

vcon.add_transfer_dialog(
    start=datetime.now(),
    transfer_data=transfer_data,
    parties=[0, 1, 2]
)
```

#### Analysis Data

```python
# Add analysis
vcon.add_analysis(
    type="sentiment",
    dialog=0,
    vendor="example-vendor",
    body={"sentiment": "positive", "confidence": 0.95},
    encoding="json"
)
```

### Specification Compliance

This library implements the latest vCon specification with:

* ✅ All required fields and validation
* ✅ Proper media type support
* ✅ Civic address (GEOPRIV) compliance
* ✅ Party history event tracking
* ✅ Transfer dialog support
* ✅ Content hashing and security
* ✅ Extensions and must\_support
* ✅ Flexible versioning (version field is optional)
* ✅ Backward compatibility
* ✅ **Lawful Basis Extension** - GDPR-compliant privacy management
* ✅ **WTF Extension** - World Transcription Format for standardized speech-to-text
* ✅ **Extension Framework** - Comprehensive validation and processing
* ✅ **Multi-Provider Support** - Whisper, Deepgram, AssemblyAI, and more
* ✅ **Export Capabilities** - SRT and WebVTT subtitle formats

### Testing

Run the test suite:

```bash
pytest tests/
```

All tests pass, covering:

* Basic functionality
* Enhanced vCon features
* Validation and error handling
* Media type support
* Security features
* Flexible versioning
* Backward compatibility
* **Extension Framework** - Lawful Basis and WTF extensions
* **Privacy Compliance** - GDPR-compliant consent management
* **Transcription Support** - Multi-provider transcription handling
* **Provider Adapters** - Data conversion and validation
* **Export Functionality** - SRT and WebVTT export testing

### Extension Framework

The vCon library includes a comprehensive extension framework that allows for standardized implementation of additional functionality:

#### Available Extensions

* **Lawful Basis Extension** - GDPR-compliant privacy management and consent tracking
* **WTF Extension** - World Transcription Format for standardized speech-to-text data

#### Extension Features

* **Validation Framework** - Comprehensive validation for all extension data
* **Processing Framework** - Standardized processing and analysis
* **Provider Adapters** - Automatic conversion from provider-specific formats
* **Export Capabilities** - Multiple export formats (SRT, WebVTT)
* **Permission Management** - Granular permission checking and validation

#### Extension Usage

```python
# Validate all extensions
validation_results = vcon.validate_extensions()

# Process all extensions
processing_results = vcon.process_extensions()

# Check specific permissions
can_record = vcon.check_lawful_basis_permission("recording", party_index=0)

# Export transcriptions
attachments = vcon.find_wtf_attachments(party_index=0)
```

### Remaining quirks (0.9.4)

The library is broadly spec-compliant as of 0.9.4. Most historical quirks (manual syntax-param setting, empty `group`/`redacted` placeholders, `add_attachment` rejecting JSON, `add_tag` missing indices) were fixed across 0.9.2 and 0.9.3. Two edges still need attention:

1. **`subject` has no setter.** Write via the dict:

   ```python
   v.vcon_dict["subject"] = "Refund request"
   ```
2. **Lawful Basis: easier to build by hand.** `add_lawful_basis_attachment()` requires model objects for `purpose_grants` and `proof_mechanisms`. Building the attachment dict directly per [`draft-howe-vcon-lawful-basis`](https://datatracker.ietf.org/doc/draft-howe-vcon-lawful-basis/) and appending to `vcon_dict["attachments"]` is usually simpler — then call `v.add_extension("lawful_basis")`. See [Lawful Basis extension](/extensions/lawful-basis).

If you're scaffolding a new adapter, the [`vcon-adapter-template`](https://github.com/vcon-dev/vcon-adapter-template) repo wraps both of these (and the syntax-param default for older library versions) in a single `new_vcon()` helper. See the [adapter Quick Start](/vcon-adapters/quick-start-from-template).

### Field-name migration (pre-0.9.1 → 0.9.2)

If you're upgrading from pre-0.9.1 code or working with vCons produced by older libraries, you'll encounter two renamed fields:

| Old name       | New name                          | Where                                                        |
| -------------- | --------------------------------- | ------------------------------------------------------------ |
| `appended`     | `amended`                         | Top-level vCon, mutually exclusive with `redacted` / `group` |
| `must_support` | `critical` (or `must_understand`) | Inside `extensions[]` declarations                           |

The library reads both forms but writes the new ones. If you have downstream tooling that depends on the old names, update it; both `vcon-mcp` (database migration `20251120150100_field_renames.sql`) and `vcon-server` have already moved.

### Documentation

* **API Reference** - Complete API documentation
* **User Guide** - Comprehensive usage guide
* **LLM Guide** - Guide for AI-assisted development
* **Migration Guide** - Upgrading from older versions

### License

This project is licensed under the MIT License - see the LICENSE file for details.


# Library API Reference

Complete API documentation for the vCon Python library (v0.9.4), targeting draft-ietf-vcon-vcon-core-02 with syntax parameter "0.4.0".

## vCon Library API Reference

Complete API documentation for the vCon library (**v0.9.4**) — a Python implementation of [`draft-ietf-vcon-vcon-core-02`](https://datatracker.ietf.org/doc/draft-ietf-vcon-vcon-core/) for Virtual Conversation objects. See the [Quickstart](/vcon-library/quickstart) for recent-release notes and the remaining quirks list.

### Overview

The vCon library provides a complete Python implementation of the latest vCon specification for representing virtual conversations. It supports all features including parties, dialogs, attachments, analysis, digital signatures, extensibility, and advanced extensions for lawful basis management and standardized transcription formats.

### Installation

```bash
pip install vcon
```

For image processing support:

```bash
pip install vcon[image]
```

### Core Classes

#### Vcon Class

The main class for working with vCon objects.

**Constructor**

```python
Vcon(vcon_dict: Dict[str, Any] = None, property_handling: str = "default")
```

**Parameters:**

* `vcon_dict` (Dict\[str, Any], optional): Dictionary representing a vCon. Defaults to empty dict.
* `property_handling` (str): How to handle non-standard properties:
  * `"default"`: Keep non-standard properties (default)
  * `"strict"`: Remove non-standard properties
  * `"meta"`: Move non-standard properties to meta object

**Class Methods**

**`build_new() -> Vcon`**

Create a new vCon object with default values.

```python
vcon = Vcon.build_new()
```

**`build_from_json(json_str: str, property_handling: str = "default") -> Vcon`**

Create a vCon object from JSON string.

```python
vcon = Vcon.build_from_json('{"uuid": "123", "created_at": "2024-01-01T00:00:00Z"}')
```

**`load(file_path_or_url: str, property_handling: str = "default") -> Vcon`**

Load a vCon from file or URL.

```python
# From file
vcon = Vcon.load("conversation.vcon.json")

# From URL
vcon = Vcon.load("https://example.com/conversation.vcon.json")
```

**`load_from_file(file_path: str, property_handling: str = "default") -> Vcon`**

Load a vCon from a local file.

**`load_from_url(url: str, property_handling: str = "default") -> Vcon`**

Load a vCon from a URL.

**`validate_file(file_path: str) -> Tuple[bool, List[str]]`**

Validate a vCon file.

```python
is_valid, errors = Vcon.validate_file("conversation.vcon.json")
```

**`validate_json(json_str: str) -> Tuple[bool, List[str]]`**

Validate a vCon JSON string.

**`generate_key_pair() -> Tuple[rsa.RSAPrivateKey, rsa.RSAPublicKey]`**

Generate RSA key pair for digital signatures.

```python
private_key, public_key = Vcon.generate_key_pair()
```

**`uuid8_domain_name(domain_name: str) -> str`**

Generate UUID8 with domain name.

**`uuid8_time(custom_c_62_bits: int) -> str`**

Generate UUID8 with custom time bits.

**Instance Methods**

**Party Management**

**`add_party(party: Party) -> None`**

Add a party to the vCon.

```python
party = Party(tel="+1234567890", name="Alice", role="caller")
vcon.add_party(party)
```

**`find_party_index(by: str, val: str) -> Optional[int]`**

Find party index by field value.

```python
index = vcon.find_party_index("tel", "+1234567890")
```

**Dialog Management**

**`add_dialog(dialog: Dialog) -> None`**

Add a dialog to the vCon.

```python
dialog = Dialog(type="text", start=datetime.now(), parties=[0, 1], body="Hello")
vcon.add_dialog(dialog)
```

**`find_dialog(by: str, val: str) -> Optional[Dialog]`**

Find dialog by field value.

```python
dialog = vcon.find_dialog("type", "text")
```

**`find_dialogs_by_type(type: str) -> List[Dict[str, Any]]`**

Find all dialogs of a specific type.

```python
text_dialogs = vcon.find_dialogs_by_type("text")
```

**`add_transfer_dialog(start: Union[datetime, str], transfer_data: Dict[str, Any], parties: List[int]) -> None`**

Add a transfer dialog.

```python
transfer_data = {
    "transferee": 0,
    "transferor": 1,
    "transfer_target": 2
}
vcon.add_transfer_dialog(datetime.now(), transfer_data, [0, 1, 2])
```

**`add_incomplete_dialog(start: Union[datetime, str], disposition: str, parties: List[int]) -> None`**

Add an incomplete dialog.

```python
vcon.add_incomplete_dialog(datetime.now(), "no-answer", [0])
```

**Attachment Management**

**`add_attachment(type: str, body: Any, encoding: str = "none") -> Attachment`**

Add an attachment to the vCon.

```python
attachment = vcon.add_attachment("transcript", "Full conversation...", "none")
```

**`add_image(image_path: str, type: str = "image") -> Attachment`**

Add an image attachment from file.

```python
attachment = vcon.add_image("screenshot.png", "screenshot")
```

**`find_attachment_by_type(type: str) -> Optional[Dict[str, Any]]`**

Find attachment by type.

```python
transcript = vcon.find_attachment_by_type("transcript")
```

**Analysis Management**

**`add_analysis(type: str, dialog: Union[int, List[int]], vendor: str, body: Any, encoding: str = "none") -> None`**

Add analysis data to the vCon.

```python
vcon.add_analysis(
    type="sentiment",
    dialog=[0, 1],
    vendor="SentimentAnalyzer",
    body={"sentiment": "positive", "confidence": 0.85},
    encoding="json"
)
```

**`find_analysis_by_type(type: str) -> Optional[Dict[str, Any]]`**

Find analysis by type.

```python
sentiment = vcon.find_analysis_by_type("sentiment")
```

**Tag Management**

**`add_tag(tag_name: str, tag_value: str) -> None`**

Add a tag to the vCon.

```python
vcon.add_tag("customer_id", "12345")
```

**`get_tag(tag_name: str) -> Optional[str]`**

Get a tag value.

```python
customer_id = vcon.get_tag("customer_id")
```

**Extension Management**

**`add_extension(extension: str) -> None`**

Add an extension to the vCon.

```python
vcon.add_extension("video")
```

**`get_extensions() -> List[str]`**

Get list of extensions.

```python
extensions = vcon.get_extensions()
```

**`remove_extension(extension: str) -> None`**

Remove an extension.

```python
vcon.remove_extension("video")
```

**`add_must_support(extension: str) -> None`**

Add a must-support extension.

```python
vcon.add_must_support("encryption")
```

**`get_must_support() -> List[str]`**

Get list of must-support extensions.

```python
must_support = vcon.get_must_support()
```

**`remove_must_support(extension: str) -> None`**

Remove a must-support extension.

**Extension-Specific Methods**

**`add_lawful_basis_attachment(lawful_basis: str, expiration: str, purpose_grants: list, party_index: Optional[int] = None, dialog_index: Optional[int] = None, **kwargs) -> None`**

Add a lawful basis attachment to the vCon.

```python
vcon.add_lawful_basis_attachment(
    lawful_basis="consent",
    expiration="2026-01-01T00:00:00Z",
    purpose_grants=[
        {"purpose": "recording", "granted": True, "granted_at": "2025-01-01T00:00:00Z"}
    ],
    party_index=0
)
```

**`add_wtf_transcription_attachment(transcript: Dict[str, Any], segments: List[Dict[str, Any]], metadata: Dict[str, Any], party_index: Optional[int] = None, dialog_index: Optional[int] = None, **kwargs) -> None`**

Add a WTF transcription as an attachment. The canonical placement is `analysis[]` — use `add_wtf_transcription_analysis()` (below) instead for new code. This helper remains for backwards compatibility.

```python
vcon.add_wtf_transcription_attachment(
    transcript={"text": "Hello world", "language": "en", "duration": 2.0, "confidence": 0.95},
    segments=[{"id": 0, "start": 0.0, "end": 2.0, "text": "Hello world", "confidence": 0.95}],
    metadata={"created_at": "2025-01-01T00:00:00Z", "provider": "whisper", "model": "whisper-1"}
)
```

**`add_wtf_transcription_analysis(transcript: Dict[str, Any], segments: List[Dict[str, Any]], metadata: Dict[str, Any], dialog_index: Optional[int] = None, **kwargs) -> None`** *(new in 0.9.2)*

Add a WTF transcription as an `analysis[]` entry — the canonical placement per the [WTF Transcription extension](/extensions/wtf-transcription). Emits `type: "transcription"`, `vendor` and `product` derived from `metadata`, `schema` set to the WTF draft URL, `encoding: "json"`, with the JSON-stringified body. Prefer this over the `_attachment` form for new adapters.

```python
vcon.add_wtf_transcription_analysis(
    transcript={"text": "Hello world", "language": "en", "duration": 2.0, "confidence": 0.95},
    segments=[{"id": 0, "start": 0.0, "end": 2.0, "text": "Hello world", "confidence": 0.95}],
    metadata={"created_at": "2025-01-01T00:00:00Z", "provider": "whisper", "model": "whisper-1"},
    dialog_index=0,
)
```

**`find_lawful_basis_attachments(party_index: Optional[int] = None) -> List[Dict[str, Any]]`**

Find lawful basis attachments in the vCon.

```python
attachments = vcon.find_lawful_basis_attachments(party_index=0)
```

**`find_wtf_attachments(party_index: Optional[int] = None) -> List[Dict[str, Any]]`**

Find WTF transcription attachments in the vCon.

```python
attachments = vcon.find_wtf_attachments(party_index=0)
```

**`check_lawful_basis_permission(purpose: str, party_index: Optional[int] = None) -> bool`**

Check if permission is granted for a specific purpose.

```python
has_permission = vcon.check_lawful_basis_permission("recording", party_index=0)
```

**`validate_extensions() -> Dict[str, Any]`**

Validate all extensions in the vCon.

```python
results = vcon.validate_extensions()
```

**`process_extensions() -> Dict[str, Any]`**

Process all extensions in the vCon.

```python
results = vcon.process_extensions()
```

**Security**

**`sign(private_key: Union[rsa.RSAPrivateKey, bytes]) -> None`**

Sign the vCon with a private key.

```python
vcon.sign(private_key)
```

**`verify(public_key: Union[rsa.RSAPublicKey, bytes]) -> bool`**

Verify the vCon signature.

```python
is_valid = vcon.verify(public_key)
```

**Validation**

**`is_valid() -> Tuple[bool, List[str]]`**

Validate the vCon object.

```python
is_valid, errors = vcon.is_valid()
```

**Serialization**

**`to_json() -> str`**

Convert vCon to JSON string.

```python
json_str = vcon.to_json()
```

**`to_dict() -> Dict[str, Any]`**

Convert vCon to dictionary.

```python
vcon_dict = vcon.to_dict()
```

**`dumps() -> str`**

Alias for `to_json()`.

**`save_to_file(file_path: str) -> None`**

Save vCon to file.

```python
vcon.save_to_file("conversation.vcon.json")
```

**HTTP Operations**

**`post_to_url(url: str, headers: Optional[Dict[str, str]] = None) -> requests.Response`**

Post vCon to URL.

```python
response = vcon.post_to_url("https://api.example.com/vcon", headers={"Authorization": "Bearer token"})
```

**Timestamp Management**

**`set_created_at(created_at: Union[str, datetime]) -> None`**

Set the creation timestamp.

**`set_updated_at(timestamp: Union[str, datetime]) -> None`**

Set the update timestamp.

**Properties**

**`uuid -> str`**

Get the vCon UUID.

**`vcon -> Optional[str]`**

Get the vCon version (optional field).

**`subject -> Optional[str]`**

Get the vCon subject.

**`created_at`**

Get the creation timestamp.

**`updated_at`**

Get the update timestamp.

**`redacted`**

Get the redacted flag.

**`appended`**

Get the appended flag.

**`group`**

Get the group information.

**`meta`**

Get the metadata.

**`parties -> List[Party]`**

Get list of parties.

**`dialog -> List[Dict[str, Any]]`**

Get list of dialogs.

**`attachments -> List[Dict[str, Any]]`**

Get list of attachments.

**`analysis -> List[Dict[str, Any]]`**

Get list of analysis data.

**`tags -> Optional[Dict[str, Any]]`**

Get all tags.

#### Party Class

Represents a participant in a vCon conversation.

**Constructor**

```python
Party(
    tel: Optional[str] = None,
    stir: Optional[str] = None,
    mailto: Optional[str] = None,
    name: Optional[str] = None,
    validation: Optional[str] = None,
    gmlpos: Optional[str] = None,
    civicaddress: Optional[CivicAddress] = None,
    uuid: Optional[str] = None,
    role: Optional[str] = None,
    contact_list: Optional[str] = None,
    meta: Optional[dict] = None,
    sip: Optional[str] = None,
    did: Optional[str] = None,
    jCard: Optional[dict] = None,
    timezone: Optional[str] = None,
    **kwargs
)
```

**Parameters:**

* `tel` (str, optional): Telephone number
* `stir` (str, optional): STIR identifier
* `mailto` (str, optional): Email address
* `name` (str, optional): Display name
* `validation` (str, optional): Validation information
* `gmlpos` (str, optional): GML position coordinates
* `civicaddress` (CivicAddress, optional): Civic address information
* `uuid` (str, optional): Unique identifier
* `role` (str, optional): Role in conversation (e.g., "caller", "agent")
* `contact_list` (str, optional): Contact list reference
* `meta` (dict, optional): Additional metadata
* `sip` (str, optional): SIP URI for VoIP communication
* `did` (str, optional, **deprecated in spec 0.4.0**): Decentralized Identifier. The field was removed from the core spec in `draft-ietf-vcon-vcon-core-02`; the library still accepts it for backwards compatibility with older vCons but new adapters should not emit it.
* `jCard` (dict, optional): vCard format contact information
* `timezone` (str, optional): Party's timezone

**Methods**

**`to_dict() -> Dict[str, Any]`**

Convert Party to dictionary.

```python
party_dict = party.to_dict()
```

#### Dialog Class

Represents a dialog segment in a vCon conversation.

**Constructor**

```python
Dialog(
    type: str,
    start: Union[datetime, str],
    parties: List[int],
    originator: Optional[int] = None,
    mimetype: Optional[str] = None,
    filename: Optional[str] = None,
    body: Optional[str] = None,
    encoding: Optional[str] = None,
    url: Optional[str] = None,
    alg: Optional[str] = None,
    signature: Optional[str] = None,
    disposition: Optional[str] = None,
    party_history: Optional[List[PartyHistory]] = None,
    transferee: Optional[int] = None,
    transferor: Optional[int] = None,
    transfer_target: Optional[int] = None,
    original: Optional[int] = None,
    consultation: Optional[int] = None,
    target_dialog: Optional[int] = None,
    campaign: Optional[str] = None,
    interaction: Optional[str] = None,
    skill: Optional[str] = None,
    duration: Optional[float] = None,
    meta: Optional[dict] = None,
    metadata: Optional[Dict[str, Any]] = None,
    transfer: Optional[Dict[str, Any]] = None,
    signaling: Optional[Dict[str, Any]] = None,
    resolution: Optional[str] = None,
    frame_rate: Optional[float] = None,
    codec: Optional[str] = None,
    bitrate: Optional[int] = None,
    thumbnail: Optional[str] = None,
    session_id: Optional[str] = None,
    content_hash: Optional[str] = None,
    application: Optional[str] = None,
    message_id: Optional[str] = None,
    **kwargs
)
```

**Parameters:**

* `type` (str): Dialog type ("text", "recording", "transfer", "incomplete", "audio", "video")
* `start` (Union\[datetime, str]): Start time
* `parties` (List\[int]): List of party indices
* `originator` (int, optional): Originator party index
* `mimetype` (str, optional): MIME type of content
* `filename` (str, optional): Filename
* `body` (str, optional): Content body
* `encoding` (str, optional): Content encoding
* `url` (str, optional): External URL
* `alg` (str, optional): Signature algorithm
* `signature` (str, optional): Content signature
* `disposition` (str, optional): Disposition for incomplete dialogs
* `party_history` (List\[PartyHistory], optional): Party event history
* `transferee` (int, optional): Transferee party index
* `transferor` (int, optional): Transferor party index
* `transfer_target` (int, optional): Transfer target party index
* `original` (int, optional): Original dialog index
* `consultation` (int, optional): Consultation dialog index
* `target_dialog` (int, optional): Target dialog index
* `campaign` (str, optional): Campaign identifier
* `interaction` (str, optional): Interaction identifier
* `skill` (str, optional): Skill identifier
* `duration` (float, optional): Dialog duration
* `meta` (dict, optional): Additional metadata
* `metadata` (Dict\[str, Any], optional): Structured metadata
* `transfer` (Dict\[str, Any], optional): Transfer-specific information
* `signaling` (Dict\[str, Any], optional): Signaling information
* `resolution` (str, optional): Video resolution (e.g., "1920x1080")
* `frame_rate` (float, optional): Video frame rate
* `codec` (str, optional): Video codec
* `bitrate` (int, optional): Video bitrate
* `thumbnail` (str, optional): Base64-encoded thumbnail
* `session_id` (str, optional): Session identifier
* `content_hash` (str, optional): Content hash for external files
* `application` (str, optional): Application identifier
* `message_id` (str, optional): Message identifier

**Methods**

**`to_dict() -> Dict[str, Any]`**

Convert Dialog to dictionary.

**`add_external_data(url: str, filename: str, mimetype: str) -> None`**

Add external data to dialog.

**`add_inline_data(body: str, filename: str, mimetype: str) -> None`**

Add inline data to dialog.

**`is_external_data() -> bool`**

Check if dialog has external data.

**`is_inline_data() -> bool`**

Check if dialog has inline data.

**`is_text() -> bool`**

Check if dialog is text type.

**`is_recording() -> bool`**

Check if dialog is recording type.

**`is_transfer() -> bool`**

Check if dialog is transfer type.

**`is_incomplete() -> bool`**

Check if dialog is incomplete type.

**`is_audio() -> bool`**

Check if dialog has audio content.

**`is_video(content_type: Optional[str] = None) -> bool`**

Check if dialog has video content.

**`is_email() -> bool`**

Check if dialog is email type.

**`is_image() -> bool`**

Check if dialog has image content.

**`is_pdf() -> bool`**

Check if dialog has PDF content.

**`add_video_data(video_data, filename: Optional[str] = None, mimetype: Optional[str] = None, inline: bool = True, metadata: Optional[dict] = None) -> None`**

Add video data to dialog.

**`extract_video_metadata(video_path: Optional[str] = None) -> dict`**

Extract video metadata using FFmpeg.

**`generate_thumbnail(timestamp: float = 0.0, width: int = 320, height: int = 240, quality: int = 90) -> bytes`**

Generate video thumbnail.

**`add_streaming_video_reference(reference_id: str, mimetype: str, metadata: Optional[dict] = None) -> None`**

Add streaming video reference.

**`add_video_with_optimal_storage(video_data, filename: str, mimetype: Optional[str] = None, size_threshold_mb: int = 10) -> None`**

Add video with optimal storage method.

**`transcode_video(target_format: str, codec: Optional[str] = None, bit_rate: Optional[int] = None, width: Optional[int] = None, height: Optional[int] = None) -> None`**

Transcode video to different format.

**`add_image_data(image_path: str, mimetype: Optional[str] = None) -> None`**

Add image data from file.

**`extract_image_metadata(image_data: bytes, mimetype: str) -> None`**

Extract image metadata.

**`generate_thumbnail(max_size: Tuple[int, int] = (200, 200)) -> Optional[str]`**

Generate image thumbnail.

**`is_external_data_changed() -> bool`**

Check if external data has changed.

**`to_inline_data() -> None`**

Convert external data to inline data.

**`set_session_id(session_id: str) -> None`**

Set session identifier.

**`get_session_id() -> Optional[str]`**

Get session identifier.

**`set_content_hash(content_hash: str) -> None`**

Set content hash.

**`get_content_hash() -> Optional[str]`**

Get content hash.

**`calculate_content_hash(algorithm: str = "sha256") -> str`**

Calculate content hash.

**`verify_content_hash(expected_hash: str, algorithm: str = "sha256") -> bool`**

Verify content hash.

#### CivicAddress Class

Represents civic address information according to GEOPRIV specification.

**Constructor**

```python
CivicAddress(
    country: Optional[str] = None,
    a1: Optional[str] = None,
    a2: Optional[str] = None,
    a3: Optional[str] = None,
    a4: Optional[str] = None,
    a5: Optional[str] = None,
    a6: Optional[str] = None,
    prd: Optional[str] = None,
    pod: Optional[str] = None,
    sts: Optional[str] = None,
    hno: Optional[str] = None,
    hns: Optional[str] = None,
    lmk: Optional[str] = None,
    loc: Optional[str] = None,
    flr: Optional[str] = None,
    nam: Optional[str] = None,
    pc: Optional[str] = None
)
```

**Parameters:**

* `country` (str, optional): Country code (ISO 3166-1 alpha-2)
* `a1` (str, optional): Administrative area 1 (state/province)
* `a2` (str, optional): Administrative area 2 (county/municipality)
* `a3` (str, optional): Administrative area 3 (city/town)
* `a4` (str, optional): Administrative area 4 (neighborhood/district)
* `a5` (str, optional): Administrative area 5 (postal code)
* `a6` (str, optional): Administrative area 6 (building/floor)
* `prd` (str, optional): Premier (department/suite number)
* `pod` (str, optional): Post office box identifier
* `sts` (str, optional): Street name
* `hno` (str, optional): House number
* `hns` (str, optional): House name
* `lmk` (str, optional): Landmark name
* `loc` (str, optional): Location name
* `flr` (str, optional): Floor
* `nam` (str, optional): Name of location
* `pc` (str, optional): Postal code

**Methods**

**`to_dict() -> Dict[str, Optional[str]]`**

Convert CivicAddress to dictionary.

#### PartyHistory Class

Represents party history events in a vCon dialog.

**Constructor**

```python
PartyHistory(party: int, event: str, time: datetime)
```

**Parameters:**

* `party` (int): Index of the party
* `event` (str): Event type ("join", "drop", "hold", "unhold", "mute", "unmute")
* `time` (datetime): Time of the event

**Methods**

**`to_dict() -> Dict[str, Any]`**

Convert PartyHistory to dictionary.

#### Attachment Class

Represents an attachment in a vCon.

**Constructor**

```python
Attachment(type: str, body: Any, encoding: str = "none")
```

**Parameters:**

* `type` (str): Type of attachment
* `body` (Any): Content of attachment
* `encoding` (str): Encoding format ("base64", "base64url", "none")

**Methods**

**`to_dict() -> Dict[str, Any]`**

Convert Attachment to dictionary.

**`from_image(image_path: str, type: str = "image") -> 'Attachment'`**

Create attachment from image file.

### Extensions

The vCon library includes a comprehensive extension framework that allows for standardized implementation of additional functionality. Two major extensions are currently implemented: the Lawful Basis extension for privacy compliance and the WTF (World Transcription Format) extension for standardized transcription data.

#### Extension Framework

The extension framework provides a standardized way to add new functionality to vCon objects while maintaining compatibility and validation.

**Core Extension Classes**

**`ExtensionType`**

Enumeration of extension types:

* `COMPATIBLE`: Safe to ignore, no breaking changes
* `INCOMPATIBLE`: Must be supported, breaking changes
* `EXPERIMENTAL`: Development/testing only

**`ExtensionValidator`**

Abstract base class for extension validation logic.

**`ExtensionProcessor`**

Abstract base class for extension processing logic.

**`ExtensionRegistry`**

Central registry for managing extensions.

```python
from vcon.extensions import get_extension_registry

# Get the global registry
registry = get_extension_registry()

# List all registered extensions
extensions = registry.list_extensions()
```

#### Lawful Basis Extension

The Lawful Basis extension provides comprehensive support for privacy compliance and consent management according to GDPR and other privacy regulations.

**Key Features**

* **Multiple Lawful Basis Types**: consent, contract, legal\_obligation, vital\_interests, public\_task, legitimate\_interests
* **Purpose-Specific Permissions**: Granular permission grants with conditions
* **Cryptographic Proof Mechanisms**: Verbal confirmation, signed documents, cryptographic signatures, external systems
* **Temporal Validity**: Expiration dates and status intervals
* **Content Integrity**: Hash validation and canonicalization
* **External Registry Integration**: SCITT (Supply Chain Integrity, Transparency, and Trust) support

**Core Classes**

**`LawfulBasisAttachment`**

Main class representing a lawful basis attachment.

```python
from vcon.extensions.lawful_basis import LawfulBasisAttachment, LawfulBasisType, PurposeGrant
from datetime import datetime, timezone, timedelta

# Create purpose grants
purpose_grants = [
    PurposeGrant(
        purpose="recording",
        granted=True,
        granted_at=datetime.now(timezone.utc).isoformat()
    ),
    PurposeGrant(
        purpose="analysis",
        granted=True,
        granted_at=datetime.now(timezone.utc).isoformat(),
        conditions=["anonymized_data_only"]
    )
]

# Create lawful basis attachment
attachment = LawfulBasisAttachment(
    lawful_basis=LawfulBasisType.CONSENT,
    expiration=(datetime.now(timezone.utc) + timedelta(days=365)).isoformat(),
    purpose_grants=purpose_grants
)
```

**`PurposeGrant`**

Represents a purpose-specific permission grant.

```python
grant = PurposeGrant(
    purpose="recording",
    granted=True,
    granted_at=datetime.now(timezone.utc).isoformat(),
    conditions=["anonymized_data_only"]
)
```

**`ContentHash`**

Represents content integrity information.

```python
from vcon.extensions.lawful_basis import ContentHash, HashAlgorithm, CanonicalizationMethod

content_hash = ContentHash(
    algorithm=HashAlgorithm.SHA_256,
    canonicalization=CanonicalizationMethod.JCS,
    value="computed_hash_value"
)
```

**`ProofMechanism`**

Represents a proof mechanism for lawful basis.

```python
from vcon.extensions.lawful_basis import ProofMechanism, ProofType

proof = ProofMechanism(
    proof_type=ProofType.VERBAL_CONFIRMATION,
    timestamp=datetime.now(timezone.utc).isoformat(),
    proof_data={
        "dialog_reference": 0,
        "confirmation_text": "I consent to recording"
    }
)
```

**Validation and Processing**

**`LawfulBasisValidator`**

Validates lawful basis attachments and extension usage.

```python
from vcon.extensions.lawful_basis import LawfulBasisValidator

validator = LawfulBasisValidator()
result = validator.validate_attachment(attachment_dict)
```

**`LawfulBasisProcessor`**

Processes lawful basis attachments and evaluates permissions.

```python
from vcon.extensions.lawful_basis import LawfulBasisProcessor

processor = LawfulBasisProcessor()
result = processor.check_permission(vcon_dict, "recording", party_index=0)
```

**Registry Integration**

**`SCITTRegistryClient`**

Client for SCITT (Supply Chain Integrity, Transparency, and Trust) registries.

```python
from vcon.extensions.lawful_basis import SCITTRegistryClient

client = SCITTRegistryClient("https://registry.example.com", auth_token="token")
receipt_id = client.submit_attestation(lawful_basis_attachment)
```

#### WTF Extension

The WTF (World Transcription Format) extension provides standardized representation of speech-to-text transcription data from multiple providers.

**Key Features**

* **Multi-Provider Support**: Whisper, Deepgram, AssemblyAI, Google, Amazon, Azure, and more
* **Standardized Format**: Hierarchical structure with transcripts, segments, words, and speakers
* **Quality Metrics**: Audio quality assessment and confidence scoring
* **Export Capabilities**: SRT and WebVTT subtitle formats
* **Provider Adapters**: Automatic conversion from provider-specific formats
* **Analysis Tools**: Keyword extraction, confidence analysis, and transcription comparison

**Core Classes**

**`WTFAttachment`**

Main class representing a WTF transcription attachment.

```python
from vcon.extensions.wtf import WTFAttachment, Transcript, Segment, Metadata
from datetime import datetime, timezone

# Create transcript
transcript = Transcript(
    text="Hello world",
    language="en",
    duration=2.0,
    confidence=0.95
)

# Create segments
segments = [
    Segment(
        id=0,
        start=0.0,
        end=2.0,
        text="Hello world",
        confidence=0.95
    )
]

# Create metadata
metadata = Metadata(
    created_at=datetime.now(timezone.utc).isoformat(),
    processed_at=datetime.now(timezone.utc).isoformat(),
    provider="whisper",
    model="whisper-1"
)

# Create WTF attachment
attachment = WTFAttachment(
    transcript=transcript,
    segments=segments,
    metadata=metadata
)
```

**`Transcript`**

Represents high-level transcript information.

```python
transcript = Transcript(
    text="Hello world",
    language="en",
    duration=2.0,
    confidence=0.95
)
```

**`Segment`**

Represents a logical chunk of transcribed content.

```python
segment = Segment(
    id=0,
    start=0.0,
    end=2.0,
    text="Hello world",
    confidence=0.95,
    speaker=0
)
```

**`Word`**

Represents a single word in the transcription.

```python
word = Word(
    id=0,
    start=0.0,
    end=1.0,
    text="Hello",
    confidence=0.95,
    speaker=0
)
```

**`Speaker`**

Represents speaker information for diarization.

```python
speaker = Speaker(
    id=0,
    label="Speaker 1",
    segments=[0, 1, 2],
    total_time=10.5,
    confidence=0.9
)
```

**`Quality`**

Represents quality metrics for the transcription.

```python
quality = Quality(
    audio_quality="high",
    background_noise=0.1,
    multiple_speakers=True,
    overlapping_speech=False,
    silence_ratio=0.2,
    average_confidence=0.95,
    low_confidence_words=5,
    processing_warnings=[]
)
```

**Provider Adapters**

**`WhisperAdapter`**

Converts Whisper transcription data to WTF format.

```python
from vcon.extensions.wtf import WhisperAdapter

adapter = WhisperAdapter()
wtf_attachment = adapter.convert(whisper_data)
```

**`DeepgramAdapter`**

Converts Deepgram transcription data to WTF format.

```python
from vcon.extensions.wtf import DeepgramAdapter

adapter = DeepgramAdapter()
wtf_attachment = adapter.convert(deepgram_data)
```

**`AssemblyAIAdapter`**

Converts AssemblyAI transcription data to WTF format.

```python
from vcon.extensions.wtf import AssemblyAIAdapter

adapter = AssemblyAIAdapter()
wtf_attachment = adapter.convert(assemblyai_data)
```

**Export Capabilities**

**SRT Export**

Export transcription to SRT subtitle format.

```python
srt_content = attachment.export_to_srt()
```

**WebVTT Export**

Export transcription to WebVTT format.

```python
vtt_content = attachment.export_to_vtt()
```

**Analysis Tools**

**Keyword Extraction**

Extract keywords from high-confidence words.

```python
keywords = attachment.extract_keywords(min_confidence=0.8)
```

**Low Confidence Detection**

Find segments with confidence below threshold.

```python
low_confidence_segments = attachment.find_low_confidence_segments(threshold=0.5)
```

**Speaking Time Calculation**

Calculate speaking time for each speaker.

```python
speaking_times = attachment.get_speaking_time()
```

**Validation and Processing**

**`WTFValidator`**

Validates WTF transcription attachments.

```python
from vcon.extensions.wtf import WTFValidator

validator = WTFValidator()
result = validator.validate_attachment(attachment_dict)
```

**`WTFProcessor`**

Processes WTF transcription attachments and provides analysis.

```python
from vcon.extensions.wtf import WTFProcessor

processor = WTFProcessor()
analysis = processor.analyze_transcription(attachment)
```

### Constants

#### Property Handling Modes

```python
PROPERTY_HANDLING_DEFAULT = "default"  # Keep non-standard properties
PROPERTY_HANDLING_STRICT = "strict"    # Remove non-standard properties
PROPERTY_HANDLING_META = "meta"        # Move non-standard properties to meta
```

#### Dialog Types

```python
VALID_TYPES = ["recording", "text", "transfer", "incomplete", "audio", "video"]
```

#### Disposition Values

```python
VALID_DISPOSITIONS = [
    "no-answer", "congestion", "failed", "busy", 
    "hung-up", "voicemail-no-message"
]
```

#### Party History Events

```python
VALID_EVENTS = ["join", "drop", "hold", "unhold", "mute", "unmute"]
```

#### Attachment Encodings

```python
VALID_ENCODINGS = ["base64", "base64url", "none"]
```

#### Supported MIME Types

```python
MIME_TYPES = [
    "text/plain",
    "audio/x-wav", "audio/wav", "audio/wave", "audio/mpeg", "audio/mp3",
    "audio/x-mp3", "audio/x-mp4", "audio/ogg", "audio/webm", "audio/x-m4a", "audio/aac",
    "video/x-mp4", "video/ogg", "video/mp4", "video/quicktime", "video/webm",
    "video/x-msvideo", "video/x-matroska", "video/mpeg", "video/x-flv", "video/3gpp", "video/x-m4v",
    "multipart/mixed", "message/rfc822",
    "image/jpeg", "image/tiff", "application/pdf", "application/json"
]
```

#### Extension Types

```python
from vcon.extensions.base import ExtensionType

ExtensionType.COMPATIBLE     # Safe to ignore, no breaking changes
ExtensionType.INCOMPATIBLE   # Must be supported, breaking changes
ExtensionType.EXPERIMENTAL   # Development/testing only
```

#### Lawful Basis Types

```python
from vcon.extensions.lawful_basis import LawfulBasisType

LawfulBasisType.CONSENT              # Explicit consent
LawfulBasisType.CONTRACT             # Contractual necessity
LawfulBasisType.LEGAL_OBLIGATION     # Legal obligation
LawfulBasisType.VITAL_INTERESTS      # Vital interests
LawfulBasisType.PUBLIC_TASK          # Public task
LawfulBasisType.LEGITIMATE_INTERESTS # Legitimate interests
```

#### Proof Types

```python
from vcon.extensions.lawful_basis import ProofType

ProofType.VERBAL_CONFIRMATION        # Verbal confirmation
ProofType.SIGNED_DOCUMENT           # Signed document
ProofType.CRYPTOGRAPHIC_SIGNATURE   # Cryptographic signature
ProofType.EXTERNAL_SYSTEM           # External system attestation
```

#### Hash Algorithms

```python
from vcon.extensions.lawful_basis import HashAlgorithm

HashAlgorithm.SHA_256    # SHA-256
HashAlgorithm.SHA_384    # SHA-384
HashAlgorithm.SHA_512    # SHA-512
```

#### Canonicalization Methods

```python
from vcon.extensions.lawful_basis import CanonicalizationMethod

CanonicalizationMethod.JCS    # JSON Canonicalization Scheme
```

#### WTF Provider Adapters

```python
from vcon.extensions.wtf import (
    WhisperAdapter,      # OpenAI Whisper
    DeepgramAdapter,     # Deepgram
    AssemblyAIAdapter,   # AssemblyAI
    ProviderAdapter      # Base adapter class
)
```

### Examples

#### Basic vCon Creation

```python
from vcon import Vcon
from vcon.party import Party
from vcon.dialog import Dialog
from datetime import datetime

# Create new vCon
vcon = Vcon.build_new()

# Add parties
caller = Party(tel="+1234567890", name="Alice", role="caller")
agent = Party(tel="+1987654321", name="Bob", role="agent")
vcon.add_party(caller)
vcon.add_party(agent)

# Add dialog
dialog = Dialog(
    type="text",
    start=datetime.now(),
    parties=[0, 1],
    originator=0,
    body="Hello, I need help with my account."
)
vcon.add_dialog(dialog)

# Save to file
vcon.save_to_file("conversation.vcon.json")
```

#### Loading and Validation

```python
# Load vCon
vcon = Vcon.load("conversation.vcon.json")

# Validate
is_valid, errors = vcon.is_valid()
if not is_valid:
    print("Validation errors:", errors)

# Access data
print(f"UUID: {vcon.uuid}")
print(f"Parties: {len(vcon.parties)}")
print(f"Dialogs: {len(vcon.dialog)}")
```

#### Digital Signatures

```python
# Generate key pair
private_key, public_key = Vcon.generate_key_pair()

# Sign vCon
vcon.sign(private_key)

# Verify signature
is_valid = vcon.verify(public_key)
print(f"Signature valid: {is_valid}")
```

#### Extensions and Must-Support

```python
# Add extensions
vcon.add_extension("video")
vcon.add_extension("encryption")

# Add must-support
vcon.add_must_support("encryption")

print(f"Extensions: {vcon.get_extensions()}")
print(f"Must support: {vcon.get_must_support()}")
```

#### Analysis and Attachments

```python
# Add analysis
vcon.add_analysis(
    type="sentiment",
    dialog=[0, 1],
    vendor="SentimentAnalyzer",
    body={"sentiment": "positive", "confidence": 0.85},
    encoding="json"
)

# Add attachment
vcon.add_attachment(
    type="transcript",
    body="Full conversation transcript...",
    encoding="none"
)

# Add tags
vcon.add_tag("customer_id", "12345")
vcon.add_tag("priority", "high")
```

#### Video Content

```python
# Add video dialog
video_dialog = Dialog(
    type="video",
    start=datetime.now(),
    parties=[0, 1],
    mimetype="video/mp4",
    resolution="1920x1080",
    frame_rate=30.0,
    codec="H.264"
)

# Add video data
video_dialog.add_video_data(
    video_data=binary_video_data,
    filename="recording.mp4",
    mimetype="video/mp4",
    inline=True
)

# Extract metadata
metadata = video_dialog.extract_video_metadata()

# Generate thumbnail
thumbnail = video_dialog.generate_thumbnail(timestamp=10.0)

vcon.add_dialog(video_dialog)
```

#### Civic Address

```python
from vcon.civic_address import CivicAddress

# Create civic address
address = CivicAddress(
    country="US",
    a1="CA",
    a3="San Francisco",
    sts="Market Street",
    hno="123",
    pc="94102"
)

# Add to party
party = Party(
    name="Jane",
    tel="+1555123456",
    civicaddress=address
)
```

#### Party History

```python
from vcon.party import PartyHistory

# Create party history
history = [
    PartyHistory(0, "join", datetime.now()),
    PartyHistory(1, "join", datetime.now()),
    PartyHistory(0, "hold", datetime.now()),
    PartyHistory(0, "unhold", datetime.now()),
    PartyHistory(1, "drop", datetime.now())
]

# Add to dialog
dialog = Dialog(
    type="recording",
    start=datetime.now(),
    parties=[0, 1],
    party_history=history
)
```

#### HTTP Operations

```python
# Post vCon to server
response = vcon.post_to_url(
    "https://api.example.com/vcon",
    headers={
        "Authorization": "Bearer your-token",
        "Content-Type": "application/json"
    }
)

if response.status_code == 200:
    print("Successfully posted vCon")
else:
    print(f"Error: {response.status_code}")
```

#### Property Handling

```python
# Strict mode - remove non-standard properties
vcon = Vcon.load("file.json", property_handling="strict")

# Meta mode - move non-standard properties to meta
vcon = Vcon.load("file.json", property_handling="meta")

# Default mode - keep all properties
vcon = Vcon.load("file.json", property_handling="default")
```

#### Lawful Basis Extension

```python
from vcon import Vcon
from vcon.party import Party
from vcon.dialog import Dialog
from datetime import datetime, timezone, timedelta

# Create vCon with parties and dialog
vcon = Vcon.build_new()
caller = Party(tel="+1234567890", name="Alice", role="caller")
agent = Party(tel="+1987654321", name="Bob", role="agent")
vcon.add_party(caller)
vcon.add_party(agent)

# Add dialog
dialog = Dialog(
    type="recording",
    start=datetime.now(timezone.utc),
    parties=[0, 1],
    mimetype="audio/mp3"
)
vcon.add_dialog(dialog)

# Add lawful basis attachment
vcon.add_lawful_basis_attachment(
    lawful_basis="consent",
    expiration=(datetime.now(timezone.utc) + timedelta(days=365)).isoformat(),
    purpose_grants=[
        {
            "purpose": "recording",
            "granted": True,
            "granted_at": datetime.now(timezone.utc).isoformat()
        },
        {
            "purpose": "analysis",
            "granted": True,
            "granted_at": datetime.now(timezone.utc).isoformat(),
            "conditions": ["anonymized_data_only"]
        }
    ],
    party_index=0,
    dialog_index=0
)

# Check permissions
recording_permission = vcon.check_lawful_basis_permission("recording", party_index=0)
marketing_permission = vcon.check_lawful_basis_permission("marketing", party_index=0)

print(f"Recording permission: {recording_permission}")
print(f"Marketing permission: {marketing_permission}")

# Find lawful basis attachments
attachments = vcon.find_lawful_basis_attachments(party_index=0)
print(f"Found {len(attachments)} lawful basis attachments")
```

#### WTF Extension

```python
from vcon import Vcon
from vcon.party import Party
from vcon.dialog import Dialog
from datetime import datetime, timezone

# Create vCon with parties and dialog
vcon = Vcon.build_new()
caller = Party(tel="+1234567890", name="Alice", role="caller")
agent = Party(tel="+1987654321", name="Bob", role="agent")
vcon.add_party(caller)
vcon.add_party(agent)

# Add dialog
dialog = Dialog(
    type="recording",
    start=datetime.now(timezone.utc),
    parties=[0, 1],
    mimetype="audio/mp3"
)
vcon.add_dialog(dialog)

# Add WTF transcription attachment
vcon.add_wtf_transcription_attachment(
    transcript={
        "text": "Hello, this is a test transcription.",
        "language": "en",
        "duration": 3.5,
        "confidence": 0.95
    },
    segments=[
        {
            "id": 0,
            "start": 0.0,
            "end": 1.5,
            "text": "Hello, this is",
            "confidence": 0.95,
            "speaker": 0
        },
        {
            "id": 1,
            "start": 1.5,
            "end": 3.5,
            "text": "a test transcription.",
            "confidence": 0.94,
            "speaker": 0
        }
    ],
    metadata={
        "created_at": datetime.now(timezone.utc).isoformat(),
        "processed_at": datetime.now(timezone.utc).isoformat(),
        "provider": "whisper",
        "model": "whisper-1",
        "audio_quality": "high",
        "background_noise": 0.1
    },
    party_index=0,
    dialog_index=0
)

# Find WTF attachments
attachments = vcon.find_wtf_attachments(party_index=0)
print(f"Found {len(attachments)} WTF attachments")

# Export to SRT format
if attachments:
    from vcon.extensions.wtf import WTFAttachment
    wtf_attachment = WTFAttachment.from_dict(attachments[0]["body"])
    srt_content = wtf_attachment.export_to_srt()
    print("SRT Export:")
    print(srt_content)
```

#### Extension Validation and Processing

```python
# Validate all extensions
validation_results = vcon.validate_extensions()
print("Extension validation results:")
for extension, result in validation_results.items():
    if extension != "attachments":
        status = "✓ Valid" if result["is_valid"] else "✗ Invalid"
        print(f"  {extension}: {status}")
        if result["errors"]:
            for error in result["errors"]:
                print(f"    Error: {error}")
        if result["warnings"]:
            for warning in result["warnings"]:
                print(f"    Warning: {warning}")

# Process all extensions
processing_results = vcon.process_extensions()
print("Extension processing completed")
```

#### Provider Data Conversion

```python
from vcon.extensions.wtf import WhisperAdapter, DeepgramAdapter

# Convert Whisper data to WTF format
whisper_data = {
    "text": "Hello world from Whisper",
    "segments": [
        {
            "start": 0.0,
            "end": 2.0,
            "text": "Hello world from Whisper"
        }
    ]
}

whisper_adapter = WhisperAdapter()
wtf_attachment = whisper_adapter.convert(whisper_data)

# Add to vCon
vcon.add_wtf_transcription_attachment(
    transcript=wtf_attachment.transcript.to_dict(),
    segments=[segment.to_dict() for segment in wtf_attachment.segments],
    metadata=wtf_attachment.metadata.to_dict()
)
```

#### Complete Extension Workflow

```python
from vcon import Vcon
from vcon.party import Party
from vcon.dialog import Dialog
from datetime import datetime, timezone, timedelta

# Create comprehensive vCon with extensions
vcon = Vcon.build_new()

# Add parties
caller = Party(tel="+1234567890", name="Alice", role="caller")
agent = Party(tel="+1987654321", name="Bob", role="agent")
vcon.add_party(caller)
vcon.add_party(agent)

# Add dialog
dialog = Dialog(
    type="recording",
    start=datetime.now(timezone.utc),
    parties=[0, 1],
    mimetype="audio/mp3"
)
vcon.add_dialog(dialog)

# Add lawful basis for consent
vcon.add_lawful_basis_attachment(
    lawful_basis="consent",
    expiration=(datetime.now(timezone.utc) + timedelta(days=365)).isoformat(),
    purpose_grants=[
        {
            "purpose": "recording",
            "granted": True,
            "granted_at": datetime.now(timezone.utc).isoformat()
        },
        {
            "purpose": "transcription",
            "granted": True,
            "granted_at": datetime.now(timezone.utc).isoformat()
        }
    ],
    party_index=0
)

# Add transcription
vcon.add_wtf_transcription_attachment(
    transcript={
        "text": "Hello, I need help with my account.",
        "language": "en",
        "duration": 4.2,
        "confidence": 0.92
    },
    segments=[
        {
            "id": 0,
            "start": 0.0,
            "end": 4.2,
            "text": "Hello, I need help with my account.",
            "confidence": 0.92,
            "speaker": 0
        }
    ],
    metadata={
        "created_at": datetime.now(timezone.utc).isoformat(),
        "processed_at": datetime.now(timezone.utc).isoformat(),
        "provider": "whisper",
        "model": "whisper-1"
    },
    party_index=0,
    dialog_index=0
)

# Validate and process
validation_results = vcon.validate_extensions()
processing_results = vcon.process_extensions()

# Check permissions
can_record = vcon.check_lawful_basis_permission("recording", party_index=0)
can_transcribe = vcon.check_lawful_basis_permission("transcription", party_index=0)

print(f"Can record: {can_record}")
print(f"Can transcribe: {can_transcribe}")

# Save vCon
vcon.save_to_file("conversation_with_extensions.vcon.json")
print("Saved vCon with extensions")
```

This API reference covers all the main functionality of the vCon library, including the new extension framework. For more detailed examples and use cases, see the Quickstart Guide and the samples directory.


# vCon Library Guide for LLMs

Stick this in your robot's context window.

This guide provides a comprehensive overview of the vCon (Virtual Conversation) Python library, designed specifically for Large Language Models (LLMs) that need to generate or modify code using this library.

> **Spec target:** [`draft-ietf-vcon-vcon-core-02`](https://datatracker.ietf.org/doc/draft-ietf-vcon-vcon-core/) · syntax parameter `"vcon": "0.4.0"` · library version `0.9.2`.
>
> **Critical for LLM-generated code:**
>
> * Top-level field is `amended` (not `appended`); declared-critical extensions live in `must_understand[]` (not `must_support`).
> * Attachments use `purpose` (REQUIRED), never `type` — except the `lawful_basis` extension, which uses `type: "lawful_basis"`.
> * Analysis entries require `vendor`; use `schema` (never `schema_version`); `body` is always a JSON-encoded string.
> * WTF transcripts go in `analysis[]`, not `attachments[]`. See [WTF Transcription extension](/extensions/wtf-transcription).

### Overview

The vCon library is a Python implementation of [`draft-ietf-vcon-vcon-core-02`](https://datatracker.ietf.org/doc/draft-ietf-vcon-vcon-core/) for structuring, managing, and manipulating conversation data in a standardized format. It enables the creation, validation, and manipulation of digital representations of conversations with rich metadata, supporting all modern conversation features including multimedia content, security, and extensibility.

#### Key Concepts

* **vCon Container**: The primary object that holds all conversation data
* **Parties**: Participants in a conversation (callers, agents, bots) with contact information
* **Dialogs**: Individual messages or segments of the conversation (text, audio, video, etc.)
* **Attachments**: Additional files or data associated with the conversation
* **Analysis**: Results from processing the conversation (sentiment analysis, transcription, etc.)
* **Extensions**: Optional features that extend the base vCon functionality
  * **Lawful Basis Extension**: GDPR-compliant consent management and privacy compliance
  * **WTF Extension**: World Transcription Format for standardized speech-to-text data
* **Digital Signatures**: Cryptographic verification of vCon integrity
* **Civic Addresses**: Location information for parties using GEOPRIV standard
* **Party History**: Event tracking for multi-party conversations

### Installation

```bash
# Basic installation
pip install vcon

# With image processing support (Pillow, PyPDF)
pip install vcon[image]

# From source
git clone https://github.com/vcon-dev/vcon-lib.git
cd vcon-lib
pip install -e .
```

### Requirements

* Python 3.12+
* Core dependencies: authlib, uuid6, requests, pydash, python-dateutil
* Optional: mutagen (audio metadata), ffmpeg (video processing), Pillow (image processing), PyPDF (PDF processing)

### Core Classes and Usage Patterns

#### 1. Vcon Class

The main container for all conversation data.

**Creating a vCon**

```python
from vcon import Vcon

# Create a new empty vCon
vcon = Vcon.build_new()

# Create from existing JSON
vcon = Vcon.build_from_json(json_string)

# Load from file
vcon = Vcon.load_from_file("conversation.json")

# Load from URL
vcon = Vcon.load_from_url("https://example.com/conversation.json")

# Generic load (detects if path or URL)
vcon = Vcon.load("conversation.json")  # or URL
```

**Saving and Exporting**

```python
# Save to file
vcon.save_to_file("conversation.json")

# Convert to JSON string
json_str = vcon.to_json()  # or vcon.dumps()

# Convert to dictionary
vcon_dict = vcon.to_dict()

# Post to URL with optional headers
response = vcon.post_to_url(
    'https://api.example.com/vcons',
    headers={'x-api-token': 'your-token-here'}
)
```

**Properties**

```python
# Access properties
uuid = vcon.uuid
version = vcon.vcon
created_at = vcon.created_at
updated_at = vcon.updated_at
parties_list = vcon.parties
dialog_list = vcon.dialog
attachments_list = vcon.attachments
analysis_list = vcon.analysis
extensions_list = vcon.extensions
must_support_list = vcon.must_support
```

#### 2. Party Class

Represents a participant in the conversation.

```python
from vcon.party import Party

# Create a party
caller = Party(
    tel="+1234567890",
    name="Alice Smith",
    role="caller",
    mailto="alice@example.com"
)

# Add to vCon
vcon.add_party(caller)

# Find a party by an attribute
party_index = vcon.find_party_index("name", "Alice Smith")  # Returns index (0-based)
```

**Party Attributes**

**Core Contact Information:**

* `tel`: Telephone number (e.g., "+1234567890")
* `name`: Display name (e.g., "Alice Smith")
* `role`: Role in conversation ("caller", "agent", "bot", etc.)
* `mailto`: Email address (e.g., "<alice@example.com>")

**Advanced Contact Methods (vCon 0.3.0):**

* `sip`: SIP URI for VoIP communication (e.g., "sip:<alice@example.com>")
* `did`: Decentralized Identifier for blockchain-based identity
* `jCard`: vCard format contact information (RFC 7095)
* `timezone`: Party's timezone (e.g., "America/New\_York")

**Location and Validation:**

* `civicaddress`: Civic address using CivicAddress class (GEOPRIV format)
* `gmlpos`: GML position coordinates
* `validation`: Validation status
* `stir`: STIR verification for secure telephony

**Metadata:**

* `uuid`: Unique identifier for the party
* `contact_list`: Reference to contact list
* `meta`: Additional metadata dictionary
* Custom attributes can be added via kwargs

#### 3. Dialog Class

Represents a message or segment in the conversation.

```python
from vcon.dialog import Dialog
from datetime import datetime, timezone

# Create a text dialog
text_dialog = Dialog(
    type="text",
    start=datetime.now(timezone.utc).isoformat(),
    parties=[0, 1],  # Indices of parties involved
    originator=0,    # Index of the party that sent the message
    mimetype="text/plain",
    body="Hello, I need help with my account."
)

# Add to vCon
vcon.add_dialog(text_dialog)

# Create an audio dialog
audio_dialog = Dialog(
    type="audio",
    start=datetime.now(timezone.utc).isoformat(),
    parties=[0, 1],
    originator=0,
    mimetype="audio/mp3",
    body=base64_encoded_audio,
    encoding="base64",
    filename="recording.mp3"
)

vcon.add_dialog(audio_dialog)

# Find a dialog by property
found_dialog = vcon.find_dialog("type", "text")
```

**Special Dialog Types**

```python
# Add a transfer dialog
vcon.add_transfer_dialog(
    start=datetime.now(timezone.utc).isoformat(),
    transfer_data={
        "reason": "Call forwarded",
        "from": "+1234567890",
        "to": "+1987654321"
    },
    parties=[0, 1]
)

# Add an incomplete dialog (for failed conversations)
vcon.add_incomplete_dialog(
    start=datetime.now(timezone.utc).isoformat(),
    disposition="NO_ANSWER",
    details={"ringDuration": 45000},
    parties=[0, 1]
)
```

**Dialog Type Methods**

```python
# Check dialog type
is_text = dialog.is_text()
is_recording = dialog.is_recording()
is_transfer = dialog.is_transfer()
is_incomplete = dialog.is_incomplete()
is_audio = dialog.is_audio()
is_video = dialog.is_video()
is_email = dialog.is_email()
```

**Dialog Types and MIME Types**

**Valid Dialog Types:**

* `"text"`: Text-based communication (chat, SMS, email)
* `"recording"`: Audio/video recording
* `"transfer"`: Call transfer operation
* `"incomplete"`: Failed or incomplete conversation setup
* `"audio"`: Audio content
* `"video"`: Video content

**Supported MIME Types:**

**Text:**

* `text/plain`

**Audio:**

* `audio/x-wav`, `audio/wav`, `audio/wave`
* `audio/mpeg`, `audio/mp3`, `audio/x-mp3`
* `audio/x-mp4`, `audio/ogg`, `audio/webm`
* `audio/x-m4a`, `audio/aac`

**Video:**

* `video/x-mp4`, `video/mp4`, `video/ogg`
* `video/quicktime`, `video/webm`
* `video/x-msvideo`, `video/x-matroska`
* `video/mpeg`, `video/x-flv`, `video/3gpp`, `video/x-m4v`

**Other:**

* `multipart/mixed`
* `message/rfc822` (for email)
* `application/json` (for signaling data)
* `image/jpeg`, `image/tiff`, `application/pdf`

#### 4. Working with Tags

Tags are key-value pairs for simple metadata.

```python
# Add tags
vcon.add_tag("customer_id", "12345")
vcon.add_tag("interaction_id", "INT-001")

# Get tag value
value = vcon.get_tag("customer_id")  # Returns "12345"

# Get all tags
all_tags = vcon.tags  # Returns the tags attachment dictionary
```

#### 5. Working with Attachments

Attachments are arbitrary data associated with the conversation.

```python
# Add an attachment
vcon.add_attachment(
    type="transcript",
    body="Conversation transcript content...",
    encoding="none"
)

# Add a base64-encoded attachment
vcon.add_attachment(
    type="recording",
    body=base64_encoded_content,
    encoding="base64url"
)

# Find an attachment
attachment = vcon.find_attachment_by_type("transcript")
```

#### 6. Working with Analysis

Analysis entries represent insights derived from dialog.

```python
# Add analysis
vcon.add_analysis(
    type="sentiment",
    dialog=[0],  # Index or indices of dialogs analyzed
    vendor="AnalysisCompany",
    body={"sentiment": "positive", "score": 0.8},
    encoding="json"
)

# Find analysis
analysis = vcon.find_analysis_by_type("sentiment")
```

#### 7. Extensions and Must-Support (vCon 0.3.0)

Extensions allow vCons to declare optional features they use, while must-support indicates required features.

```python
# Add extensions used in this vCon
vcon.add_extension("video")
vcon.add_extension("encryption")
vcon.add_extension("sentiment_analysis")

# Add extensions that must be supported by consumers
vcon.add_must_support("encryption")
vcon.add_must_support("video")

# Get extensions
extensions = vcon.get_extensions()  # ['video', 'encryption', 'sentiment_analysis']
must_support = vcon.get_must_support()  # ['encryption', 'video']

# Remove extensions
vcon.remove_extension("sentiment_analysis")
vcon.remove_must_support("video")
```

#### 7.1. Lawful Basis Extension

The Lawful Basis extension provides comprehensive support for privacy compliance and consent management according to GDPR and other privacy regulations.

**Key Features**

* **Multiple Lawful Basis Types**: consent, contract, legal\_obligation, vital\_interests, public\_task, legitimate\_interests
* **Purpose-Specific Permissions**: Granular permission grants with conditions
* **Cryptographic Proof Mechanisms**: Verbal confirmation, signed documents, cryptographic signatures, external systems
* **Temporal Validity**: Expiration dates and status intervals
* **Content Integrity**: Hash validation and canonicalization
* **External Registry Integration**: SCITT (Supply Chain Integrity, Transparency, and Trust) support

**Adding Lawful Basis Attachments**

```python
from datetime import datetime, timezone, timedelta

# Add lawful basis attachment
vcon.add_lawful_basis_attachment(
    lawful_basis="consent",
    expiration=(datetime.now(timezone.utc) + timedelta(days=365)).isoformat(),
    purpose_grants=[
        {
            "purpose": "recording",
            "granted": True,
            "granted_at": datetime.now(timezone.utc).isoformat()
        },
        {
            "purpose": "analysis",
            "granted": True,
            "granted_at": datetime.now(timezone.utc).isoformat(),
            "conditions": ["anonymized_data_only"]
        }
    ],
    party_index=0,
    dialog_index=0
)

# Add extension to vCon
vcon.add_extension("lawful_basis")
```

**Checking Permissions**

```python
# Check if permission is granted for a specific purpose
recording_permission = vcon.check_lawful_basis_permission("recording", party_index=0)
marketing_permission = vcon.check_lawful_basis_permission("marketing", party_index=0)

print(f"Recording permission: {recording_permission}")
print(f"Marketing permission: {marketing_permission}")
```

**Finding Lawful Basis Attachments**

```python
# Find all lawful basis attachments
attachments = vcon.find_lawful_basis_attachments()

# Find attachments for a specific party
party_attachments = vcon.find_lawful_basis_attachments(party_index=0)
```

**Advanced Lawful Basis Features**

```python
from vcon.extensions.lawful_basis import (
    LawfulBasisAttachment, 
    PurposeGrant, 
    ContentHash,
    ProofMechanism,
    LawfulBasisType,
    ProofType,
    HashAlgorithm
)

# Create purpose grants with conditions
purpose_grants = [
    PurposeGrant(
        purpose="recording",
        granted=True,
        granted_at=datetime.now(timezone.utc).isoformat()
    ),
    PurposeGrant(
        purpose="analysis",
        granted=True,
        granted_at=datetime.now(timezone.utc).isoformat(),
        conditions=["anonymized_data_only", "retention_30_days"]
    )
]

# Create content hash for integrity
content_hash = ContentHash(
    algorithm=HashAlgorithm.SHA_256,
    canonicalization="JCS",
    value="computed_hash_value"
)

# Create proof mechanism
proof = ProofMechanism(
    proof_type=ProofType.VERBAL_CONFIRMATION,
    timestamp=datetime.now(timezone.utc).isoformat(),
    proof_data={
        "dialog_reference": 0,
        "confirmation_text": "I consent to recording"
    }
)

# Create comprehensive lawful basis attachment
attachment = LawfulBasisAttachment(
    lawful_basis=LawfulBasisType.CONSENT,
    expiration=(datetime.now(timezone.utc) + timedelta(days=365)).isoformat(),
    purpose_grants=purpose_grants,
    content_hash=content_hash,
    proof_mechanisms=[proof]
)
```

#### 7.2. WTF (World Transcription Format) Extension

The WTF extension provides standardized representation of speech-to-text transcription data from multiple providers.

**Key Features**

* **Multi-Provider Support**: Whisper, Deepgram, AssemblyAI, Google, Amazon, Azure, and more
* **Standardized Format**: Hierarchical structure with transcripts, segments, words, and speakers
* **Quality Metrics**: Audio quality assessment and confidence scoring
* **Export Capabilities**: SRT and WebVTT subtitle formats
* **Provider Adapters**: Automatic conversion from provider-specific formats
* **Analysis Tools**: Keyword extraction, confidence analysis, and transcription comparison

**Adding WTF Transcription Attachments**

```python
# Add WTF transcription attachment
vcon.add_wtf_transcription_attachment(
    transcript={
        "text": "Hello, this is a test transcription.",
        "language": "en",
        "duration": 3.5,
        "confidence": 0.95
    },
    segments=[
        {
            "id": 0,
            "start": 0.0,
            "end": 1.5,
            "text": "Hello, this is",
            "confidence": 0.95,
            "speaker": 0
        },
        {
            "id": 1,
            "start": 1.5,
            "end": 3.5,
            "text": "a test transcription.",
            "confidence": 0.94,
            "speaker": 0
        }
    ],
    metadata={
        "created_at": datetime.now(timezone.utc).isoformat(),
        "processed_at": datetime.now(timezone.utc).isoformat(),
        "provider": "whisper",
        "model": "whisper-1",
        "audio_quality": "high",
        "background_noise": 0.1
    },
    party_index=0,
    dialog_index=0
)

# Add extension to vCon
vcon.add_extension("wtf_transcription")
```

**Finding WTF Attachments**

```python
# Find all WTF attachments
attachments = vcon.find_wtf_attachments()

# Find attachments for a specific party
party_attachments = vcon.find_wtf_attachments(party_index=0)
```

**Exporting Transcriptions**

```python
# Find WTF attachments and export to SRT
attachments = vcon.find_wtf_attachments(party_index=0)
if attachments:
    from vcon.extensions.wtf import WTFAttachment
    wtf_attachment = WTFAttachment.from_dict(attachments[0]["body"])
    
    # Export to SRT format
    srt_content = wtf_attachment.export_to_srt()
    print("SRT Export:")
    print(srt_content)
    
    # Export to WebVTT format
    vtt_content = wtf_attachment.export_to_vtt()
    print("WebVTT Export:")
    print(vtt_content)
```

**Provider Data Conversion**

```python
from vcon.extensions.wtf import WhisperAdapter, DeepgramAdapter

# Convert Whisper data to WTF format
whisper_data = {
    "text": "Hello world from Whisper",
    "segments": [
        {
            "start": 0.0,
            "end": 2.0,
            "text": "Hello world from Whisper"
        }
    ]
}

whisper_adapter = WhisperAdapter()
wtf_attachment = whisper_adapter.convert(whisper_data)

# Add to vCon
vcon.add_wtf_transcription_attachment(
    transcript=wtf_attachment.transcript.to_dict(),
    segments=[segment.to_dict() for segment in wtf_attachment.segments],
    metadata=wtf_attachment.metadata.to_dict()
)
```

**Advanced WTF Features**

```python
from vcon.extensions.wtf import (
    WTFAttachment, 
    Transcript, 
    Segment, 
    Word, 
    Speaker,
    Quality,
    Metadata
)

# Create detailed transcript
transcript = Transcript(
    text="Hello world",
    language="en",
    duration=2.0,
    confidence=0.95
)

# Create segments with words
segments = [
    Segment(
        id=0,
        start=0.0,
        end=2.0,
        text="Hello world",
        confidence=0.95,
        speaker=0,
        words=[
            Word(id=0, start=0.0, end=1.0, text="Hello", confidence=0.95, speaker=0),
            Word(id=1, start=1.0, end=2.0, text="world", confidence=0.95, speaker=0)
        ]
    )
]

# Create speaker information
speakers = [
    Speaker(
        id=0,
        label="Speaker 1",
        segments=[0],
        total_time=2.0,
        confidence=0.9
    )
]

# Create quality metrics
quality = Quality(
    audio_quality="high",
    background_noise=0.1,
    multiple_speakers=False,
    overlapping_speech=False,
    silence_ratio=0.2,
    average_confidence=0.95,
    low_confidence_words=0,
    processing_warnings=[]
)

# Create metadata
metadata = Metadata(
    created_at=datetime.now(timezone.utc).isoformat(),
    processed_at=datetime.now(timezone.utc).isoformat(),
    provider="whisper",
    model="whisper-1",
    audio_quality="high",
    background_noise=0.1
)

# Create comprehensive WTF attachment
attachment = WTFAttachment(
    transcript=transcript,
    segments=segments,
    metadata=metadata,
    words=[word for segment in segments for word in segment.words],
    speakers=speakers,
    quality=quality
)
```

**Analysis Tools**

```python
# Extract keywords from high-confidence words
keywords = attachment.extract_keywords(min_confidence=0.8)

# Find segments with low confidence
low_confidence_segments = attachment.find_low_confidence_segments(threshold=0.5)

# Calculate speaking time for each speaker
speaking_times = attachment.get_speaking_time()
```

#### 7.3. Extension Validation and Processing

```python
# Validate all extensions
validation_results = vcon.validate_extensions()
print("Extension validation results:")
for extension, result in validation_results.items():
    if extension != "attachments":
        status = "✓ Valid" if result["is_valid"] else "✗ Invalid"
        print(f"  {extension}: {status}")
        if result["errors"]:
            for error in result["errors"]:
                print(f"    Error: {error}")
        if result["warnings"]:
            for warning in result["warnings"]:
                print(f"    Warning: {warning}")

# Process all extensions
processing_results = vcon.process_extensions()
print("Extension processing completed")
```

#### 8. Civic Address Support (vCon 0.3.0)

Civic addresses provide location information for parties using the GEOPRIV standard.

```python
from vcon.civic_address import CivicAddress

# Create civic address
address = CivicAddress(
    country="US",
    a1="CA",  # State
    a3="San Francisco",  # City
    sts="Market Street",  # Street
    hno="123",  # House number
    pc="94102"  # Postal code
)

# Add to party
party = Party(
    name="Jane Doe",
    tel="+1555123456",
    civicaddress=address
)

# Convert to dictionary
address_dict = address.to_dict()
```

#### 9. Party History Events (vCon 0.3.0)

Track when parties join, leave, or change state during conversations.

```python
from vcon.party import PartyHistory
from datetime import datetime

# Create party history events
history = [
    PartyHistory(0, "join", datetime.now()),    # Party 0 joins
    PartyHistory(1, "join", datetime.now()),    # Party 1 joins
    PartyHistory(0, "hold", datetime.now()),    # Party 0 on hold
    PartyHistory(0, "unhold", datetime.now()),  # Party 0 off hold
    PartyHistory(1, "drop", datetime.now())     # Party 1 drops
]

# Add to dialog
dialog = Dialog(
    type="recording",
    start=datetime.now(),
    parties=[0, 1],
    party_history=history
)

# Valid event types: "join", "drop", "hold", "unhold", "mute", "unmute"
```

#### 10. Advanced Dialog Features (vCon 0.3.0)

New dialog fields for enhanced functionality.

```python
# Dialog with session tracking and content hashing
dialog = Dialog(
    type="text",
    start=datetime.now(),
    parties=[0, 1],
    originator=0,
    body="Hello, this is a test message!",
    session_id="session-12345",
    content_hash="c8d3d67f662a787e96e74ccb0a77803138c0f13495a186ccbde495c57c385608",
    application="chat-app",
    message_id="<message-id@example.com>"
)

# Video dialog with metadata
video_dialog = Dialog(
    type="video",
    start=datetime.now(),
    parties=[0, 1],
    mimetype="video/mp4",
    resolution="1920x1080",
    frame_rate=30.0,
    codec="H.264",
    bitrate=5000000,
    filename="recording.mp4"
)

# Incomplete dialog with disposition
incomplete_dialog = Dialog(
    type="incomplete",
    start=datetime.now(),
    parties=[0],
    disposition="no-answer"  # Valid: no-answer, congestion, failed, busy, hung-up, voicemail-no-message
)
```

#### Signing and Verification

```python
# Generate a key pair
private_key, public_key = Vcon.generate_key_pair()

# Sign the vCon
vcon.sign(private_key)

# Verify the signature
is_valid = vcon.verify(public_key)
```

#### Validation

```python
# Validate a vCon object
is_valid, errors = vcon.is_valid()
if not is_valid:
    print("Validation errors:", errors)

# Validate a file
is_valid, errors = Vcon.validate_file("conversation.json")

# Validate a JSON string
is_valid, errors = Vcon.validate_json(json_string)
```

### Common Patterns and Best Practices

#### 1. Creating a Complete Conversation

```python
from vcon import Vcon
from vcon.party import Party
from vcon.dialog import Dialog
from datetime import datetime, timezone

# Create a new vCon
vcon = Vcon.build_new()

# Add participants
caller = Party(tel="+1234567890", name="Alice", role="caller")
agent = Party(tel="+1987654321", name="Bob", role="agent")
vcon.add_party(caller)
vcon.add_party(agent)

# Add conversation dialogs in sequence
vcon.add_dialog(Dialog(
    type="text",
    start=datetime.now(timezone.utc).isoformat(),
    parties=[0, 1],
    originator=0,  # Caller
    mimetype="text/plain",
    body="Hello, I need help with my account."
))

vcon.add_dialog(Dialog(
    type="text",
    start=datetime.now(timezone.utc).isoformat(),
    parties=[0, 1],
    originator=1,  # Agent
    mimetype="text/plain",
    body="I'd be happy to help. Can you provide your account number?"
))

# Add metadata
vcon.add_tag("customer_id", "12345")
vcon.add_tag("interaction_id", "INT-001")

# Validate and save
is_valid, errors = vcon.is_valid()
if is_valid:
    vcon.save_to_file("conversation.json")
else:
    print("Validation errors:", errors)
```

#### 2. Working with Audio Content

```python
import base64

# Reading an audio file and adding it to a dialog
with open("recording.mp3", "rb") as f:
    audio_data = f.read()
    audio_base64 = base64.b64encode(audio_data).decode("utf-8")

audio_dialog = Dialog(
    type="audio",
    start=datetime.now(timezone.utc).isoformat(),
    parties=[0, 1],
    originator=0,
    mimetype="audio/mp3",
    body=audio_base64,
    encoding="base64",
    filename="recording.mp3"
)
vcon.add_dialog(audio_dialog)
```

#### 3. External vs Inline Content

```python
# External content (referenced by URL)
external_dialog = Dialog(
    type="recording",
    start=datetime.now(timezone.utc).isoformat(),
    parties=[0, 1],
    url="https://example.com/recordings/call123.mp3",
    mimetype="audio/mp3"
)

# Check if dialog refers to external content
if external_dialog.is_external_data():
    # Convert to inline data
    external_dialog.to_inline_data()

# Check if dialog contains inline data
if dialog.is_inline_data():
    print("Dialog contains embedded content")
```

#### 4. Video Content Handling

```python
# Add video data with metadata
video_dialog = Dialog(
    type="video",
    start=datetime.now(),
    parties=[0, 1],
    mimetype="video/mp4",
    resolution="1920x1080",
    frame_rate=30.0,
    codec="H.264"
)

# Add video data (inline or external)
video_dialog.add_video_data(
    video_data=binary_video_data,  # or URL string
    filename="recording.mp4",
    mimetype="video/mp4",
    inline=True,  # False for external reference
    metadata={"duration": 120, "quality": "high"}
)

# Extract video metadata using FFmpeg
metadata = video_dialog.extract_video_metadata()

# Generate thumbnail
thumbnail_data = video_dialog.generate_thumbnail(
    timestamp=10.0,  # Time in seconds
    width=320,
    height=240,
    quality=90
)

# Transcode video to different format
video_dialog.transcode_video(
    target_format="webm",
    codec="vp9",
    bit_rate=2000000,
    width=1280,
    height=720
)
```

#### 5. Image Content Handling

```python
# Add image data from file
image_dialog = Dialog(
    type="text",  # Can be any type
    start=datetime.now(),
    parties=[0, 1]
)

# Add image from file
image_dialog.add_image_data(
    image_path="screenshot.png",
    mimetype="image/jpeg"  # Optional, auto-detected if not provided
)

# Generate thumbnail
thumbnail_b64 = image_dialog.generate_thumbnail(max_size=(200, 200))

# Check if dialog has image content
if image_dialog.is_image():
    print("Dialog contains image content")

# Check for PDF content
if image_dialog.is_pdf():
    print("Dialog contains PDF content")
```

#### 6. Content Hashing and Integrity

```python
# Calculate content hash
content_hash = dialog.calculate_content_hash("sha256")

# Set content hash for external files
dialog.set_content_hash(content_hash)

# Verify content integrity
is_valid = dialog.verify_content_hash(expected_hash, "sha256")

# Check if external data has changed
if dialog.is_external_data_changed():
    print("External content has been modified")
```

### Error Handling

```python
try:
    vcon = Vcon.load_from_file("conversation.json")
    is_valid, errors = vcon.is_valid()
    if not is_valid:
        print("Validation errors:", errors)
except FileNotFoundError:
    print("File not found")
except json.JSONDecodeError:
    print("Invalid JSON format")
except Exception as e:
    print(f"Error: {str(e)}")
```

### Working with Property Handling Modes

The Vcon constructor accepts a `property_handling` parameter to control how non-standard properties are handled:

```python
# Default mode: keep non-standard properties
vcon = Vcon(vcon_dict)  # or Vcon(vcon_dict, property_handling="default")

# Strict mode: remove non-standard properties
vcon = Vcon(vcon_dict, property_handling="strict")

# Meta mode: move non-standard properties to meta object
vcon = Vcon(vcon_dict, property_handling="meta")
```

### LLM-Specific Patterns and Best Practices

#### 1. Code Generation Templates

When generating vCon code, use these templates as starting points:

**Basic Conversation Template**

```python
from vcon import Vcon
from vcon.party import Party
from vcon.dialog import Dialog
from datetime import datetime

def create_basic_conversation():
    # Create vCon
    vcon = Vcon.build_new()
    
    # Add parties
    caller = Party(tel="+1234567890", name="Caller", role="caller")
    agent = Party(tel="+1987654321", name="Agent", role="agent")
    vcon.add_party(caller)
    vcon.add_party(agent)
    
    # Add conversation
    vcon.add_dialog(Dialog(
        type="text",
        start=datetime.now().isoformat(),
        parties=[0, 1],
        originator=0,
        body="Hello, I need help."
    ))
    
    return vcon
```

**Multimedia Conversation Template**

```python
def create_multimedia_conversation():
    vcon = Vcon.build_new()
    
    # Add parties with enhanced contact info
    caller = Party(
        tel="+1234567890",
        name="Alice",
        role="caller",
        mailto="alice@example.com",
        timezone="America/New_York"
    )
    agent = Party(
        tel="+1987654321", 
        name="Bob",
        role="agent",
        sip="sip:bob@company.com"
    )
    vcon.add_party(caller)
    vcon.add_party(agent)
    
    # Add text dialog
    vcon.add_dialog(Dialog(
        type="text",
        start=datetime.now().isoformat(),
        parties=[0, 1],
        originator=0,
        body="Hello, I need help with my account."
    ))
    
    # Add audio dialog
    vcon.add_dialog(Dialog(
        type="recording",
        start=datetime.now().isoformat(),
        parties=[0, 1],
        mimetype="audio/mp3",
        filename="conversation.mp3"
    ))
    
    # Add analysis
    vcon.add_analysis(
        type="sentiment",
        dialog=[0, 1],
        vendor="SentimentAnalyzer",
        body={"sentiment": "positive", "confidence": 0.85},
        encoding="json"
    )
    
    return vcon
```

**Extension-Enabled Conversation Template**

```python
def create_extension_enabled_conversation():
    """Create a vCon with both Lawful Basis and WTF extensions."""
    from datetime import datetime, timezone, timedelta
    
    vcon = Vcon.build_new()
    
    # Add parties
    caller = Party(
        tel="+1234567890",
        name="Alice",
        role="caller",
        mailto="alice@example.com"
    )
    agent = Party(
        tel="+1987654321",
        name="Bob", 
        role="agent",
        sip="sip:bob@company.com"
    )
    vcon.add_party(caller)
    vcon.add_party(agent)
    
    # Add recording dialog
    dialog = Dialog(
        type="recording",
        start=datetime.now(timezone.utc),
        parties=[0, 1],
        mimetype="audio/mp3"
    )
    vcon.add_dialog(dialog)
    
    # Add lawful basis for consent
    vcon.add_lawful_basis_attachment(
        lawful_basis="consent",
        expiration=(datetime.now(timezone.utc) + timedelta(days=365)).isoformat(),
        purpose_grants=[
            {
                "purpose": "recording",
                "granted": True,
                "granted_at": datetime.now(timezone.utc).isoformat()
            },
            {
                "purpose": "transcription",
                "granted": True,
                "granted_at": datetime.now(timezone.utc).isoformat()
            }
        ],
        party_index=0
    )
    
    # Add transcription
    vcon.add_wtf_transcription_attachment(
        transcript={
            "text": "Hello, I need help with my account.",
            "language": "en",
            "duration": 4.2,
            "confidence": 0.92
        },
        segments=[
            {
                "id": 0,
                "start": 0.0,
                "end": 4.2,
                "text": "Hello, I need help with my account.",
                "confidence": 0.92,
                "speaker": 0
            }
        ],
        metadata={
            "created_at": datetime.now(timezone.utc).isoformat(),
            "processed_at": datetime.now(timezone.utc).isoformat(),
            "provider": "whisper",
            "model": "whisper-1"
        },
        party_index=0,
        dialog_index=0
    )
    
    # Add extensions
    vcon.add_extension("lawful_basis")
    vcon.add_extension("wtf_transcription")
    
    return vcon
```

**Privacy-Compliant Conversation Template**

```python
def create_privacy_compliant_conversation():
    """Create a vCon with comprehensive privacy compliance."""
    from datetime import datetime, timezone, timedelta
    from vcon.extensions.lawful_basis import (
        LawfulBasisAttachment, 
        PurposeGrant, 
        ContentHash,
        ProofMechanism,
        LawfulBasisType,
        ProofType,
        HashAlgorithm
    )
    
    vcon = Vcon.build_new()
    
    # Add parties
    caller = Party(
        tel="+1234567890",
        name="Alice",
        role="caller"
    )
    agent = Party(
        tel="+1987654321",
        name="Bob",
        role="agent"
    )
    vcon.add_party(caller)
    vcon.add_party(agent)
    
    # Add dialog
    dialog = Dialog(
        type="recording",
        start=datetime.now(timezone.utc),
        parties=[0, 1],
        mimetype="audio/mp3"
    )
    vcon.add_dialog(dialog)
    
    # Create comprehensive lawful basis
    purpose_grants = [
        PurposeGrant(
            purpose="recording",
            granted=True,
            granted_at=datetime.now(timezone.utc).isoformat()
        ),
        PurposeGrant(
            purpose="analysis",
            granted=True,
            granted_at=datetime.now(timezone.utc).isoformat(),
            conditions=["anonymized_data_only", "retention_30_days"]
        ),
        PurposeGrant(
            purpose="marketing",
            granted=False,
            granted_at=datetime.now(timezone.utc).isoformat()
        )
    ]
    
    # Create content hash for integrity
    content_hash = ContentHash(
        algorithm=HashAlgorithm.SHA_256,
        canonicalization="JCS",
        value="computed_hash_value"
    )
    
    # Create proof mechanism
    proof = ProofMechanism(
        proof_type=ProofType.VERBAL_CONFIRMATION,
        timestamp=datetime.now(timezone.utc).isoformat(),
        proof_data={
            "dialog_reference": 0,
            "confirmation_text": "I consent to recording for quality assurance"
        }
    )
    
    # Create lawful basis attachment
    attachment = LawfulBasisAttachment(
        lawful_basis=LawfulBasisType.CONSENT,
        expiration=(datetime.now(timezone.utc) + timedelta(days=365)).isoformat(),
        purpose_grants=purpose_grants,
        content_hash=content_hash,
        proof_mechanisms=[proof]
    )
    
    # Add to vCon
    vcon.vcon_dict["attachments"].append({
        "type": "lawful_basis",
        "encoding": "json",
        "body": attachment.to_dict(),
        "party": 0,
        "dialog": 0
    })
    
    # Add extension
    vcon.add_extension("lawful_basis")
    
    return vcon
```

**Transcription-Enabled Conversation Template**

```python
def create_transcription_enabled_conversation():
    """Create a vCon with comprehensive transcription support."""
    from datetime import datetime, timezone
    from vcon.extensions.wtf import (
        WTFAttachment, 
        Transcript, 
        Segment, 
        Word, 
        Speaker,
        Quality,
        Metadata
    )
    
    vcon = Vcon.build_new()
    
    # Add parties
    caller = Party(
        tel="+1234567890",
        name="Alice",
        role="caller"
    )
    agent = Party(
        tel="+1987654321",
        name="Bob",
        role="agent"
    )
    vcon.add_party(caller)
    vcon.add_party(agent)
    
    # Add dialog
    dialog = Dialog(
        type="recording",
        start=datetime.now(timezone.utc),
        parties=[0, 1],
        mimetype="audio/mp3"
    )
    vcon.add_dialog(dialog)
    
    # Create detailed transcript
    transcript = Transcript(
        text="Hello, I need help with my account. Can you assist me?",
        language="en",
        duration=6.5,
        confidence=0.94
    )
    
    # Create segments with words
    segments = [
        Segment(
            id=0,
            start=0.0,
            end=3.2,
            text="Hello, I need help with my account.",
            confidence=0.95,
            speaker=0,
            words=[
                Word(id=0, start=0.0, end=0.5, text="Hello", confidence=0.98, speaker=0),
                Word(id=1, start=0.5, end=0.8, text="I", confidence=0.95, speaker=0),
                Word(id=2, start=0.8, end=1.1, text="need", confidence=0.92, speaker=0),
                Word(id=3, start=1.1, end=1.4, text="help", confidence=0.94, speaker=0),
                Word(id=4, start=1.4, end=1.7, text="with", confidence=0.90, speaker=0),
                Word(id=5, start=1.7, end=2.0, text="my", confidence=0.96, speaker=0),
                Word(id=6, start=2.0, end=2.5, text="account", confidence=0.93, speaker=0)
            ]
        ),
        Segment(
            id=1,
            start=3.2,
            end=6.5,
            text="Can you assist me?",
            confidence=0.92,
            speaker=0,
            words=[
                Word(id=7, start=3.2, end=3.5, text="Can", confidence=0.91, speaker=0),
                Word(id=8, start=3.5, end=3.8, text="you", confidence=0.94, speaker=0),
                Word(id=9, start=3.8, end=4.2, text="assist", confidence=0.89, speaker=0),
                Word(id=10, start=4.2, end=4.5, text="me", confidence=0.95, speaker=0)
            ]
        )
    ]
    
    # Create speaker information
    speakers = [
        Speaker(
            id=0,
            label="Customer",
            segments=[0, 1],
            total_time=6.5,
            confidence=0.93
        )
    ]
    
    # Create quality metrics
    quality = Quality(
        audio_quality="high",
        background_noise=0.05,
        multiple_speakers=False,
        overlapping_speech=False,
        silence_ratio=0.1,
        average_confidence=0.94,
        low_confidence_words=2,
        processing_warnings=[]
    )
    
    # Create metadata
    metadata = Metadata(
        created_at=datetime.now(timezone.utc).isoformat(),
        processed_at=datetime.now(timezone.utc).isoformat(),
        provider="whisper",
        model="whisper-1",
        audio_quality="high",
        background_noise=0.05
    )
    
    # Create comprehensive WTF attachment
    attachment = WTFAttachment(
        transcript=transcript,
        segments=segments,
        metadata=metadata,
        words=[word for segment in segments for word in segment.words],
        speakers=speakers,
        quality=quality
    )
    
    # Add to vCon as an ANALYSIS entry (recommended location for transcripts per
    # draft-howe-vcon-wtf). The core spec uses `purpose` on attachments, not `type`.
    # The vcon Python library's add_wtf_transcription_attachment() helper emits
    # `type: "wtf_transcription"` for legacy reasons — rename it to `purpose:` (or
    # better, place the WTF document in analysis[] as shown below).
    import json
    vcon.vcon_dict["analysis"].append({
        "type": "transcript",
        "dialog": 0,
        "vendor": "openai-whisper",
        "product": "whisper-1",
        "encoding": "json",
        "schema": "https://datatracker.ietf.org/doc/draft-howe-vcon-wtf/",
        "body": json.dumps(attachment.to_dict()),
    })

    # Declare the extension
    vcon.add_extension("wtf")

    return vcon
```

#### 2. Common LLM Tasks

**Converting Chat History to vCon**

```python
def chat_to_vcon(chat_messages, participants):
    vcon = Vcon.build_new()
    
    # Add participants as parties
    party_map = {}
    for i, participant in enumerate(participants):
        party = Party(
            name=participant.get("name", f"User {i}"),
            role=participant.get("role", "participant")
        )
        vcon.add_party(party)
        party_map[participant["id"]] = i
    
    # Add messages as dialogs
    for message in chat_messages:
        vcon.add_dialog(Dialog(
            type="text",
            start=message["timestamp"],
            parties=[party_map[message["sender_id"]]],
            originator=party_map[message["sender_id"]],
            body=message["content"]
        ))
    
    return vcon
```

**Adding AI Analysis to vCon**

```python
def add_ai_analysis(vcon, analysis_type, results, dialog_indices=None):
    if dialog_indices is None:
        dialog_indices = list(range(len(vcon.dialog)))
    
    vcon.add_analysis(
        type=analysis_type,
        dialog=dialog_indices,
        vendor="AI-Analyzer",
        body=results,
        encoding="json"
    )
    
    # Add extension if using AI features
    vcon.add_extension("ai_analysis")
```

**Extracting Conversation Data**

```python
def extract_conversation_data(vcon):
    data = {
        "uuid": vcon.uuid,
        "created_at": vcon.created_at,
        "parties": [],
        "dialogs": [],
        "analysis": []
    }
    
    # Extract parties
    for party in vcon.parties:
        data["parties"].append({
            "name": getattr(party, "name", None),
            "role": getattr(party, "role", None),
            "tel": getattr(party, "tel", None)
        })
    
    # Extract dialogs
    for dialog in vcon.dialog:
        data["dialogs"].append({
            "type": dialog.get("type"),
            "start": dialog.get("start"),
            "body": dialog.get("body", "")[:100] + "..." if len(dialog.get("body", "")) > 100 else dialog.get("body", ""),
            "parties": dialog.get("parties", [])
        })
    
    # Extract analysis
    for analysis in vcon.analysis:
        data["analysis"].append({
            "type": analysis.get("type"),
            "vendor": analysis.get("vendor"),
            "dialog_count": len(analysis.get("dialog", []))
        })
    
    return data
```

**Adding Privacy Compliance to vCon**

```python
def add_privacy_compliance(vcon, party_index, purposes, expiration_days=365):
    """Add lawful basis attachment for privacy compliance."""
    from datetime import datetime, timezone, timedelta
    
    purpose_grants = []
    for purpose in purposes:
        purpose_grants.append({
            "purpose": purpose,
            "granted": True,
            "granted_at": datetime.now(timezone.utc).isoformat()
        })
    
    vcon.add_lawful_basis_attachment(
        lawful_basis="consent",
        expiration=(datetime.now(timezone.utc) + timedelta(days=expiration_days)).isoformat(),
        purpose_grants=purpose_grants,
        party_index=party_index
    )
    
    vcon.add_extension("lawful_basis")
    return vcon
```

**Converting Provider Transcription to WTF**

```python
def convert_provider_transcription(vcon, provider_data, provider_type, party_index=0, dialog_index=0):
    """Convert provider-specific transcription data to WTF format."""
    from vcon.extensions.wtf import WhisperAdapter, DeepgramAdapter, AssemblyAIAdapter
    
    # Select appropriate adapter
    adapters = {
        "whisper": WhisperAdapter(),
        "deepgram": DeepgramAdapter(),
        "assemblyai": AssemblyAIAdapter()
    }
    
    if provider_type not in adapters:
        raise ValueError(f"Unsupported provider: {provider_type}")
    
    # Convert to WTF format
    adapter = adapters[provider_type]
    wtf_attachment = adapter.convert(provider_data)
    
    # Add to vCon
    vcon.add_wtf_transcription_attachment(
        transcript=wtf_attachment.transcript.to_dict(),
        segments=[segment.to_dict() for segment in wtf_attachment.segments],
        metadata=wtf_attachment.metadata.to_dict(),
        party_index=party_index,
        dialog_index=dialog_index
    )
    
    vcon.add_extension("wtf_transcription")
    return vcon
```

**Checking Privacy Permissions**

```python
def check_privacy_permissions(vcon, party_index, purposes):
    """Check if party has permission for specific purposes."""
    results = {}
    for purpose in purposes:
        results[purpose] = vcon.check_lawful_basis_permission(purpose, party_index)
    return results
```

**Exporting Transcriptions**

```python
def export_transcriptions(vcon, party_index=None, format="srt"):
    """Export transcriptions from vCon to various formats."""
    attachments = vcon.find_wtf_attachments(party_index)
    exports = []
    
    for attachment in attachments:
        from vcon.extensions.wtf import WTFAttachment
        wtf_attachment = WTFAttachment.from_dict(attachment["body"])
        
        if format.lower() == "srt":
            content = wtf_attachment.export_to_srt()
        elif format.lower() == "vtt":
            content = wtf_attachment.export_to_vtt()
        else:
            raise ValueError(f"Unsupported format: {format}")
        
        exports.append({
            "party_index": attachment.get("party"),
            "dialog_index": attachment.get("dialog"),
            "format": format,
            "content": content
        })
    
    return exports
```

**Validating Extensions**

```python
def validate_vcon_extensions(vcon):
    """Validate all extensions in a vCon and return detailed results."""
    validation_results = vcon.validate_extensions()
    
    summary = {
        "valid": True,
        "extensions": {},
        "errors": [],
        "warnings": []
    }
    
    for extension, result in validation_results.items():
        if extension != "attachments":
            summary["extensions"][extension] = {
                "valid": result["is_valid"],
                "errors": result["errors"],
                "warnings": result["warnings"]
            }
            
            if not result["is_valid"]:
                summary["valid"] = False
                summary["errors"].extend(result["errors"])
            
            summary["warnings"].extend(result["warnings"])
    
    return summary
```

**Processing Extensions**

```python
def process_vcon_extensions(vcon):
    """Process all extensions in a vCon and return results."""
    processing_results = vcon.process_extensions()
    
    summary = {
        "success": True,
        "results": processing_results,
        "errors": []
    }
    
    # Check for processing errors
    if "error" in processing_results:
        summary["success"] = False
        summary["errors"].append(processing_results["error"])
    
    return summary
```

#### 3. Error Handling Patterns

```python
def safe_vcon_operation(operation_func, *args, **kwargs):
    """Safely execute vCon operations with proper error handling."""
    try:
        return operation_func(*args, **kwargs)
    except ValueError as e:
        return {"error": f"Validation error: {str(e)}", "success": False}
    except FileNotFoundError as e:
        return {"error": f"File not found: {str(e)}", "success": False}
    except Exception as e:
        return {"error": f"Unexpected error: {str(e)}", "success": False}

# Usage
result = safe_vcon_operation(Vcon.load, "conversation.json")
if not result.get("success", True):
    print(f"Error: {result['error']}")
```

#### 4. Validation Patterns

```python
def validate_and_fix_vcon(vcon):
    """Validate vCon and attempt to fix common issues."""
    is_valid, errors = vcon.is_valid()
    
    if is_valid:
        return {"valid": True, "errors": []}
    
    fixes_applied = []
    
    # Fix common issues
    for error in errors:
        if "missing uuid" in error.lower():
            # UUID is auto-generated, this shouldn't happen
            pass
        elif "invalid dialog type" in error.lower():
            # Try to fix invalid dialog types
            for dialog in vcon.dialog:
                if dialog.get("type") not in ["text", "recording", "transfer", "incomplete", "audio", "video"]:
                    dialog["type"] = "text"  # Default to text
                    fixes_applied.append(f"Fixed invalid dialog type: {dialog.get('type')}")
    
    # Re-validate after fixes
    is_valid, remaining_errors = vcon.is_valid()
    
    return {
        "valid": is_valid,
        "errors": remaining_errors,
        "fixes_applied": fixes_applied
    }
```

#### 5. Integration Patterns

**REST API Integration**

```python
def vcon_to_api_payload(vcon):
    """Convert vCon to API payload format."""
    return {
        "vcon": vcon.to_dict(),
        "metadata": {
            "version": vcon.vcon,
            "created_at": vcon.created_at,
            "party_count": len(vcon.parties),
            "dialog_count": len(vcon.dialog)
        }
    }

def api_payload_to_vcon(payload):
    """Convert API payload to vCon."""
    return Vcon(payload["vcon"])
```

**Database Integration**

```python
def vcon_to_database_record(vcon):
    """Convert vCon to database record format."""
    return {
        "id": vcon.uuid,
        "version": vcon.vcon,
        "created_at": vcon.created_at,
        "updated_at": vcon.updated_at,
        "data": vcon.to_json(),
        "party_count": len(vcon.parties),
        "dialog_count": len(vcon.dialog),
        "has_attachments": len(vcon.attachments) > 0,
        "has_analysis": len(vcon.analysis) > 0
    }
```

#### 6. Performance Considerations

```python
def optimize_vcon_for_storage(vcon):
    """Optimize vCon for storage by converting large content to external references."""
    for i, dialog in enumerate(vcon.dialog):
        if dialog.get("body") and len(dialog["body"]) > 1000000:  # 1MB threshold
            # In real implementation, upload to storage and get URL
            external_url = f"https://storage.example.com/dialog_{i}_content"
            dialog["url"] = external_url
            dialog["content_hash"] = dialog.calculate_content_hash()
            del dialog["body"]
            dialog["encoding"] = None

def optimize_vcon_for_processing(vcon):
    """Optimize vCon for processing by loading external content."""
    for dialog in vcon.dialog:
        if dialog.is_external_data():
            try:
                dialog.to_inline_data()
            except Exception as e:
                print(f"Failed to load external content: {e}")
```

### Conclusion

The vCon library provides a comprehensive framework for working with conversation data. When generating code:

1. **Start Simple**: Begin with `Vcon.build_new()` and basic Party/Dialog objects
2. **Add Rich Metadata**: Use tags, attachments, and analysis for comprehensive data
3. **Handle Multimedia**: Leverage video/image processing capabilities when needed
4. **Ensure Security**: Use digital signatures for integrity verification
5. **Validate Always**: Check vCon validity before saving or transmitting
6. **Handle Errors Gracefully**: Implement proper error handling for robust applications
7. **Consider Performance**: Optimize for storage or processing based on use case
8. **Use Extensions**: Declare optional features and must-support requirements
9. **Track Events**: Use party history for complex multi-party conversations
10. **Integrate Seamlessly**: Follow patterns for API and database integration
11. **Implement Privacy Compliance**: Use Lawful Basis extension for GDPR compliance
12. **Standardize Transcriptions**: Use WTF extension for multi-provider transcription support
13. **Validate Extensions**: Always validate extension data before processing
14. **Export Transcriptions**: Leverage WTF export capabilities for subtitle formats
15. **Check Permissions**: Use lawful basis permission checking for privacy compliance

The vCon 0.3.0 specification provides a robust foundation for modern conversation data management with support for multimedia content, security, and extensibility.


# vCon-JS Library

The TypeScript / JavaScript implementation of vCon, parallel to the Python library.

`vcon-js` is the TypeScript / JavaScript implementation of the vCon specification. It is a peer to the [Python `vcon` library](/vcon-library), targeting the same [`draft-ietf-vcon-vcon-core`](https://datatracker.ietf.org/doc/draft-ietf-vcon-vcon-core/) spec.

> **Current version:** `vcon-js` **0.5.0** · Install with `npm install vcon-js` · [GitHub: vcon-dev/vcon-js](https://github.com/vcon-dev/vcon-js) · Targets [`draft-ietf-vcon-vcon-core`](https://datatracker.ietf.org/doc/draft-ietf-vcon-vcon-core/).

## When to use vcon-js vs the Python library

* **vcon-js** if you're writing Node services, Edge functions, Cloudflare Workers, browser code, or any TypeScript codebase that needs to read or write vCons.
* **Python `vcon`** if you're writing data pipelines, ML preprocessing, or anything inside the conserver runtime.

The two libraries produce byte-compatible vCons. You can build with one and consume with the other.

## Parity with the Python library

What you get in 0.5.0:

* ✅ Full core spec coverage: `Vcon`, `Party`, `Dialog`, `Attachment`, `Analysis`, `PartyHistory`
* ✅ Current core-draft surface: `recording-set` dialogs (`recordings` / `recording_set`), analysis `attachment` reference (with `dialog` optional), party `type` / `org` / `dept`
* ✅ Spec-correct field names: `amended` (not `appended`), `purpose` on attachments, `vcon: "0.4.0"` syntax param set automatically
* ✅ External and inline media (`body` + `encoding` or `url` + `content_hash`)
* ✅ Content hash validation (enforces `sha512-<base64url>` format)
* ✅ Auto-serialization: `addAnalysis({body: someObject, encoding: "json"})` JSON-stringifies the body for you
* ✅ Generic extension declaration (`addExtension`, `addCriticalExtension`)
* ✅ Tags via `addTag()`, with read-through `tags` property
* ✅ Per-class validators: `Dialog.validate()`, `Attachment.validate()`, `Party.validate()`, `PartyHistory.validate()`

What's not in 0.5.0 yet (vs. Python `vcon`):

* ❌ Per-extension helpers. The Python library has `add_lawful_basis_attachment()`, `add_wtf_transcription_attachment()`, and `add_wtf_transcription_analysis()`; vcon-js exposes the generic `addAttachment` / `addAnalysis` and you provide the extension shape yourself. Every extension parameter round-trips untyped; the one typed exception is `provenance` (draft-howe-vcon-provenance) on dialog and analysis. The [Extensions section](/extensions) shows what each one requires.
* ❌ Built-in signing/encryption convenience. The Python lib wraps JWS/JWE via `sign()`/`verify()` (RS256); in vcon-js the `signatures` and `payload` shapes are typed in `VconData` but you handle key management and signing through `jsonwebtoken` or a similar peer dependency.
* ❌ Extension-specific search helpers (`findLawfulBasisAttachments`, `findWtfAttachments`). Filter the arrays manually for now.

## Worked examples

The library ships three runnable TypeScript tutorials under [`examples/`](https://github.com/vcon-dev/vcon-js/tree/main/examples):

* `01-text-chat.ts` — multi-turn text chat with mixed-identifier parties, tags, serialization (`npm run example:chat`)
* `02-call-recording.ts` — phone recording with external media, content hash, STIR validation, sentiment/transcription analysis, contact\_center extension, party history (`npm run example:call`)
* `03-video-conference.ts` — five-party video call, incomplete dialogs, multiple attachments, meeting series grouping, action-items analysis (`npm run example:conference`)

## Documentation in this section

* [Quickstart](/vcon-js-library/quickstart) — create a vCon, add parties and dialog, add an analysis, serialize.
* [API Reference](/vcon-js-library/api-reference) — every exported class and method.
* [LLM Guide](/vcon-js-library/llm-guide) — paste this into a model's context window when you want it to generate vcon-js code.

## See also

* [Python vCon Library](/vcon-library) — the peer implementation
* [Extensions](/extensions) — the shape of each extension, useful when you need to add extension data manually in vcon-js


# Quickstart

Create a vCon in TypeScript or JavaScript — parties, dialog, analysis, serialize.

This is the TypeScript counterpart to the [Python Quickstart](/vcon-library/quickstart). Both libraries produce byte-compatible vCons.

## Install

```bash
npm install vcon-js
```

vcon-js targets [`draft-ietf-vcon-vcon-core`](https://datatracker.ietf.org/doc/draft-ietf-vcon-vcon-core/) and sets the `vcon: "0.4.0"` syntax parameter automatically.

## Minimal example

```typescript
import { Vcon, Party, Dialog } from 'vcon-js';

const vcon = Vcon.buildNew();
vcon.subject = 'Customer Support Chat';

vcon.addParty(new Party({ tel: '+15551234567', name: 'Alice', role: 'customer' }));
vcon.addParty(new Party({ mailto: 'bob@example.com', name: 'Bob', role: 'agent' }));

vcon.addDialog(new Dialog({
  type: 'text',
  start: new Date().toISOString(),
  parties: [0, 1],
  originator: 0,
  mediatype: 'text/plain',
  body: 'Hi, I need help with my account.',
  encoding: 'none',
}));

console.log(vcon.toJson());
```

## Recording with external media

For audio/video, prefer the external-media pattern: keep the recording out of the JSON, reference it with `url`, and provide a SHA-512 `content_hash` so consumers can verify integrity.

```typescript
const dialog = new Dialog({
  type: 'recording',
  start: '2026-05-18T14:00:00Z',
  parties: [0, 1],
  mediatype: 'audio/x-wav',
  duration: 137.5,
  url: 'https://media.example.com/recordings/abc123.wav',
  content_hash: 'sha512-iWS5VtJSp7v...',
});
vcon.addDialog(dialog);
```

## Adding an analysis (transcript)

Analysis entries record what was derived from the conversation. Always include `vendor`. For JSON-bodied analyses, set `encoding: 'json'` and pass a string body (use `JSON.stringify`).

```typescript
vcon.addAnalysis({
  type: 'transcript',
  dialog: 0,
  vendor: 'openai-whisper',
  product: 'whisper-large-v3',
  encoding: 'json',
  schema: 'https://datatracker.ietf.org/doc/draft-howe-vcon-wtf/',
  body: JSON.stringify({
    transcript: { text: 'Hello, I need help.', language: 'en', duration: 1.5, confidence: 0.95 },
    segments: [{ id: 0, start: 0.0, end: 1.5, text: 'Hello, I need help.', confidence: 0.95 }],
    metadata: { provider: 'whisper', model: 'whisper-large-v3', created_at: new Date().toISOString() },
  }),
});
```

## Declaring an extension

Extension declarations are generic in vcon-js — there are no per-extension builder helpers in 0.5.0. You add extension data to the right array yourself and declare the extension:

```typescript
vcon.addExtension('lawful_basis');           // declared but optional for consumers
vcon.addCriticalExtension('lawful_basis');   // consumers MUST understand this to process the vCon
```

For the JSON shape of each extension, see the [Extensions section](/extensions).

### Lawful Basis example

```typescript
vcon.addAttachment({
  type: 'lawful_basis',  // <-- exception: lawful_basis uses `type`, not `purpose`
  encoding: 'json',
  party: 0,
  dialog: 0,
  body: JSON.stringify({
    lawful_basis: 'consent',
    expiration: '2027-05-18T00:00:00Z',
    purpose_grants: [
      { purpose: 'recording', granted: true, granted_at: new Date().toISOString() },
      { purpose: 'transcription', granted: true, granted_at: new Date().toISOString() },
    ],
    proof_mechanisms: [
      { mechanism_type: 'audio_recording', dialog_index: 0,
        description: 'Verbal consent at start of recording' },
    ],
  }),
});
vcon.addCriticalExtension('lawful_basis');
```

## Tags

```typescript
vcon.addTag('region', 'us-east');
vcon.addTag('campaign', 'spring-2026');
```

Tags are surfaced through the conserver and through the [vCon MCP server](/mcp-server) for fast filtering.

## Loading and saving

```typescript
import { Vcon } from 'vcon-js';

// Serialize
const json = vcon.toJson();
const obj  = vcon.toDict();

// Deserialize
const restored = Vcon.buildFromJson(json);
```

## Common mistakes (caught in code review)

* Using `appended` instead of `amended` — the library writes `amended`; if you carry data forward from older code, rename.
* Using `must_support` — use `addCriticalExtension()` (writes to `critical[]`).
* Putting transcripts in `attachments[]` — transcripts belong in `analysis[]` (see [WTF Transcription](/extensions/wtf-transcription)).
* Passing a JS object directly as `body` for JSON content — `body` must be a string. Use `JSON.stringify`.
* Forgetting `vendor` on analysis entries — `vendor` is REQUIRED.

## See also

* [API Reference](/vcon-js-library/api-reference) — full method list
* [LLM Guide](/vcon-js-library/llm-guide) — drop into an LLM's context window for assisted development
* [Python Quickstart](/vcon-library/quickstart) — the Python equivalent


# API Reference

Public API of vcon-js 0.5.0 — every exported class, method, and constant.

Complete API reference for **`vcon-js` 0.5.0**, targeting [`draft-ietf-vcon-vcon-core`](https://datatracker.ietf.org/doc/draft-ietf-vcon-vcon-core/) (syntax `"0.4.0"`).

See the [Quickstart](/vcon-js-library/quickstart) for a code-walkthrough; this page is a reference.

## Top-level exports

```typescript
import {
  Vcon, Party, Dialog, Attachment, PartyHistory,
  // Types:
  VconData, Analysis, Encoding, CivicAddress, Redacted, Amended,
  DialogType, DialogTypeEnum, DialogDisposition, SessionId, ContentHash,
  AttachmentType, PartyType, PartyHistoryType,
  // Constants:
  VCON_VERSION,  // '0.4.0' (syntax parameter; unchanged as the draft advanced)
} from 'vcon-js';
```

## `Vcon`

Main container class.

### Static methods

| Method                             | Returns | Description                                                           |
| ---------------------------------- | ------- | --------------------------------------------------------------------- |
| `Vcon.buildNew()`                  | `Vcon`  | Create an empty vCon. Sets `uuid`, `created_at`, and `vcon: "0.4.0"`. |
| `Vcon.buildFromJson(json: string)` | `Vcon`  | Parse a vCon from JSON.                                               |

### Instance properties

`uuid`, `vcon`, `created_at`, `updated_at`, `subject`, `parties`, `dialog`, `attachments`, `analysis`, `tags`, `extensions`, `critical`, `redacted`, `amended`, `meta`.

Note: `amended` is the spec-correct name. `redacted` and `amended` are mutually exclusive — assigning one while the other is set throws. `group` is reserved by the spec and is not exposed.

### Adding content

| Method                               | Description                                                                 |
| ------------------------------------ | --------------------------------------------------------------------------- |
| `addParty(party: Party)`             | Append a party. The returned index is `parties.length - 1`.                 |
| `addDialog(dialog: Dialog)`          | Append a dialog entry.                                                      |
| `addAttachment(attachment)`          | Append an attachment. See the [Extensions section](/extensions) for shapes. |
| `addAnalysis(analysis)`              | Append an analysis entry. `vendor` is REQUIRED.                             |
| `addTag(key: string, value: string)` | Add a tag (surfaces in conserver / MCP search).                             |

### Extensions

| Method                                       | Description                                                                                   |
| -------------------------------------------- | --------------------------------------------------------------------------------------------- |
| `addExtension(name: string)`                 | Add to `extensions[]`. Consumers MAY understand it.                                           |
| `addCriticalExtension(name: string)`         | Add to both `extensions[]` and `critical[]`. Consumers MUST understand it or refuse the vCon. |
| `hasExtension(name: string): boolean`        | Check whether an extension is declared.                                                       |
| `isCriticalExtension(name: string): boolean` | Check whether an extension is in `critical[]`.                                                |

### Search helpers

| Method                             | Returns                   | Description                                             |
| ---------------------------------- | ------------------------- | ------------------------------------------------------- |
| `findPartyIndex(by, val)`          | `number \| undefined`     | Index of the first party whose `by` field equals `val`. |
| `findDialog(by, val)`              | `Dialog \| undefined`     | First dialog whose `by` field equals `val`.             |
| `findAttachmentByPurpose(purpose)` | `Attachment \| undefined` | First attachment with a given purpose.                  |
| `findAnalysisByType(type)`         | `Analysis \| undefined`   | First analysis of a given type.                         |

### Serialization

| Method     | Returns  | Description                                    |
| ---------- | -------- | ---------------------------------------------- |
| `toJson()` | `string` | JSON string ready to sign, store, or transmit. |
| `toDict()` | `object` | Plain JS object.                               |

## `Party`

```typescript
new Party({
  tel?: string,             // E.164 telephone number
  sip?: string,             // SIP URI
  mailto?: string,          // Email address
  stir?: string,            // STIR PASSporT (JWS compact)
  did?: string,             // Decentralized Identifier
  name?: string,
  role?: string,            // 'customer', 'agent', 'supervisor', ...
  type?: string,            // participant type (schema-defined token)
  org?: string,             // organization the party belongs to
  dept?: string,            // department the party belongs to
  validation?: string,      // 'verified', 'unverified', 'synthetic', ...
  gmlpos?: string,          // GML position (lat/long)
  civicaddress?: CivicAddress,
  timezone?: string,
  uuid?: string,
  meta?: object,            // Extension-specific data (e.g. agent_session)
});
```

Methods: `toDict()`, `hasIdentifier()`, `getPrimaryIdentifier()`, `validate()`.

## `Dialog`

```typescript
new Dialog({
  type: 'recording' | 'text' | 'transfer' | 'incomplete' | 'recording-set',
  start: string,            // ISO 8601 with timezone
  parties: number[] | number[][],   // party indices, or [[primary], [secondary]] for multi-channel
  originator?: number,
  mediatype?: string,
  duration?: number,
  // Inline content (mutually exclusive with url):
  body?: string,
  encoding?: 'base64url' | 'json' | 'none',
  // External content (mutually exclusive with body):
  url?: string,
  content_hash?: string | string[],   // sha512-<base64url>
  // Optional:
  filename?: string,
  disposition?: string,     // for type: 'incomplete'
  party_history?: PartyHistory[],
  session_id?: SessionId,   // RFC 7989
  // recording-set:
  recordings?: number[],    // on a 'recording-set' dialog: member recording indices
  recording_set?: number,   // on a recording: back-reference to its 'recording-set'
  // Transfer-specific (index or array of indices):
  transferor?: number,
  transferee?: number,
  transfer_target?: number | number[],
  original?: number | number[],
  consultation?: number | number[],
  target_dialog?: number | number[],
  // Extension parameter:
  provenance?: object,      // draft-howe-vcon-provenance, for machine-generated dialog
});
```

Methods: `toDict()`, `addInlineData(body, encoding, mediatype?)`, `addExternalData(url, contentHash, mediatype?)`, `isText()`, `isRecording()`, `isRecordingSet()`, `isTransfer()`, `isIncomplete()`, `validate()`.

## `Attachment`

```typescript
new Attachment({
  purpose: string,          // REQUIRED for core attachments — e.g. 'contract', 'screenshot'
  party: number,            // REQUIRED — party index, or 0 for vCon-level
  dialog: number,           // REQUIRED — dialog index, or 0 for vCon-level
  start?: string,
  mediatype?: string,
  filename?: string,
  // Inline:
  body?: string,
  encoding?: Encoding,
  // External:
  url?: string,
  content_hash?: string | string[],
  // Lawful Basis exception only:
  type?: string,            // Use 'lawful_basis' for the lawful-basis extension
});
```

Methods: `toDict()`, `addInlineData()`, `addExternalData()`, `isExternalData()`, `isInlineData()`, `validate()`.

> The core spec uses `purpose`, not `type`, on attachments. The Lawful Basis extension is the documented exception — see [Lawful Basis](/extensions/lawful-basis).

## `Analysis` (type)

```typescript
interface Analysis {
  type: string;             // 'transcript', 'summary', 'translation', 'sentiment', 'tts', ...
  dialog?: number | number[];    // optional: analysis may key off `attachment` instead
  attachment?: number | number[]; // attachment index/indices the analysis is based on
  vendor: string;           // REQUIRED
  product?: string;
  schema?: string;          // URL or identifier of the body format
  mediatype?: string;
  filename?: string;
  body?: string;            // JSON content goes here as a string (use JSON.stringify)
  encoding?: Encoding;      // 'json', 'base64url', 'none'
  url?: string;
  content_hash?: string | string[];
  provenance?: object;      // draft-howe-vcon-provenance: model/provider/params for the analysis
}
```

## `PartyHistory`

```typescript
new PartyHistory(
  party: number,
  event: 'join' | 'drop' | 'hold' | 'unhold' | 'mute' | 'unmute' | 'keydown' | 'keyup',
  time: Date | string,
  button?: string,          // for keydown/keyup
);
```

Methods: `toDict()`, `PartyHistory.fromDict(obj)`, `validate()`.

## Types

* **`SessionId`** — `{ local: string; remote: string }` per RFC 7989.
* **`CivicAddress`** — GEOPRIV-style address (country, a1–a6, sts, hno, lmk, pc, …).
* **`Redacted`** / **`Amended`** — top-level reference objects to a related vCon UUID (mutually exclusive).
* **`Encoding`** — `'base64url' | 'json' | 'none'`.

## See also

* [Quickstart](/vcon-js-library/quickstart) — walking through the API in context
* [LLM Guide](/vcon-js-library/llm-guide) — same material formatted for model context
* [Extensions section](/extensions) — the JSON shape you'll be passing into `addAttachment` / `addAnalysis` for each extension


# LLM Guide

Drop this in an LLM's context window when you want it to generate vcon-js code.

This guide gives a Large Language Model everything it needs to generate spec-compliant code against `vcon-js` 0.5.0.

> **Spec target:** [`draft-ietf-vcon-vcon-core`](https://datatracker.ietf.org/doc/draft-ietf-vcon-vcon-core/) · syntax parameter `"vcon": "0.4.0"` · library version `0.5.0`.

## Non-negotiable rules

If you violate any of these, the output is not a valid vCon. Apply them every time:

1. **Syntax param.** Every vCon must have `vcon: "0.4.0"`. `Vcon.buildNew()` sets this automatically — do not override.
2. **Field names.**
   * Top-level: `amended` (NOT `appended`).
   * Critical extensions: `addCriticalExtension(name)` writes to `critical[]`. Do NOT emit `must_support`.
3. **Attachments use `purpose`** (REQUIRED). The single exception is the Lawful Basis extension, which uses `type: "lawful_basis"`.
4. **Analysis requires `vendor`.** Always set `vendor`. Use `schema` (URL or identifier) to declare the body format. Never write `schema_version`.
5. **Bodies are strings.** When `encoding: 'json'`, the `body` value is a `JSON.stringify(...)` string, not a JS object.
6. **External media needs both `url` and `content_hash`.** The hash is `sha512-<base64url>`.
7. **Timestamps are ISO 8601 with timezone.** Use `new Date().toISOString()` or an explicit offset; never bare local time.
8. **Don't emit empty `group: []` or `redacted: {}`.** `group` is reserved; omit it unless populated.

## Public API at a glance

```typescript
import {
  Vcon, Party, Dialog, Attachment, PartyHistory,
  VCON_VERSION,
} from 'vcon-js';
```

**`Vcon`** — `buildNew()`, `buildFromJson(json)`, `addParty()`, `addDialog()`, `addAttachment()`, `addAnalysis()`, `addTag(key, value)`, `addExtension(name)`, `addCriticalExtension(name)`, `toJson()`, `toDict()`.

**`Party`** — identifiers: `tel`, `sip`, `mailto`, `stir`, `did`; descriptive: `name`, `role`, `type`, `org`, `dept`, `validation`, `civicaddress`, `timezone`, `meta`.

**`Dialog`** — `type: 'recording' | 'text' | 'transfer' | 'incomplete' | 'recording-set'`; required `start`, `parties`; inline `body` + `encoding` OR external `url` + `content_hash`. A `recording-set` dialog carries `recordings: number[]`; member recordings may carry `recording_set: number`.

**`Attachment`** — required `purpose`, `party`, `dialog`. Lawful Basis exception uses `type`.

**`Analysis`** is a type, not a class — pass a plain object to `addAnalysis()`. Required: `type` and `vendor`. `dialog` is optional (an analysis may key off `attachment` instead).

> **New in 0.5.0** (tracking the current core draft): the `recording-set` dialog type with `recordings`/`recording_set`, the analysis `attachment` reference, party `type`/`org`/`dept`, and a typed `provenance` parameter on dialog and analysis (draft-howe-vcon-provenance). All other extension parameters still round-trip untyped.

## Canonical end-to-end example

```typescript
import { Vcon, Party, Dialog } from 'vcon-js';

const vcon = Vcon.buildNew();
vcon.subject = 'Refund discussion';

const customerIdx = vcon.addParty(new Party({
  tel: '+15551234567', name: 'Alice', role: 'customer'
}));
const agentIdx = vcon.addParty(new Party({
  mailto: 'bob@example.com', name: 'Bob', role: 'agent'
}));

const callStart = '2026-05-18T14:00:00Z';

vcon.addDialog(new Dialog({
  type: 'recording',
  start: callStart,
  parties: [customerIdx, agentIdx],
  originator: customerIdx,
  mediatype: 'audio/x-wav',
  duration: 137.5,
  url: 'https://media.example.com/recordings/abc123.wav',
  content_hash: 'sha512-iWS5VtJSp7v...',
}));

// Transcript as analysis (NOT attachment)
vcon.addAnalysis({
  type: 'transcript',
  dialog: 0,
  vendor: 'openai-whisper',
  product: 'whisper-large-v3',
  encoding: 'json',
  schema: 'https://datatracker.ietf.org/doc/draft-howe-vcon-wtf/',
  body: JSON.stringify({
    transcript: { text: '...', language: 'en', duration: 137.5, confidence: 0.93 },
    segments: [],
    metadata: { provider: 'whisper', model: 'whisper-large-v3', created_at: new Date().toISOString() },
  }),
});

// Lawful basis (note the `type` field — extension exception)
vcon.addAttachment({
  type: 'lawful_basis',
  encoding: 'json',
  party: 0,
  dialog: 0,
  body: JSON.stringify({
    lawful_basis: 'consent',
    expiration: '2027-05-18T00:00:00Z',
    purpose_grants: [
      { purpose: 'recording', granted: true, granted_at: callStart },
    ],
    proof_mechanisms: [
      { mechanism_type: 'audio_recording', dialog_index: 0,
        description: 'Verbal consent at start of recording' },
    ],
  }),
});
vcon.addCriticalExtension('lawful_basis');

// Optional: tags for downstream search
vcon.addTag('region', 'us-east');

console.log(vcon.toJson());
```

## Common bugs in generated code (avoid)

* ❌ `vcon.addAttachment({ type: 'transcript', ... })` — wrong; transcripts live in `analysis[]`. Use `addAnalysis`.
* ❌ `vcon.addAttachment({ purpose: 'lawful_basis', ... })` — wrong; lawful\_basis uses `type`.
* ❌ `body: { transcript: ... }` paired with `encoding: 'json'` — body must be a string. Use `JSON.stringify`.
* ❌ Emitting `appended: { uuid: '...' }` or `must_support: [...]` — use `amended` and `critical[]` (via `addCriticalExtension`).
* ❌ `start: '2026-05-18 14:00:00'` (no timezone) — use `.toISOString()`.
* ❌ External media without `content_hash` — both `url` and `content_hash` are required for external media.

## Where to find extension shapes

The vcon-js library does not include per-extension helpers in 0.5.0. When asked to add extension data, refer to the corresponding page in the [Extensions section](/extensions) for the exact JSON shape, then construct an attachment or analysis entry matching that shape.

## See also

* [Quickstart](/vcon-js-library/quickstart)
* [API Reference](/vcon-js-library/api-reference)
* [Python Library Guide for LLMs](/vcon-library/vcon-library-guide-for-llms) — same material, Python edition


# vCon Adapters

Patterns, templates, and operational guidance for building services that turn foreign conversation data into vCons.

An **adapter** is anything that takes conversation data out of a foreign system — a phone PBX, a softswitch, a contact-center suite, an LLM transcript, a chat platform, a SIPREC stream — and produces a [vCon](/vcons) on the other side. Adapters are how the rest of the vCon ecosystem (conservers, MCP servers, analytics, archives) gets fed.

This section is the playbook for building, deploying, and operating one.

## Spec target

Adapters in this section target IETF [`draft-ietf-vcon-vcon-core-02`](https://datatracker.ietf.org/doc/draft-ietf-vcon-vcon-core/) with the `vcon` syntax parameter set to `"0.4.0"`. If you see code or examples elsewhere referring to `0.2.0` or `0.3.0`, treat it as out of date.

## The canonical flow

Every adapter — webhook receiver, polling job, file watcher, batch CLI — boils down to the same four stages:

```
   ┌───────────┐    ┌──────────────┐    ┌────────────────┐    ┌──────────────────┐
   │ Source    │ →  │ Build vCon   │ →  │ Sign / store   │ →  │ Deliver          │
   │ event     │    │ (lib helpers)│    │ (optional JWS) │    │ (HMAC webhook)   │
   └───────────┘    └──────────────┘    └────────────────┘    └──────────────────┘
```

The work that's actually adapter-specific is the leftmost box: knowing *your* source platform's events, IDs, timestamps, and recording URLs. Everything to the right of that — vCon construction, signing, retries, delivery — is solved. **Don't write it from scratch.**

## Start here: use the template

The canonical scaffold lives at [**vcon-dev/vcon-adapter-template**](https://github.com/vcon-dev/vcon-adapter-template). It's a GitHub template repo: click "Use this template" or run `gh repo create --template vcon-dev/vcon-adapter-template …`. You get:

* A `vcon_builder.py` thin wrapper over the official `vcon` Python library — spec-correct by construction
* HMAC-SHA256-signed async webhook delivery with `Idempotency-Key`, exponential-backoff retries, and a dead-letter queue
* `/healthz` and Prometheus `/metrics` endpoints out of the box
* YAML config with `${ENV_VAR}` substitution
* 14 spec-compliance smoke tests that fail loudly if you drift from the spec
* Dockerfile + `docker-compose.yml` + GitHub Actions CI

→ [Quick Start From Template](/vcon-adapters/quick-start-from-template) is a one-page recipe to get a new adapter scaffolded in under five minutes.

## Reading order

| Page                                                                                 | When to read it                                                                                           |
| ------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------- |
| [Quick Start From Template](/vcon-adapters/quick-start-from-template)                | First adapter, or every new adapter. Five-minute scaffold.                                                |
| [Operational Patterns](/vcon-adapters/operational-patterns)                          | Production deployment. Delivery, signing, retries, DLQ, health, metrics.                                  |
| [Spec Compliance Checklist](/vcon-adapters/spec-compliance-checklist)                | Code review and PR gate. Print and pin to wall.                                                           |
| [Extensions Cookbook](/vcon-adapters/extensions-cookbook)                            | Adding transcripts, consent records, SIP signaling, agent sessions.                                       |
| [vCon Adapter Development Guide](/vcon-adapters/vcon-adapter-development-guide)      | Going beyond the template — custom architectures, polling vs. webhook listeners, batch CLI, multi-source. |
| [LLM Guide: Creating vCon Adapters](/vcon-adapters/llm-guide-creating-vcon-adapters) | Drop into a model's context window when you want it to generate adapter code.                             |

## Existing adapters in the ecosystem

These live in their own repos under the [vcon-dev GitHub org](https://github.com/vcon-dev) at varying levels of maturity. Several were written before the canonical template existed, so they're useful as architecture references but **not** as compliance references — when their patterns disagree with the [checklist](/vcon-adapters/spec-compliance-checklist), trust the checklist.

| Repo                                                                               | Source                  | Pattern               |
| ---------------------------------------------------------------------------------- | ----------------------- | --------------------- |
| [`signalwire_adapter`](https://github.com/vcon-dev/signalwire_adapter)             | SignalWire telephony    | Polling job           |
| [`vcon-eleven-labs-adapter`](https://github.com/vcon-dev/vcon-eleven-labs-adapter) | ElevenLabs voice AI     | Polling + CLI batch   |
| [`vcon-audio-adapter`](https://github.com/vcon-dev/vcon-audio-adapter)             | Audio files on disk     | Directory watcher     |
| [`sippy-conserver-adapter`](https://github.com/vcon-dev/sippy-conserver-adapter)   | Sippy softswitch (S3)   | S3 bucket monitor     |
| [`ietf2vcon`](https://github.com/vcon-dev/ietf2vcon)                               | IETF meeting recordings | Batch CLI per-meeting |
| [`matrix_vcon_emitter`](https://github.com/vcon-dev/matrix_vcon_emitter)           | Matrix chat             | Event stream          |

For per-adapter documentation pages (`vCon Faker`, `vCon Anthropic Chats`, `vCon SIPREC Adapter`, etc.), see the [Tools](/tools) section.

## Related

* [vCon Library (Python)](/vcon-library) — the official Python library every adapter should use
* [vCon-JS Library](/vcon-js-library) — TypeScript equivalent for Node-based adapters
* [Extensions](/extensions) — WTF transcription, lawful basis, SIP signaling, agent session, lifecycle
* [Conserver](/conserver) — the typical downstream consumer of adapter output


# Quick Start From Template

Scaffold a new vCon adapter in five minutes from the official template repo.

The [vcon-adapter-template](https://github.com/vcon-dev/vcon-adapter-template) repo is a GitHub template repository. This page is the shortest path from "I have a source platform that produces conversations" to "I have a running, spec-compliant adapter delivering vCons over a signed webhook."

Spec target: [`draft-ietf-vcon-vcon-core-02`](https://datatracker.ietf.org/doc/draft-ietf-vcon-vcon-core/), syntax `"0.4.0"`.

## Prerequisites

* Python 3.12+
* [`uv`](https://docs.astral.sh/uv/) (recommended) or `pip`
* `gh` CLI (or use the GitHub web UI for step 1)
* A source platform that exposes conversation data — webhook events, an API to poll, files on a disk, an S3 bucket, anything

## Step 1 — Create your repo from the template

Pick three names up front, all referring to the same adapter:

| Name                | Form           | Example              |
| ------------------- | -------------- | -------------------- |
| **Adapter name**    | kebab-case     | `signalwire`         |
| **Python package**  | snake\_case    | `signalwire_adapter` |
| **Source platform** | human-readable | `SignalWire`         |

Then create the repo:

```bash
gh repo create vcon-dev/vcon-foo-adapter \
  --template vcon-dev/vcon-adapter-template \
  --public \
  --clone

cd vcon-foo-adapter
```

## Step 2 — Run find-and-replace on placeholders

The template uses three placeholder tokens. Substitute them in one pass:

```bash
ADAPTER_NAME=foo
ADAPTER_PACKAGE=foo_adapter
SOURCE_PLATFORM=Foo

# Rename the package directory
mv "src/__ADAPTER_PACKAGE__" "src/${ADAPTER_PACKAGE}"

# Substitute placeholders in every relevant file
find . -type f \( -name "*.py" -o -name "*.toml" -o -name "*.yaml" -o -name "*.yml" -o -name "*.md" -o -name "Dockerfile" \) \
  -not -path "./.git/*" \
  -exec sed -i.bak \
    -e "s/__ADAPTER_PACKAGE__/${ADAPTER_PACKAGE}/g" \
    -e "s/__ADAPTER_NAME__/${ADAPTER_NAME}/g" \
    -e "s/__SOURCE_PLATFORM__/${SOURCE_PLATFORM}/g" \
    {} \;
find . -name "*.bak" -delete
```

Then delete the scaffolding boilerplate the template ships with:

```bash
rm USAGE.md
# In README.md, delete the "## What this is" section explaining the template
```

## Step 3 — Install and verify the smoke tests pass

```bash
uv venv && source .venv/bin/activate
uv pip install -e ".[dev]"
pytest
```

You should see all 14 spec-compliance smoke tests pass. If anything fails, you have placeholder residue or a Python version mismatch — fix before touching anything else.

## Step 4 — Wire the source-platform listener

Open `src/<your_package>/cli.py` and find the `# TODO: wire your source-platform listener` comment. Replace it with whatever fits your source:

* **Webhook receiver:** add `aiohttp.web` routes that consume incoming events
* **Polling job:** use `asyncio.create_task` to run a loop with `asyncio.sleep(interval)`
* **File watcher:** import `watchdog` and observe a directory
* **Batch CLI:** read input files, build vCons in a loop, deliver, exit

The shape of the per-event work is the same regardless:

```python
from foo_adapter.vcon_builder import new_vcon
from foo_adapter.webhook_delivery import WebhookDelivery

async def handle_event(event: dict, delivery: WebhookDelivery) -> None:
    v = new_vcon(subject=event.get("title"))
    v.add_party(tel=event["caller"], role="caller")
    v.add_party(tel=event["agent"], role="agent")
    v.add_dialog(
        type="recording",
        start=event["started_at"],
        parties=[0, 1],
        url=event["recording_url"],
        mediatype="audio/wav",
    )
    await delivery.deliver(v.vcon_dict)
```

`new_vcon()` handles the four [`Vcon.build_new()` quirks](/vcon-adapters/spec-compliance-checklist) (syntax param, dropped empty `group`/`redacted`, `subject` setter, extensions list) for you. Use the library's `add_party` / `add_dialog` / `add_attachment` / `add_analysis` / `add_tag` helpers for everything else — they're spec-correct out of the box.

## Step 5 — Configure

Copy `config.example.yaml` to `config.yaml` and set the values. Required env vars:

| Env var                    | Purpose                              |
| -------------------------- | ------------------------------------ |
| `<PACKAGE>_API_KEY`        | Credentials for your source platform |
| `VCON_WEBHOOK_URL`         | Where to POST vCons                  |
| `VCON_WEBHOOK_HMAC_SECRET` | Shared secret for body signing       |

The config file uses `${ENV_VAR}` substitution at startup, so you never commit secrets.

## Step 6 — Run it

```bash
python -m foo_adapter
```

Or via Docker:

```bash
docker compose up
```

Then in another terminal:

```bash
curl localhost:8000/healthz   # → {"status":"ok"}
curl localhost:8000/metrics   # → Prometheus exposition
```

## Step 7 — Verify a real vCon end-to-end

Trigger an event from your source platform (or simulate one) and watch:

* Logs show `delivered url=... uuid=...` from `webhook_delivery.py`
* The `vcons_delivered_total` Prometheus counter increments
* The receiving end sees `Idempotency-Key: <uuid>` and `X-Hub-Signature-256: sha256=…` headers
* If you forcibly take the receiver down, vCons should land in `dlq/` after retries exhaust

## Step 8 — Publish

1. Push your repo: `git push -u origin main`
2. (Optional) Add to the [vcon super repo](https://github.com/vcon-dev/vcon) as a submodule
3. (Optional) Publish to PyPI — CI will do this on tag push if `PYPI_API_TOKEN` is set

## What to read next

* [Spec Compliance Checklist](/vcon-adapters/spec-compliance-checklist) — the must/never list every PR needs to pass
* [Operational Patterns](/vcon-adapters/operational-patterns) — what the template's delivery layer is actually doing under the hood
* [Extensions Cookbook](/vcon-adapters/extensions-cookbook) — attaching transcripts, consent records, and SIP signaling
* [vCon Adapter Development Guide](/vcon-adapters/vcon-adapter-development-guide) — when the template's shape isn't enough


# Operational Patterns

How to deliver vCons reliably in production — signing, idempotency, retries, dead-letter queues, health, metrics.

The vCon construction story is well-defined: use the [`vcon`](/vcon-library) library helpers and stay on the [Spec Compliance Checklist](/vcon-adapters/spec-compliance-checklist). The *delivery* story — how a built vCon actually leaves your adapter and reaches a conserver, MCP server, archive, or downstream pipeline — is where most adapters historically went wrong. This page documents the patterns the [vcon-adapter-template](https://github.com/vcon-dev/vcon-adapter-template) ships with and that new adapters should adopt.

Every pattern below is implemented in code at [`webhook_delivery.py`](https://github.com/vcon-dev/vcon-adapter-template/blob/main/src/__ADAPTER_PACKAGE__/webhook_delivery.py) and [`health_server.py`](https://github.com/vcon-dev/vcon-adapter-template/blob/main/src/__ADAPTER_PACKAGE__/health_server.py). If you scaffolded from the template, you already have this — read this page to know *why* it works the way it does.

## The delivery contract

An adapter SHOULD POST each vCon as a single JSON body to one or more configured webhook endpoints. The HTTP request looks like this:

```http
POST /vcons HTTP/1.1
Content-Type: application/json
Idempotency-Key: 6f1c5a8b-3e2d-4a1f-9c4f-7b8a2f0e1d3c
X-Hub-Signature-256: sha256=2c8f9...

{"vcon":"0.4.0","uuid":"6f1c5a8b-3e2d-4a1f-9c4f-7b8a2f0e1d3c", ...}
```

Three things are non-negotiable for production:

1. The body is **compact JSON** — no extra whitespace. The signature is computed over the exact bytes on the wire, so any reformatting on either side breaks verification.
2. The `Idempotency-Key` is the **vCon's `uuid`**. Same vCon ⇒ same key ⇒ the receiver MUST treat retries as no-ops.
3. The `X-Hub-Signature-256` header is computed exactly the same way GitHub computes it for webhooks. This is intentional — most receivers already have well-tested verification code for this format.

## HMAC body signing

Every adapter SHOULD sign its outgoing vCon webhooks with `X-Hub-Signature-256: sha256=<hexdigest>`. The signing recipe:

```python
import hashlib
import hmac

def sign(body: bytes, secret: str) -> str:
    mac = hmac.new(secret.encode("utf-8"), body, hashlib.sha256)
    return "sha256=" + mac.hexdigest()
```

On the receiver side, the verification flow is:

1. Read the raw request body (do NOT re-serialize the parsed JSON — the bytes must match the signed bytes exactly)
2. Recompute the HMAC with the shared secret
3. Compare in constant time with `hmac.compare_digest`

The shared secret lives in `VCON_WEBHOOK_HMAC_SECRET` (or per-endpoint in `config.yaml`). It is independent of any JWS keys used for vCon-level signing — they solve different problems (transport authentication vs. content provenance).

## Idempotency

Every webhook delivery MUST carry an `Idempotency-Key` header equal to the vCon's `uuid`. This serves two purposes:

* **Retries are safe.** When the adapter retries after a 5xx response or a timeout, the receiver sees the same key and can skip work it has already done.
* **Replay is detectable.** If an attacker captures and replays a signed payload, the receiver can spot the duplicate `uuid` and reject it.

vCon UUIDs are v4 — sufficiently random that collisions are operationally impossible. The receiver can use them as primary keys directly.

## Retries with exponential backoff

Networks fail. Downstream services are deployed, restart, get overloaded. The template's delivery layer retries up to `max_attempts` times (default 5) with exponential backoff:

| Attempt | Wait before retry |
| ------- | ----------------- |
| 1       | — (immediate)     |
| 2       | 1 s               |
| 3       | 2 s               |
| 4       | 4 s               |
| 5       | 8 s               |

Backoff doubles each attempt, capped at `max_backoff_seconds` (default 60). Both knobs are configurable in `config.yaml`:

```yaml
webhook:
  retry:
    max_attempts: 5
    initial_backoff_seconds: 1
    max_backoff_seconds: 60
```

A response counts as success when the status code is `2xx`. Anything else — `4xx`, `5xx`, timeout, connection error — counts as a failure and triggers the next retry (or the dead-letter queue if attempts are exhausted).

> Note: 4xx responses are retried by default. If the receiver returns a 4xx for a malformed vCon, retrying won't change the outcome — but it also won't hurt, and it keeps the delivery path simple. If you need 4xx fast-fail semantics for your downstream, customize the retry predicate.

## Dead-letter queue

When all retry attempts are exhausted across all configured endpoints, the vCon is written to disk under `dead_letter_path` (default `./dlq`) as `<uuid>.vcon.json`. This guarantees that:

* No vCon is ever silently lost
* An operator can inspect, requeue, or hand-deliver failed messages
* Audit trails survive receiver outages

The DLQ is a directory of JSON files, not a queue service. This keeps adapters small and stateless. Plug in your own re-injection cron, ops dashboard, or alerting on the directory's size — see the [Conserver](/conserver) docs for one way to wire DLQ replay into a broader pipeline.

The `vcons_dlq_total` Prometheus counter (see below) tracks DLQ writes — alert on it.

## Multiple endpoints (fan-out)

The template treats `endpoints` as a list. A vCon is delivered to **every** endpoint in the list; success on at least one keeps it out of the DLQ.

```yaml
webhook:
  endpoints:
    - url: https://primary.example/vcons
      hmac_secret: ${PRIMARY_HMAC_SECRET}
      timeout_seconds: 30
    - url: https://backup-archive.example/vcons
      hmac_secret: ${ARCHIVE_HMAC_SECRET}
      timeout_seconds: 60
```

This is the standard pattern for shipping the same conversation to a primary processing pipeline and a long-term archive simultaneously. Each endpoint has its own HMAC secret — the body is signed independently per endpoint.

## Health and metrics

The template starts an HTTP server on `${SERVER_HOST}:${SERVER_PORT}` (default `0.0.0.0:8000`) exposing two endpoints:

### `/healthz`

Returns `200 OK` with `{"status": "ok"}` JSON. Designed for Kubernetes liveness/readiness probes and load-balancer health checks. Wire it into your Dockerfile's `HEALTHCHECK` instruction (the template already does).

### `/metrics`

Returns Prometheus exposition format. Three counters out of the box:

| Counter                 | Increments on                              | Labels     |
| ----------------------- | ------------------------------------------ | ---------- |
| `vcons_built_total`     | Every successful vCon construction         | —          |
| `vcons_delivered_total` | Every 2xx response from a webhook endpoint | `endpoint` |
| `vcons_dlq_total`       | Every write to the dead-letter queue       | —          |

A reasonable alerting rule pair:

```yaml
- alert: VconAdapterDlqGrowing
  expr: rate(vcons_dlq_total[5m]) > 0
  for: 5m
- alert: VconAdapterDeliveryStalled
  expr: rate(vcons_delivered_total[10m]) == 0 and rate(vcons_built_total[10m]) > 0
  for: 10m
```

The first catches the failure mode where the receiver is broken; the second catches the failure mode where the adapter is broken upstream.

## JWS signing of vCons (optional, content-level)

HMAC webhook signing authenticates the *transport* — it tells the receiver "this body came from someone holding the shared secret." It does not travel with the vCon if the receiver later forwards it.

For end-to-end provenance, sign the vCon itself with JWS (JSON Web Signature, RS256). The template includes an optional signing path; enable it in config:

```yaml
vcon:
  signing:
    enabled: true
    private_key_path: ${VCON_SIGNING_KEY_PATH}
    key_id: ${VCON_SIGNING_KEY_ID}
```

The signed form is a JWS with the vCon as its detached payload. Receivers verify with the corresponding public key — usually distributed out of band or via a JWKS endpoint. JWS-signed vCons carry their authenticity across re-forwarding, archival, and downstream tooling.

JWS signing is independent of HMAC webhook signing. Most production deployments use both: HMAC for the transport hop, JWS for content provenance.

## Configuration surface

The template's [`config.example.yaml`](https://github.com/vcon-dev/vcon-adapter-template/blob/main/config.example.yaml) is the canonical source of truth. The relevant sections for operational concerns:

```yaml
webhook:
  endpoints:
    - url: ${VCON_WEBHOOK_URL}
      hmac_secret: ${VCON_WEBHOOK_HMAC_SECRET}
      timeout_seconds: 30
  retry:
    max_attempts: 5
    initial_backoff_seconds: 1
    max_backoff_seconds: 60
  dead_letter_path: /app/vcons/dlq

server:
  host: 0.0.0.0
  port: 8000

logging:
  level: INFO
  format: json
```

`${ENV_VAR}` substitution happens at startup, so secrets never need to be in the YAML file itself.

## Logging

The template uses [`structlog`](https://www.structlog.org/) with JSON output by default. Every delivery attempt logs a structured event:

```json
{"event":"delivered","url":"https://...","uuid":"6f1c...","attempt":1,"level":"info"}
{"event":"delivery_failed","url":"https://...","status":502,"attempt":2,"level":"warning"}
{"event":"dlq_write","uuid":"6f1c...","path":"/app/vcons/dlq/6f1c.vcon.json","level":"error"}
```

This is grep-friendly *and* fits cleanly into Loki/Datadog/Splunk pipelines. The `uuid` field is the join key for tracing a single vCon end-to-end.

## What this page is not

This is the operational story for the **template's delivery layer**. If you're shipping vCons over a different transport (Kafka, NATS, gRPC, files on a shared filesystem, an S3 bucket), the principles above still apply — sign the bytes, key off the UUID for idempotency, retry with backoff, persist failures — but the concrete code will differ. See the [Adapter Development Guide](/vcon-adapters/vcon-adapter-development-guide) for non-webhook delivery patterns.


# Spec Compliance Checklist

The must/never list for every adapter PR. Mirrors the smoke tests in vcon-adapter-template.

This is the gate every adapter PR (and every hand-written vCon construction) should pass. It mirrors the 14 smoke tests in [`vcon-adapter-template/tests/test_vcon_builder.py`](https://github.com/vcon-dev/vcon-adapter-template/blob/main/tests/test_vcon_builder.py) and the [`CONTRIBUTING.md`](https://github.com/vcon-dev/vcon-adapter-template/blob/main/CONTRIBUTING.md) in the same repo. If you scaffolded from the template, `pytest` runs all of this for you.

**Spec target:** IETF [`draft-ietf-vcon-vcon-core-02`](https://datatracker.ietf.org/doc/draft-ietf-vcon-vcon-core/), syntax `"0.4.0"`.

## The one-line rule

> **Always use the** [**`vcon`**](/vcon-library) **library helpers (`add_party`, `add_dialog`, `add_attachment`, `add_analysis`, `add_tag`). Never write directly to `vcon_dict[...]` except for the four documented quirks below.**

Recent versions of the `vcon` Python library (≥0.9.4) emit spec-correct output for every helper. Hand-rolling a dict bypasses that and is the single most common source of compliance drift in real adapters.

## Top-level vCon

* [ ] `vcon` syntax parameter is exactly the string `"0.4.0"` — not `0.0.1`, `0.0.2`, `0.2.0`, `0.3.0`, or any number
* [ ] `uuid` is a v4 UUID string
* [ ] All timestamps are ISO-8601 with a timezone (`Z` or explicit offset)
* [ ] No empty `group: []` or empty `redacted: {}` left over from `Vcon.build_new()` — drop them; the spec reserves these fields for actual use
* [ ] `subject` is written via `v.vcon_dict["subject"]` (the lib has no setter)

The template's `new_vcon()` helper handles all five of these — call it instead of `Vcon.build_new()` directly.

## Analysis objects

* [ ] Constructed via `Vcon.add_analysis(type, dialog, vendor, body, encoding, schema, product, ...)`
* [ ] Field name is **`schema`** — never `schema_version`
* [ ] `vendor` is REQUIRED on every analysis (the lib enforces this as a kwarg)
* [ ] `body` is always a string. For JSON bodies, pair it with `encoding="json"`
* [ ] Transcripts live in `analysis[]`, not `attachments[]`
* [ ] Transcript analysis has `schema=<WTF draft URL>`, `encoding="json"`, `vendor="<provider>"`, `product="<model>"`

See the [Extensions Cookbook](/vcon-adapters/extensions-cookbook) for full transcript examples.

## Attachment objects

* [ ] Constructed via `Vcon.add_attachment(purpose, body, encoding, party, dialog, ...)`
* [ ] Field name is **`purpose`** — never `type` (in core; `lawful_basis` is a documented exception, see below)
* [ ] `party` AND `dialog` indices are passed. Use `0, 0` for vCon-level attachments not tied to a specific party or dialog
* [ ] JSON-bodied attachments use `encoding="json"` with a JSON-string `body`

## Tags

* [ ] Use `Vcon.add_tag(name, value)` directly. Library ≥0.9.3 writes `party`/`dialog` on the tags attachment correctly — no backfill needed.

## External media

* [ ] Both `url` AND `content_hash` are present on the dialog
* [ ] `content_hash` is formatted as `sha512-<base64url-of-digest>` — not hex, not base64 (with `+/=`), not SHA-256
* [ ] `mediatype` is set (e.g. `audio/wav`, `video/mp4`, `text/plain`)

The template provides `sha512_b64url(data)` and `external_media_url(url, content, mediatype)` helpers in [`vcon_builder.py`](https://github.com/vcon-dev/vcon-adapter-template/blob/main/src/__ADAPTER_PACKAGE__/vcon_builder.py).

## Legacy field-name traps

These names appear in older vcon-mcp code and older draft revisions. **Never** emit them in a new vCon — the receiver may reject the vCon or, worse, silently misroute it.

| ❌ Never write           | ✅ Always write | Why                                                                 |
| ----------------------- | -------------- | ------------------------------------------------------------------- |
| `appended`              | `amended`      | Legacy vcon-mcp column name                                         |
| `must_support`          | `critical`     | Legacy vcon-mcp column name                                         |
| `schema_version`        | `schema`       | Older draft field name; current spec is `schema`                    |
| `type` (on attachments) | `purpose`      | Core spec uses `purpose`; only `lawful_basis` extension uses `type` |
| `did` (on parties)      | (removed)      | The `did` field was removed in `0.4.0`                              |

The template's smoke test `test_no_legacy_field_names_in_serialized_vcon` greps the serialized vCon for `appended` and `must_support` and fails the build if either appears.

## Extensions

* [ ] Every extension used is listed in top-level `extensions[]`
* [ ] Extension names match the spec exactly (e.g. `"sip-signaling"`, `"lawful_basis"`, `"wtf"` or `"wtf_transcription"`, `"agent_session"`, `"lifecycle"`)

## Lawful basis (if recording consent is tracked)

The `lawful_basis` attachment is the **single documented exception** to the "use `purpose` on attachments" rule:

* [ ] Attachment uses `type: "lawful_basis"` (not `purpose`)
* [ ] `"lawful_basis"` is added to top-level `extensions[]`
* [ ] For synthetic test data: `lawful_basis: "legitimate_interests"` + `proof_mechanism` of type `external_system`

See the [Extensions Cookbook](/vcon-adapters/extensions-cookbook) and the [Lawful Basis page](/extensions/lawful-basis) for full examples.

## Synthetic test data

If you're generating synthetic vCons for testing or training:

* [ ] Each synthetic party is marked with `validation: "synthetic"`
* [ ] A `purpose: "synthetic_data_consent"` attachment is present, OR a `lawful_basis` attachment documents the synthetic origin via an `external_system` proof mechanism

## The 14 smoke tests, by name

These are the canonical compliance gates. From [`test_vcon_builder.py`](https://github.com/vcon-dev/vcon-adapter-template/blob/main/tests/test_vcon_builder.py):

1. `test_syntax_is_0_4_0`
2. `test_build_new_strips_group_and_redacted`
3. `test_subject_is_written_via_vcon_dict`
4. `test_extensions_listed_at_top_level`
5. `test_lib_add_attachment_uses_purpose_with_party_and_dialog`
6. `test_lib_add_analysis_uses_schema_not_schema_version`
7. `test_lib_add_analysis_requires_vendor`
8. `test_lib_add_tag_writes_party_and_dialog`
9. `test_content_hash_format`
10. `test_external_media_dialog_has_url_and_content_hash`
11. `test_no_legacy_field_names_in_serialized_vcon[appended]`
12. `test_no_legacy_field_names_in_serialized_vcon[must_support]`
13. (Plus delivery-layer tests for HMAC signature format and DLQ behavior)

Run them with `pytest` from any adapter scaffolded from the template. If you're not using the template, copy the test file — it's spec-version-pinned and short.

## When you discover compliance drift

If you find a vCon in the wild — your archive, a partner's payload, a test fixture — that violates this list, do *not* round-trip it through `Vcon.from_dict(...)` and pretend it's fine. Open an issue against the producing adapter, file the offending field, and either:

* Add a migration link in your conserver to repair the shape, OR
* Reject the vCon at ingress

The spec is what the spec says. Tolerating drift is how the ecosystem fragments.


# Extensions Cookbook

Worked examples for the extensions adapters use most — WTF transcription, lawful basis, SIP signaling, agent session.

The vCon core spec is intentionally small. Most of what adapters actually care about — transcripts, recording consent, SIP signaling provenance, AI-agent session tracking — lives in **extensions**. This page is a recipe book for the four extensions adapters use in practice.

Each recipe shows the exact `vcon` library call, the resulting JSON shape, and links to the corresponding extension page for the full spec.

**Spec target:** [`draft-ietf-vcon-vcon-core-02`](https://datatracker.ietf.org/doc/draft-ietf-vcon-vcon-core/), syntax `"0.4.0"`. Library: [`vcon`](https://pypi.org/project/vcon/) ≥0.9.4.

***

## WTF Transcription

📄 **Spec:** [WTF Transcription Extension](/extensions/wtf-transcription) · [`draft-howe-vcon-wtf-extension`](https://datatracker.ietf.org/doc/draft-howe-vcon-wtf/) · **Extension name:** `"wtf"` (older code uses `"wtf_transcription"`)

Use WTF when your adapter calls a speech-to-text provider — Whisper, Deepgram, AssemblyAI, ElevenLabs, AWS, Azure, Google. The point is that downstream tooling shouldn't have to special-case each provider's output.

**Transcripts go in `analysis[]`, not `attachments[]`.** They're derived data, not supplied data.

### Recipe

```python
import json
from foo_adapter.vcon_builder import new_vcon

v = new_vcon(subject="Sales call", extensions=["wtf"])
v.add_party(tel="+15555550100", role="caller")
v.add_party(tel="+15555550200", role="agent")
v.add_dialog(
    type="recording",
    start="2026-05-19T14:32:00Z",
    parties=[0, 1],
    url="https://recordings.example/abc.wav",
    content_hash="sha512-...",
    mediatype="audio/wav",
)

wtf_document = {
    "transcript": {"text": "Hello, this is Foo Corp..."},
    "segments": [
        {"start": 0.0, "end": 2.3, "speaker": 0, "text": "Hello, this is Foo Corp."},
        {"start": 2.3, "end": 5.1, "speaker": 1, "text": "Hi, I'm calling about my account."},
    ],
    "language": "en-US",
}

v.add_analysis(
    type="transcript",
    dialog=0,
    vendor="openai-whisper",
    product="whisper-large-v3",
    body=json.dumps(wtf_document),
    encoding="json",
    schema="https://datatracker.ietf.org/doc/draft-howe-vcon-wtf-extension/",
)
```

### Resulting `analysis[]` entry

```json
{
  "type": "transcript",
  "dialog": 0,
  "vendor": "openai-whisper",
  "product": "whisper-large-v3",
  "encoding": "json",
  "schema": "https://datatracker.ietf.org/doc/draft-howe-vcon-wtf-extension/",
  "body": "{\"transcript\":{\"text\":\"Hello, this is Foo Corp...\"}, ...}"
}
```

### Common mistakes

* ❌ Putting the transcript in `attachments[]` instead of `analysis[]`
* ❌ Storing the WTF document as a Python dict in `body` — `body` is always a string, paired with `encoding="json"`
* ❌ Adding a separate plain-text transcript analysis — the WTF document already carries `transcript.text`
* ❌ Forgetting `vendor` (the library will raise `TypeError`)
* ❌ Writing `schema_version` instead of `schema`

***

## Lawful Basis (recording consent)

⚖️ **Spec:** [Lawful Basis Extension](/extensions/lawful-basis) · [`draft-howe-vcon-lawful-basis`](https://datatracker.ietf.org/doc/draft-howe-vcon-lawful-basis/) · **Extension name:** `"lawful_basis"`

Use this when your adapter handles conversations covered by GDPR, CCPA, HIPAA, TCPA, state-by-state recording consent laws, or when you're generating synthetic data for which you want auditable origin tracking.

**Critical exception:** the lawful\_basis attachment is the one place where attachments use `type` instead of `purpose`. This is documented in the spec.

### Recipe — consent-based (GDPR Article 6(1)(a))

The library's `add_lawful_basis_attachment` helper requires model objects for `purpose_grants` and `proof_mechanisms`. For most adapters it's easier to build the attachment dict directly and append it:

```python
import json
from foo_adapter.vcon_builder import new_vcon

v = new_vcon(extensions=["lawful_basis"])
v.add_party(tel="+15555550100", role="caller", validation="self-reported")

lawful_basis_attachment = {
    "type": "lawful_basis",  # NOTE: type, not purpose — extension-defined exception
    "encoding": "json",
    "party": 0,
    "dialog": 0,
    "body": json.dumps({
        "lawful_basis": "consent",
        "regulation": "GDPR",
        "expiration": "2027-05-19T00:00:00Z",
        "purpose_grants": [
            {"purpose": "call_recording", "granted_at": "2026-05-19T14:32:00Z"},
            {"purpose": "transcription", "granted_at": "2026-05-19T14:32:00Z"},
            {"purpose": "analysis", "granted_at": "2026-05-19T14:32:00Z"},
        ],
        "proof_mechanisms": [
            {
                "type": "audio_prompt",
                "description": "Caller responded 'yes' to IVR prompt",
                "captured_at": "2026-05-19T14:32:00Z",
            }
        ],
    }),
}
v.vcon_dict["attachments"].append(lawful_basis_attachment)
```

### Recipe — synthetic data

When generating synthetic conversations (test fixtures, training data, demos), document the synthetic origin rather than forging a real consent record:

```python
lawful_basis_attachment = {
    "type": "lawful_basis",
    "encoding": "json",
    "party": 0,
    "dialog": 0,
    "body": json.dumps({
        "lawful_basis": "legitimate_interests",
        "expiration": None,
        "purpose_grants": [
            {"purpose": "recording"},
            {"purpose": "transcription"},
            {"purpose": "analysis"},
            {"purpose": "redistribution"},
        ],
        "proof_mechanisms": [
            {
                "type": "external_system",
                "description": "Synthetic data generated by vcon-faker v2.3 on 2026-05-19",
            }
        ],
    }),
}
v.vcon_dict["attachments"].append(lawful_basis_attachment)
```

Also mark each synthetic party with `validation: "synthetic"`. See the [synthetic data section of the compliance checklist](/vcon-adapters/spec-compliance-checklist#synthetic-test-data).

### Common mistakes

* ❌ Using `purpose: "lawful_basis"` instead of `type: "lawful_basis"` — this attachment is the *one* core spec exception
* ❌ Inventing an ad-hoc `synthetic_data_consent` attachment shape instead of using lawful\_basis with `legitimate_interests` + `external_system` proof
* ❌ Omitting `"lawful_basis"` from top-level `extensions[]`
* ❌ Treating `expiration` as optional for consent-based grounds — GDPR consent without an expiry is brittle

***

## SIP Signaling

📞 **Spec:** [SIP Signaling Extension](/extensions/sip-signaling) · [`draft-howe-vcon-sip-signaling`](https://datatracker.ietf.org/doc/draft-howe-vcon-sip-signaling/) · **Extension name:** `"sip-signaling"`

Use this when your adapter handles telephony — SignalWire, Twilio, Sippy, SIPREC streams, FreeSWITCH. The extension records the SIP-level facts (Call-ID, From/To URIs, P-Asserted-Identity, SDP fingerprints, signaling timestamps) that don't fit cleanly into a generic vCon party/dialog shape.

### Recipe

```python
import json

v = new_vcon(extensions=["sip-signaling"])
v.add_party(tel="+15555550100", sip="sip:caller@example.com", role="caller")
v.add_party(tel="+15555550200", sip="sip:agent@example.com", role="agent")
v.add_dialog(
    type="recording",
    start="2026-05-19T14:32:00Z",
    parties=[0, 1],
    duration=125.3,
    url="https://recordings.example/abc.wav",
    content_hash="sha512-...",
    mediatype="audio/wav",
)

v.add_attachment(
    purpose="sip_signaling",
    body=json.dumps({
        "call_id": "abc123@signalwire.com",
        "from": "sip:caller@example.com",
        "to": "sip:agent@example.com",
        "p_asserted_identity": "+15555550100",
        "invite_at": "2026-05-19T14:31:55Z",
        "answer_at": "2026-05-19T14:32:00Z",
        "bye_at": "2026-05-19T14:34:05Z",
        "sdp_fingerprint": "sha-256 AB:CD:...",
    }),
    encoding="json",
    party=0,
    dialog=0,
)
```

Note this uses `purpose=`, not `type=` — SIP signaling follows the standard core attachment shape (only `lawful_basis` is the exception).

***

## Agent Session

🤖 **Spec:** [Agent Session Extension](/extensions/agent-session) · [`draft-howe-vcon-agent-session`](https://datatracker.ietf.org/doc/draft-howe-vcon-agent-session/) · **Extension name:** `"agent_session"`

Use this when your adapter handles AI-agent conversations — Claude AI exports, LLM tool-use traces, voice-agent sessions. The extension carries the session-level facts the core spec doesn't model: model identity, tool invocations, system-prompt fingerprints, token counts.

### Recipe

```python
v = new_vcon(extensions=["agent_session"])
v.add_party(name="User", role="user")
v.add_party(name="Claude Sonnet 4.6", role="agent", validation="ai_agent")

v.add_dialog(
    type="text",
    start="2026-05-19T14:32:00Z",
    parties=[0],
    originator=0,
    body="Help me debug this Python function",
    mimetype="text/plain",
)
v.add_dialog(
    type="text",
    start="2026-05-19T14:32:02Z",
    parties=[1],
    originator=1,
    body="Sure — paste the function and a sample of the failing input.",
    mimetype="text/plain",
)

v.add_attachment(
    purpose="agent_session",
    body=json.dumps({
        "model": "claude-sonnet-4-6",
        "model_id": "claude-sonnet-4-6-20260319",
        "system_prompt_hash": "sha256:7b8a2f0e...",
        "tools_available": ["Read", "Edit", "Bash"],
        "tool_calls": [],
        "total_input_tokens": 1247,
        "total_output_tokens": 89,
        "session_id": "session_abc123",
    }),
    encoding="json",
    party=0,
    dialog=0,
)
```

The [`vcon-anthropic-chats`](/tools/vcon-anthropic-chats) adapter is a reference implementation if you're building an LLM-export adapter.

***

## Combining extensions

Most production adapters combine three or more. A contact-center adapter typically ships SIP signaling + WTF + lawful basis on every vCon. A voice-AI adapter combines agent session + WTF + lawful basis. Just list every extension you emit in the top-level `extensions[]`:

```python
v = new_vcon(extensions=["sip-signaling", "wtf", "lawful_basis"])
```

There's no ordering constraint and no limit. List them all, then add the corresponding attachments and analyses below.

## Where to read the actual specs

When you need authoritative answers, read the drafts — not this cookbook:

* [WTF Transcription](/extensions/wtf-transcription) → [`draft-howe-vcon-wtf-extension`](https://datatracker.ietf.org/doc/draft-howe-vcon-wtf/)
* [Lawful Basis](/extensions/lawful-basis) → [`draft-howe-vcon-lawful-basis`](https://datatracker.ietf.org/doc/draft-howe-vcon-lawful-basis/)
* [SIP Signaling](/extensions/sip-signaling) → [`draft-howe-vcon-sip-signaling`](https://datatracker.ietf.org/doc/draft-howe-vcon-sip-signaling/)
* [Agent Session](/extensions/agent-session) → [`draft-howe-vcon-agent-session`](https://datatracker.ietf.org/doc/draft-howe-vcon-agent-session/)
* [Lifecycle (SCITT)](/extensions/lifecycle) → [`draft-howe-vcon-lifecycle`](https://datatracker.ietf.org/doc/draft-howe-vcon-lifecycle/)


# vCon Adapter Development Guide

Design patterns for adapter authors going beyond the template — custom architectures, listener shapes, multi-source merging, batch processing.

The [Quick Start From Template](/vcon-adapters/quick-start-from-template) covers \~90% of new adapters. This page is for the other 10% — the cases where the template's shape isn't quite right, where you need to think about architecture rather than just substitute placeholders.

If you haven't read the [Quick Start From Template](/vcon-adapters/quick-start-from-template) and the [Spec Compliance Checklist](/vcon-adapters/spec-compliance-checklist) yet, do that first. This page assumes both.

**Spec target:** [`draft-ietf-vcon-vcon-core-02`](https://datatracker.ietf.org/doc/draft-ietf-vcon-vcon-core/), syntax `"0.4.0"`.

## Listener shapes

How conversation data arrives at your adapter dictates the listener shape. Four common ones, with worked examples from the ecosystem:

### Webhook receiver

Source platforms that push events to you. The adapter exposes an HTTP endpoint, validates the source's signature on the incoming webhook, builds a vCon, and delivers it downstream.

* **When to use:** SignalWire (call.ended), Twilio (call status), Slack (message events), Zoom (meeting.recording.completed), any modern SaaS with outbound webhooks.
* **Skeleton:**

```python
from aiohttp import web
from foo_adapter.vcon_builder import new_vcon
from foo_adapter.webhook_delivery import WebhookDelivery

async def on_call_ended(request: web.Request) -> web.Response:
    # 1. Validate the SOURCE's signature (NOT the same as your downstream HMAC)
    if not verify_source_signature(request):
        return web.Response(status=401)

    event = await request.json()

    # 2. Build the vCon
    v = new_vcon(subject=event.get("title"))
    v.add_party(tel=event["from"], role="caller")
    v.add_party(tel=event["to"], role="agent")
    v.add_dialog(
        type="recording",
        start=event["started_at"],
        parties=[0, 1],
        url=event["recording_url"],
        mediatype="audio/wav",
    )

    # 3. Deliver downstream
    await request.app["delivery"].deliver(v.vcon_dict)
    return web.Response(status=200)
```

The template's `health_server.py` already has an aiohttp app; add your routes to it rather than starting a second server.

### Polling job

Source platforms that don't push but expose a list/since API. The adapter loops on a schedule, asks "anything new since `cursor`?", processes results, advances the cursor.

* **When to use:** REST APIs without webhooks, older PBX systems, ElevenLabs (poll for completed jobs).
* **State management:** persist the cursor somewhere durable — a file, a tiny SQLite DB, Redis. Don't keep it in memory; restarts will replay or skip events.
* **Skeleton:**

```python
import asyncio

async def poll_loop(client, delivery, state_path: Path, interval: float = 300) -> None:
    cursor = load_cursor(state_path)
    while True:
        events = await client.list_completed_since(cursor)
        for event in events:
            v = build_vcon(event)
            ok = await delivery.deliver(v.vcon_dict)
            if ok:
                cursor = event["completed_at"]
        save_cursor(state_path, cursor)
        await asyncio.sleep(interval)
```

[`signalwire_adapter`](https://github.com/vcon-dev/signalwire_adapter) is a polling-based reference.

### File watcher

Source produces files on a disk or in an object store. The adapter watches the location and ingests new files.

* **When to use:** legacy systems that drop CDRs as XML/CSV, audio recorders that write WAV files, Sippy softswitch writing to S3.
* **Local disk:** [`watchdog`](https://pypi.org/project/watchdog/) is the standard library.
* **S3:** poll the bucket with `list_objects_v2` and a marker, or wire up SQS notifications.
* **Critical:** track which files you've already processed. A `.processed` sibling file, a manifest, or a small DB. Re-ingesting the same file produces a *new* UUID and a duplicate vCon downstream.

[`vcon-audio-adapter`](https://github.com/vcon-dev/vcon-audio-adapter) demonstrates the directory-watch pattern.

### Batch CLI

One-shot: read a manifest of records, build vCons, deliver, exit. Run from cron, a Kubernetes Job, a GitHub Action, an ad-hoc terminal.

* **When to use:** backfilling historical data, processing closed datasets (IETF meeting recordings, archive imports), CI-time generation of test fixtures.
* **Pattern:** add a `click` or `argparse` command to `cli.py` that reads the input, loops, and uses the same `WebhookDelivery` for output.

[`ietf2vcon`](https://github.com/vcon-dev/ietf2vcon) is a batch-CLI reference.

## Multi-source adapters

Sometimes one logical conversation lives across two sources — e.g. recording URLs in one system and transcripts in another. Two approaches:

### Stitch in the adapter

Wait until both sides arrive, build a single vCon, deliver. Use this when latency between sides is short (seconds to minutes) and missed-correlation rate is low.

```python
class CallCorrelator:
    def __init__(self):
        self.pending: dict[str, dict] = {}  # call_id → partial event

    def on_event(self, event: dict) -> Vcon | None:
        cid = event["call_id"]
        existing = self.pending.get(cid, {})
        merged = {**existing, **event}
        if "recording_url" in merged and "transcript" in merged:
            self.pending.pop(cid, None)
            return build_vcon(merged)
        self.pending[cid] = merged
        return None
```

Persist `self.pending` across restarts (Redis, SQLite). Set a TTL — after some maximum delay, deliver the partial vCon with whatever you have rather than dropping it.

### Stitch downstream

Deliver one vCon per source, mark each with a correlation tag, let the conserver merge them via a [link](/conserver/standard-links).

```python
v.add_tag("correlation_id", event["call_id"])
v.add_tag("source_role", "recording")  # or "transcript"
```

This is simpler — your adapter stays stateless — and shifts the correlation cost to a place that already does pipeline work. Prefer this unless you have a specific reason not to.

## Choosing where data lives in the vCon

A constant question for adapter authors: "where does X go?" The shortest answer:

| What                                   | Where                                         | Why                                 |
| -------------------------------------- | --------------------------------------------- | ----------------------------------- |
| Audio/video recordings                 | `dialog[]` (`type: "recording"` or `"video"`) | The recording *is* the conversation |
| Text messages, chat lines, IVR prompts | `dialog[]` (`type: "text"`)                   | Each utterance is a dialog turn     |
| Failed calls (no-answer, busy)         | `dialog[]` (`type: "incomplete"`)             | Still a conversation event          |
| Transcripts (WTF or otherwise)         | `analysis[]`                                  | Derived FROM the recording          |
| Sentiment, summaries, intent labels    | `analysis[]`                                  | Derived FROM the conversation       |
| SIP Call-ID, P-Asserted-Identity       | `attachments[]` (`purpose: "sip_signaling"`)  | Signaling metadata about the call   |
| Recording consent, GDPR basis          | `attachments[]` (`type: "lawful_basis"`)      | Legal metadata about the recording  |
| CRM ticket IDs, source row IDs         | `tags` attachment via `add_tag()`             | Lookup keys for joins               |
| Agent name, queue name                 | `party.role`, `party.name`                    | Party metadata                      |

When in doubt: **derived data → `analysis[]`; supplied metadata → `attachments[]`; the conversation itself → `dialog[]`**.

## When NOT to use the template

The template is opinionated. If your situation conflicts with its opinions, fork the structure but keep the [Spec Compliance Checklist](/vcon-adapters/spec-compliance-checklist):

* **Non-HTTP delivery.** Kafka, NATS, gRPC, files on a shared volume, an S3 bucket. The `webhook_delivery.py` shape doesn't fit, but the principles — sign the bytes, idempotency key off the UUID, retry with backoff, persist failures — still apply.
* **Streaming / partial vCons.** If you need to emit progressively-updated vCons as a long call unfolds, the [Lifecycle extension](/extensions/lifecycle) is the right pattern, not webhook fan-out.
* **Embedded inside another service.** Sometimes "the adapter" is a function called inside a larger app. Just import `vcon_builder.py` and skip the CLI/health-server scaffolding.
* **Non-Python.** Use [`vcon-js`](/vcon-js-library) for Node, hand-port to other languages with the same checklist. The spec is language-neutral; the templates are not.

## Validating what you built

The library has a built-in validator:

```python
is_valid, errors = v.is_valid()
if not is_valid:
    raise ValueError(f"Generated invalid vCon: {errors}")
```

The template's smoke tests are a stronger gate — they catch spec-compliance bugs the library validator doesn't (legacy field names, missing required kwargs, hex-vs-base64url content hashes). Copy `tests/test_vcon_builder.py` into any non-template adapter as a starting test suite.

For broader sanity, dump a sample vCon and run it through the [`/vcon-compliance`](https://github.com/vcon-dev/vcon-speckit) skill if you have access — it catches drift the unit tests can't, like outdated spec version references in surrounding code.

## Common pitfalls (real-world drift)

A survey of the existing ecosystem adapters found these recurring bugs. Don't repeat them:

1. **Missing `vcon` syntax field.** `Vcon.build_new()` leaves it empty; `new_vcon()` from the template fills it. Skipping the helper drops the field.
2. **Hand-rolled dicts.** Some older adapters bypass the library entirely. They drift the moment the spec changes. Always go through the library helpers.
3. **`schema_version` in analysis.** Old field name. The library kwarg is `schema`; emit `schema`.
4. **`type` on attachments (other than `lawful_basis`).** Core attachments use `purpose`. Only `lawful_basis` is the documented exception.
5. **Hex content\_hash.** External media `content_hash` MUST be `sha512-<base64url>`. Hex is silently accepted by lenient parsers and silently broken by strict ones.
6. **Timestamps without timezone.** Naive datetimes are ambiguous. Always serialize with `Z` or an explicit offset.
7. **Empty `group: []` / `redacted: {}`.** Drop these unless you actually use them. Some validators flag them.
8. **Transcripts in `attachments[]`.** Wrong shape. Derived data goes in `analysis[]`.

## Further reading

* [Spec Compliance Checklist](/vcon-adapters/spec-compliance-checklist) — the gate every PR should pass
* [Operational Patterns](/vcon-adapters/operational-patterns) — production delivery concerns
* [Extensions Cookbook](/vcon-adapters/extensions-cookbook) — WTF, lawful basis, SIP signaling, agent session
* [vCon Library (Python)](/vcon-library) — full library API reference
* [vcon-speckit](https://github.com/vcon-dev/vcon-speckit) — the authoritative spec digest with legacy-field-name traps


# LLM Guide: Creating vCon Adapters

Drop-into-context guide for LLMs generating vCon adapter code. Spec target: draft-ietf-vcon-vcon-core-02, syntax 0.4.0. Pairs with the vcon-adapter-template repo.

This page is designed to be pasted into a model's context window when you want it to generate adapter code. It encodes the spec target, the canonical scaffold, the library API, and the legacy-field-name traps in one place.

## Ground truth

**Spec:** IETF [`draft-ietf-vcon-vcon-core-02`](https://datatracker.ietf.org/doc/draft-ietf-vcon-vcon-core/). The `vcon` syntax parameter is the string `"0.4.0"`. Any older value (`0.0.1`, `0.0.2`, `0.2.0`, `0.3.0`) is wrong.

**Canonical scaffold:** [vcon-dev/vcon-adapter-template](https://github.com/vcon-dev/vcon-adapter-template). New adapters SHOULD start from this template — it ships a `vcon_builder.py` wrapper, HMAC webhook delivery, retries/DLQ, health + Prometheus endpoints, and 14 spec-compliance smoke tests.

**Library:** [`vcon`](https://pypi.org/project/vcon/) ≥ 0.9.4. The lib's helpers are spec-correct out of the box — use them instead of writing to `vcon_dict[...]` directly.

## The one rule

> Always use the `vcon` library helpers (`add_party`, `add_dialog`, `add_attachment`, `add_analysis`, `add_tag`). Never write directly to `vcon_dict[...]` except for the four documented quirks below.

## Imports

```python
import hashlib
import json
import logging
from abc import ABC, abstractmethod
from base64 import urlsafe_b64encode
from datetime import datetime, timezone
from typing import Any

from vcon import Vcon
```

`Party` and `Dialog` model classes are no longer commonly used — pass values as kwargs to `add_party()` / `add_dialog()` instead.

## Create a vCon (handle the four quirks)

`Vcon.build_new()` has four spec-incorrect behaviors that every adapter must paper over. The template's `new_vcon()` helper does it for you:

```python
def new_vcon(
    *,
    subject: str | None = None,
    extensions: list[str] | None = None,
) -> Vcon:
    v = Vcon.build_new()
    v.vcon_dict["vcon"] = "0.4.0"                # 1. Set the syntax parameter
    if subject is not None:
        v.vcon_dict["subject"] = subject         # 2. No setter on the class
    v.vcon_dict.pop("group", None)               # 3. Drop empty placeholder
    v.vcon_dict.pop("redacted", None)            # 4. Drop empty placeholder
    if extensions:
        v.vcon_dict["extensions"] = list(extensions)
    return v
```

Always call this (or equivalent) instead of `Vcon.build_new()` directly.

## Base adapter pattern

```python
class BaseVconAdapter(ABC):
    """Base class for vCon adapters."""

    def __init__(self, config: dict[str, Any]):
        self.config = config
        self.logger = logging.getLogger(self.__class__.__name__)

    @abstractmethod
    def extract_data(self, source: Any) -> dict[str, Any]:
        """Pull raw conversation data out of the source system."""

    @abstractmethod
    def transform_to_vcon(self, raw_data: dict[str, Any]) -> Vcon:
        """Turn raw data into a vCon."""

    def validate(self, v: Vcon) -> None:
        is_valid, errors = v.is_valid()
        if not is_valid:
            raise ValueError(f"Invalid vCon: {errors}")

    def process(self, source: Any) -> Vcon:
        raw = self.extract_data(source)
        v = self.transform_to_vcon(raw)
        self.validate(v)
        return v
```

## Parties

```python
participant_map: dict[str, int] = {}
for i, p in enumerate(raw_data.get("participants", [])):
    v.add_party(
        name=p.get("name"),
        tel=p.get("phone"),
        mailto=p.get("email"),
        sip=p.get("sip_uri"),          # OK in 0.4.0
        timezone=p.get("timezone"),    # OK in 0.4.0
        role=p.get("role", "participant"),
    )
    participant_map[p["id"]] = i
```

Do NOT pass `did=` — the `did` field was removed in 0.4.0.

## Dialogs (text, recording, video)

```python
v.add_dialog(
    type="text",                         # or "recording", "video", "transfer", "incomplete"
    start=parse_timestamp(msg["timestamp"]),
    parties=[participant_map[msg["sender_id"]]],
    originator=participant_map[msg["sender_id"]],
    mimetype="text/plain",
    body=msg["content"],
)
```

## External media (recordings)

```python
def sha512_b64url(data: bytes) -> str:
    return "sha512-" + urlsafe_b64encode(hashlib.sha512(data).digest()).rstrip(b"=").decode("ascii")

v.add_dialog(
    type="recording",
    start=parse_timestamp(media["timestamp"]),
    parties=parties,
    mediatype="audio/wav",
    url=media["url"],
    content_hash=sha512_b64url(media["bytes"]),  # MUST be sha512-<base64url>
    duration=media.get("duration"),
)
```

Do NOT emit a hex `content_hash`. Always `sha512-<base64url-of-digest>` (no `=` padding).

## Analysis (transcripts, sentiment, summaries)

```python
v.add_analysis(
    type="transcript",
    dialog=0,
    vendor="openai-whisper",                                          # REQUIRED
    product="whisper-large-v3",
    body=json.dumps(wtf_document),
    encoding="json",
    schema="https://datatracker.ietf.org/doc/draft-howe-vcon-wtf-extension/",
)
```

Field name is `schema`, NOT `schema_version`. `vendor` is REQUIRED — the lib raises `TypeError` if you omit it.

## Attachments (metadata, signaling, consent)

Standard core attachment — uses `purpose`:

```python
v.add_attachment(
    purpose="call_metadata",     # NEVER "type" for core attachments
    body=json.dumps({"queue": "support", "skill": "billing"}),
    encoding="json",
    party=0,                     # REQUIRED — use 0 for vCon-level
    dialog=0,                    # REQUIRED — use 0 for vCon-level
)
```

The lawful\_basis extension is the **only** documented exception — it uses `type: "lawful_basis"`. See [Extensions Cookbook](/vcon-adapters/extensions-cookbook).

## Tags

```python
v.add_tag("source", "your_platform")
v.add_tag("call_id", raw_data["call_id"])
```

Library ≥0.9.3 writes `party: 0, dialog: 0` on the tags attachment correctly.

## Extensions

```python
v = new_vcon(extensions=["sip-signaling", "wtf", "lawful_basis"])
```

Every extension used MUST appear in top-level `extensions[]`. The template includes this in `new_vcon()`.

## NEVER write these field names

| ❌ Never          | ✅ Always   | Where                               |
| ---------------- | ---------- | ----------------------------------- |
| `appended`       | `amended`  | top-level metadata                  |
| `must_support`   | `critical` | top-level metadata                  |
| `schema_version` | `schema`   | analysis                            |
| `type`           | `purpose`  | attachments (except `lawful_basis`) |
| `did`            | (removed)  | party                               |
| `0.2.0`, `0.3.0` | `"0.4.0"`  | `vcon` syntax param                 |

## Timestamps

Always ISO-8601 with timezone:

```python
def parse_timestamp(ts: Any) -> str:
    if isinstance(ts, datetime):
        if ts.tzinfo is None:
            ts = ts.replace(tzinfo=timezone.utc)
        return ts.isoformat()
    if isinstance(ts, str):
        from dateutil import parser
        return parser.parse(ts).isoformat()
    if isinstance(ts, (int, float)):
        return datetime.fromtimestamp(ts, timezone.utc).isoformat()
    raise ValueError(f"Unparseable timestamp: {ts!r}")
```

Never emit naive datetimes.

## Dialog types

| Type           | Use for                                                  |
| -------------- | -------------------------------------------------------- |
| `"text"`       | Messages, chat, IVR prompts, individual transcript turns |
| `"recording"`  | Audio recordings                                         |
| `"video"`      | Video recordings / calls                                 |
| `"transfer"`   | Call transfers — see `add_transfer_dialog`               |
| `"incomplete"` | Failed/abandoned calls — see `add_incomplete_dialog`     |

## MIME types (mediatype)

* Text: `"text/plain"`, `"text/html"`
* Audio: `"audio/wav"`, `"audio/mp3"`, `"audio/ogg"`, `"audio/x-wav"`
* Video: `"video/mp4"`, `"video/webm"`
* Email: `"message/rfc822"`

The field name is `mediatype`, not `mimetype`, in the spec (the library accepts both as kwarg names).

## Validation

Always end `transform_to_vcon` with library validation:

```python
is_valid, errors = v.is_valid()
if not is_valid:
    raise ValueError(f"Invalid vCon: {errors}")
```

For stronger checking, copy the smoke tests from [`vcon-adapter-template/tests/test_vcon_builder.py`](https://github.com/vcon-dev/vcon-adapter-template/blob/main/tests/test_vcon_builder.py).

## Testing pattern

```python
def test_adapter_produces_compliant_vcon():
    adapter = MyAdapter({})
    v = adapter.process(sample_input)

    # Library validation
    is_valid, errors = v.is_valid()
    assert is_valid, errors

    # Spec compliance
    assert v.vcon_dict["vcon"] == "0.4.0"
    assert "group" not in v.vcon_dict
    assert "redacted" not in v.vcon_dict
    serialized = json.dumps(v.vcon_dict)
    assert "appended" not in serialized        # legacy field name
    assert "must_support" not in serialized    # legacy field name
    assert "schema_version" not in serialized  # legacy field name

    # Structural sanity
    assert len(v.vcon_dict["parties"]) > 0
    assert len(v.vcon_dict["dialog"]) > 0
```

## Delivery (downstream)

For HTTP webhook delivery, sign the body with HMAC-SHA256 (`X-Hub-Signature-256: sha256=<hex>`), key the request off the vCon `uuid` as `Idempotency-Key`, retry with exponential backoff, persist failures to a dead-letter queue. The template's [`webhook_delivery.py`](https://github.com/vcon-dev/vcon-adapter-template/blob/main/src/__ADAPTER_PACKAGE__/webhook_delivery.py) is the reference implementation. See [Operational Patterns](/vcon-adapters/operational-patterns).

## Key considerations

1. Use the library helpers; never hand-roll `vcon_dict[...]` (except the four `new_vcon` quirks).
2. Set `vcon` syntax to `"0.4.0"`.
3. Drop empty `group: []` and `redacted: {}` from `build_new()`.
4. Attachments use `purpose` — except `lawful_basis`, which uses `type`.
5. Analysis uses `schema`, never `schema_version`. `vendor` is required.
6. Transcripts live in `analysis[]`, not `attachments[]`.
7. `content_hash` is `sha512-<base64url>`, never hex.
8. List every extension you use in top-level `extensions[]`.
9. Timestamps are ISO-8601 with timezone.
10. Validate before returning.

When generating adapter code, ground every decision on the [Spec Compliance Checklist](/vcon-adapters/spec-compliance-checklist) and the [Extensions Cookbook](/vcon-adapters/extensions-cookbook). If you're unsure, prefer the shape used by [`vcon-adapter-template`](https://github.com/vcon-dev/vcon-adapter-template).


# Extensions

vCon extensions add structured data for specific use cases without breaking the core spec.

The vCon core specification ([`draft-ietf-vcon-vcon-core-02`](https://datatracker.ietf.org/doc/draft-ietf-vcon-vcon-core/)) keeps the container small on purpose. Anything beyond parties, dialog, analysis, and attachments lives in an extension — a separately documented spec that adds new fields, attachment purposes, analysis types, or external lifecycle behavior.

## How extensions work

Two top-level fields on a vCon coordinate extensions:

* **`extensions[]`** — strings listing every extension this vCon uses. Consumers can use this to decide whether they have enough support to safely process the vCon.
* **`must_understand[]`** — a subset of `extensions[]` that consumers MUST be able to interpret. If a consumer sees a value here that it doesn't understand, it must refuse to process the vCon rather than silently drop data. (In older drafts and library code this field was called `must_support` or `critical` — they mean the same thing.)

A typical signal looks like this:

```json
{
  "vcon": "0.4.0",
  "uuid": "...",
  "extensions": ["lawful_basis", "wtf"],
  "must_understand": ["lawful_basis"],
  "parties": [...],
  "dialog": [...],
  "analysis": [...],
  "attachments": [...]
}
```

## Available extensions

| Extension                                          | Purpose                                                                                                | Where it lives                                                                                                | Draft                                                                                              |
| -------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| [Lawful Basis](/extensions/lawful-basis)           | Records the legal grounds for processing conversation data (GDPR / privacy)                            | `attachments[]` with `type: "lawful_basis"`                                                                   | [`draft-howe-vcon-lawful-basis`](https://datatracker.ietf.org/doc/draft-howe-vcon-lawful-basis/)   |
| [Lifecycle](/extensions/lifecycle)                 | SCITT-anchored append-only ledger of vCon lifecycle events (create, send, consent, delete)             | External SCITT ledger; metadata-only on the vCon                                                              | [`draft-howe-vcon-lifecycle`](https://datatracker.ietf.org/doc/draft-howe-vcon-lifecycle/)         |
| [WTF Transcription](/extensions/wtf-transcription) | World Transcription Format — provider-agnostic shape for speech-to-text output                         | `analysis[]` with `type: "wtf_transcription"` (or `type: "transcript"` + `schema:` pointing at the WTF draft) | [`draft-howe-vcon-wtf-extension`](https://datatracker.ietf.org/doc/draft-howe-vcon-wtf/)           |
| [Agent Session](/extensions/agent-session)         | Captures an AI agent's session trace (prompts, tool calls, artifacts) alongside the human conversation | `parties[].meta.agent_session`, `analysis[].type: "agent_trace"`, `attachments[].purpose: "agent_*"`          | [`draft-howe-vcon-agent-session`](https://datatracker.ietf.org/doc/draft-howe-vcon-agent-session/) |
| [SIP Signaling](/extensions/sip-signaling)         | SIP call metadata, STIR/SHAKEN certificates, and signaling traces                                      | `parties[].sip_*`, `dialog[].sip_*`, `attachments[].purpose: "sip-*"`                                         | [`draft-howe-vcon-sip-signaling`](https://datatracker.ietf.org/doc/draft-howe-vcon-sip-signaling/) |

## A note on field naming

The core spec uses `purpose` on attachments (never `type`). The **Lawful Basis** extension is the one documented exception — it uses `type: "lawful_basis"` because the attachment is treated as a typed structural object rather than a free-form payload. Every other extension that touches `attachments[]` uses `purpose`.

If you see code or older docs that put `type:` on an attachment that isn't lawful\_basis, that's a legacy pattern from pre-spec-02 libraries; the spec-correct form is `purpose:`.

## Related work (not vCon extensions, but commonly confused with them)

* [`draft-howe-sipcore-mcp-extension`](https://datatracker.ietf.org/doc/draft-howe-sipcore-mcp-extension/) is a SIP protocol extension (SIPCORE WG, not the vCon WG). It defines a SIP option tag, headers, and a media type for carrying MCP payloads inside SIP sessions. It does **not** add fields to a vCon — but it does come up in conversations about MCP-enhanced telephony, so it's worth knowing where it lives.

## When to define a new extension

Don't add fields to a vCon outside of an extension; consumers won't know what to do with them and may reject the vCon. If you have data that doesn't fit one of the extensions above:

1. Check whether the data fits in `analysis[]` (anything derived from the conversation) or `attachments[]` (anything supplied alongside it). Most use cases do.
2. If you genuinely need new top-level fields or new attachment purposes/analysis types, write an extension draft. The `vcon-speckit` repo has templates; the IETF VCON working group is the venue.


# Lawful Basis

Records the legal grounds for processing conversation data, with cryptographic proof and granular per-purpose consent.

**Draft:** [`draft-howe-vcon-lawful-basis`](https://datatracker.ietf.org/doc/draft-howe-vcon-lawful-basis/) · **Extension name:** `"lawful_basis"`

## What it is

Most data privacy regimes — GDPR in the EU, CCPA in California, and a growing list of others — require that you can demonstrate the legal grounds on which you collected, stored, processed, and shared each piece of personal data. For conversations, that question has historically been answered with a separate document, a separate database, or nothing at all.

The Lawful Basis extension puts the answer **inside the vCon itself**. The extension defines a structured attachment that records the lawful basis under which the conversation is being processed, the specific purposes that basis covers, when consent (if any) expires, and a cryptographic proof binding the basis to the vCon's content.

## When to use it

* Any conversation involving EU data subjects (GDPR consent or legitimate-interest grounds)
* Contact-center recordings subject to state-by-state recording-consent laws
* Healthcare conversations subject to HIPAA or equivalent
* Sales conversations covered by TCPA / Do Not Call obligations
* Synthetic-conversation datasets where you want to make the synthetic-data origin auditable rather than implied

## Spec surface

The extension adds a single entry to `attachments[]`, using `type: "lawful_basis"` (this is the documented exception to the core spec's "use `purpose`" rule):

```json
{
  "type": "lawful_basis",
  "encoding": "json",
  "party": 0,
  "dialog": 0,
  "body": {
    "lawful_basis": "consent",
    "expiration": "2026-01-02T12:00:00Z",
    "purpose_grants": [
      { "purpose": "recording",     "granted": true, "granted_at": "2025-01-02T12:15:30Z" },
      { "purpose": "transcription", "granted": true, "granted_at": "2025-01-02T12:15:30Z" },
      { "purpose": "analysis",      "granted": true, "granted_at": "2025-01-02T12:15:30Z" }
    ],
    "proof_mechanisms": [
      {
        "mechanism_type": "audio_recording",
        "dialog_index": 0,
        "description": "Verbal consent captured at start of recording"
      }
    ]
  }
}
```

**Required body fields:**

* `lawful_basis` — one of `consent`, `contract`, `legal_obligation`, `vital_interests`, `public_task`, `legitimate_interests` (the six GDPR bases). The first five require an expiration; `legitimate_interests` can use `null` if the basis is ongoing.
* `expiration` — ISO 8601 timestamp, or `null` for ongoing legitimate-interest grounds.
* `purpose_grants[]` — at least one entry. Each grant identifies a specific processing purpose (e.g. `recording`, `transcription`, `analysis`, `redistribution`) and whether it was granted.
* `proof_mechanisms[]` — at least one entry documenting how the basis was established (e.g. an audio segment containing verbal consent, an external-system reference, a signed document).

Don't forget to declare the extension at the top level:

```json
{
  "vcon": "0.4.0",
  "extensions": ["lawful_basis"],
  "must_understand": ["lawful_basis"]
}
```

Adding `"lawful_basis"` to `must_understand` is the safer default — consumers that don't know how to process lawful-basis data should refuse the vCon rather than silently lose the consent record.

## Python helper

The `vcon` library has an `add_lawful_basis_attachment()` helper, but it requires constructing model objects for `purpose_grants` and `proof_mechanisms`. In practice it's often simpler to build the attachment dict directly and append it:

```python
from vcon import Vcon

v = Vcon.build_new()
v.vcon_dict["vcon"] = "0.4.0"  # the lib doesn't set this for you

# ... add parties, dialog, etc.

v.vcon_dict["attachments"].append({
    "type": "lawful_basis",
    "encoding": "json",
    "party": 0,
    "dialog": 0,
    "body": {
        "lawful_basis": "consent",
        "expiration": "2026-01-02T12:00:00Z",
        "purpose_grants": [
            {"purpose": "recording",     "granted": True, "granted_at": "2025-01-02T12:15:30Z"},
            {"purpose": "transcription", "granted": True, "granted_at": "2025-01-02T12:15:30Z"},
        ],
        "proof_mechanisms": [
            {"mechanism_type": "audio_recording", "dialog_index": 0,
             "description": "Verbal consent captured at start of recording"}
        ],
    },
})
v.add_extension("lawful_basis")
```

## Synthetic-data pattern

For synthetic conversations (e.g. corpora generated by `vcon_faker`), use `legitimate_interests` with `expiration: null`, and document the synthetic origin in a `proof_mechanism` of type `external_system`:

```json
{
  "lawful_basis": "legitimate_interests",
  "expiration": null,
  "purpose_grants": [
    { "purpose": "recording",      "granted": true },
    { "purpose": "transcription",  "granted": true },
    { "purpose": "analysis",       "granted": true },
    { "purpose": "redistribution", "granted": true }
  ],
  "proof_mechanisms": [
    {
      "mechanism_type": "external_system",
      "description": "Synthetic conversation generated by vcon_faker; no real data subject"
    }
  ]
}
```

Also mark the parties as synthetic with `validation: "synthetic"` so downstream consumers know not to treat them as real identities.

## See also

* [Privacy-First Conversation Management](/deep-dives/privacy-first-conversation-management) — the design rationale and operational pattern.
* [Lifecycle](/extensions/lifecycle) — the lifecycle extension records consent acceptance and revocation events on a SCITT ledger; the two extensions are designed to be used together.


# Lifecycle (SCITT)

Append-only audit ledger anchored in SCITT for vCon creation, transmission, consent, and deletion events.

**Draft:** [`draft-howe-vcon-lifecycle`](https://datatracker.ietf.org/doc/draft-howe-vcon-lifecycle/) · **Extension name:** `"lifecycle"`

## What it is

A vCon is a snapshot. The question regulators and auditors usually want to answer — "what happened to this conversation, when, and who saw it?" — needs more than a snapshot. The Lifecycle extension answers that question by anchoring vCon events in an external [SCITT (Supply Chain Integrity, Transparency, and Trust)](/deep-dives/scitt-supply-chain-integrity-transparency-and-trust) ledger.

Unlike most extensions, **Lifecycle adds no per-vCon fields**. The vCon itself stays small. The lifecycle data lives on an append-only, cryptographically verifiable SCITT ledger that the vCon and its downstream consumers reference.

## When to use it

* GDPR compliance: demonstrate the chain of consent acceptance, transfers, and (eventually) deletion in response to a Right to Erasure request.
* CCPA / state-level privacy laws with similar audit requirements.
* Internal compliance for regulated industries (healthcare, financial services) where conversation handling has to be auditable end-to-end.
* Multi-party processing pipelines (call recording → transcription → analysis → CRM) where each handoff needs an independent, tamper-evident record.

## Spec surface

Lifecycle defines a vocabulary of event types that get recorded on a SCITT ledger. The events the draft enumerates include:

| Event                   | When to record it                                                   |
| ----------------------- | ------------------------------------------------------------------- |
| `vcon_created`          | New vCon assembled and signed                                       |
| `vcon_enhanced`         | An analysis or attachment was added                                 |
| `vcon_sent`             | The vCon (or a derived projection) was transmitted to another party |
| `vcon_received`         | The vCon was received from another party                            |
| `vcon_consent_accepted` | Consent (lawful basis) was recorded for the conversation            |
| `vcon_consent_revoked`  | A party revoked consent — triggers downstream cleanup               |
| `vcon_deleted`          | The vCon was deleted in response to a Right to Erasure request      |

Each event entry on the SCITT ledger references the vCon's UUID, includes a timestamp, and is signed by the party producing it. The SCITT receipt that comes back from the ledger is the durable proof that the event happened.

To signal that a vCon participates in this lifecycle scheme, declare the extension:

```json
{
  "vcon": "0.4.0",
  "extensions": ["lifecycle"]
}
```

You generally do **not** put `"lifecycle"` in `must_understand[]` — consumers that don't speak SCITT can still process the vCon, they just won't validate the audit trail.

## Operational pattern

A typical flow:

1. **Create.** Build the vCon, sign it, post a `vcon_created` entry to the SCITT ledger. Store the receipt alongside the vCon.
2. **Consent.** When you add a [lawful basis attachment](/extensions/lawful-basis), post a `vcon_consent_accepted` entry.
3. **Process.** Each time you add analysis or share the vCon, post `vcon_enhanced` / `vcon_sent` / `vcon_received` entries.
4. **Revocation.** If a data subject revokes consent, post `vcon_consent_revoked`. Downstream consumers watching the ledger trigger their own deletion workflows.
5. **Deletion.** Post `vcon_deleted` after the vCon (and any derived data) has been removed. The deletion event itself remains on the ledger forever — that's the audit trail.

## See also

* [vCon Lifecycle Management using SCITT](/deep-dives/vcon-lifecycle-management-using-scitt) — the long-form rationale and walkthrough.
* [SCITT: Supply Chain Integrity, Transparency and Trust](/deep-dives/scitt-supply-chain-integrity-transparency-and-trust) — what SCITT is and why it fits here.
* [Lawful Basis](/extensions/lawful-basis) — the consent record that lifecycle events reference.


# WTF Transcription

World Transcription Format — a vendor-neutral analysis shape for speech-to-text output.

**Draft:** [`draft-howe-vcon-wtf-extension`](https://datatracker.ietf.org/doc/draft-howe-vcon-wtf/) · **Extension name:** `"wtf"` (often emitted as `"wtf_transcription"` by older library code)

## What it is

Every speech-to-text provider — Whisper, Deepgram, AssemblyAI, Google, AWS, Azure, ElevenLabs, the next one — has its own JSON output shape. If you want to swap providers, compare them on the same audio, or build downstream tooling that doesn't care who did the transcription, you end up writing adapter code over and over.

The World Transcription Format (WTF) extension defines a single canonical shape for that output. It covers the transcript text, time-aligned segments, optional word-level timing, optional speaker labels, quality metrics, and provider metadata.

WTF data lives in `analysis[]`, not `attachments[]` — it's *derived from* the conversation, not supplied alongside it.

## When to use it

* Recording → transcription pipelines where you may switch providers later.
* Quality benchmarking: same audio, multiple providers, identical downstream code.
* LLM ingestion: a stable transcript shape means prompts and parsers don't change when the ASR vendor does.
* Speaker diarization workflows: WTF carries speaker labels in a standard way.

## Spec surface

The WTF document is added as an `analysis[]` entry. The recommended form per the speckit is to use `type: "transcript"` and identify WTF via the `schema:` URL — this stays compatible with consumers that just want "any transcript". Older library code (and the draft's own examples) use `type: "wtf_transcription"`; both forms are valid as long as `schema:` points at the WTF draft.

```json
{
  "analysis": [
    {
      "type": "transcript",
      "dialog": 0,
      "vendor": "openai-whisper",
      "product": "whisper-large-v3",
      "encoding": "json",
      "schema": "https://datatracker.ietf.org/doc/draft-howe-vcon-wtf/",
      "body": "{\"transcript\":{\"text\":\"Hello, I need help with my account.\",\"language\":\"en\",\"duration\":3.2,\"confidence\":0.95},\"segments\":[{\"id\":0,\"start\":0.0,\"end\":3.2,\"text\":\"Hello, I need help with my account.\",\"confidence\":0.95}],\"metadata\":{\"created_at\":\"2026-05-18T10:00:00Z\",\"provider\":\"whisper\",\"model\":\"whisper-large-v3\"}}"
    }
  ]
}
```

**Required analysis fields:**

* `type` — `"transcript"` (recommended) or `"wtf_transcription"`.
* `dialog` — index of the dialog this transcription covers.
* `vendor` — REQUIRED by the core spec. Identifies the ASR provider (e.g. `"openai-whisper"`, `"deepgram"`, `"assemblyai"`).
* `product` — the specific model (e.g. `"whisper-large-v3"`, `"nova-2"`).
* `encoding` — `"json"`.
* `schema` — URL pointing at the WTF draft, so consumers know how to parse `body`.
* `body` — JSON-encoded WTF document as a **string**. The body is always a string in vCon; pair it with `encoding: "json"` to indicate the string is itself JSON.

## The WTF document shape

Inside `body` (decoded), the WTF document has four top-level sections:

```json
{
  "transcript": {
    "text": "Hello, I need help with my account.",
    "language": "en",
    "duration": 3.2,
    "confidence": 0.95
  },
  "segments": [
    { "id": 0, "start": 0.0, "end": 3.2, "text": "Hello, I need help with my account.",
      "confidence": 0.95, "speaker": 0 }
  ],
  "speakers": [
    { "id": 0, "label": "Customer", "segments": [0], "total_time": 3.2, "confidence": 0.95 }
  ],
  "metadata": {
    "created_at": "2026-05-18T10:00:00Z",
    "provider": "whisper",
    "model": "whisper-large-v3"
  }
}
```

`transcript` and `segments` are required. `speakers` is optional (use it when diarization was performed). `metadata` carries provider/model details and processing context.

Word-level timing is optional and lives inside each segment as a `words[]` array:

```json
{ "id": 0, "start": 0.0, "end": 0.5, "text": "Hello", "confidence": 0.98, "speaker": 0 }
```

Don't forget to declare the extension at the top level:

```json
{
  "vcon": "0.4.0",
  "extensions": ["wtf"]
}
```

## Python helper

The `vcon` Python library has `add_wtf_transcription_attachment()`. Be aware of two quirks:

1. The helper places the transcription as an **attachment**, not an analysis entry. Spec-compliant code puts WTF data in `analysis[]`. You can either rebuild the attachment manually (shown above) or call the helper and then move the entry.
2. The helper emits `type: "wtf_transcription"` — you may want to rename this to `purpose:` (if you keep it as an attachment) or to `type: "transcript"` (if you move it into `analysis[]`).

The direct-construction pattern is usually simpler:

```python
import json
from vcon import Vcon

v = Vcon.build_new()
v.vcon_dict["vcon"] = "0.4.0"
# ... add parties, dialog ...

wtf_doc = {
    "transcript": {"text": "...", "language": "en", "duration": 3.2, "confidence": 0.95},
    "segments": [{"id": 0, "start": 0.0, "end": 3.2, "text": "...", "confidence": 0.95}],
    "metadata": {"provider": "whisper", "model": "whisper-large-v3",
                 "created_at": "2026-05-18T10:00:00Z"},
}

v.vcon_dict["analysis"].append({
    "type": "transcript",
    "dialog": 0,
    "vendor": "openai-whisper",
    "product": "whisper-large-v3",
    "encoding": "json",
    "schema": "https://datatracker.ietf.org/doc/draft-howe-vcon-wtf/",
    "body": json.dumps(wtf_doc),
})
v.add_extension("wtf")
```

## See also

* [Standard Links — Conserver](/conserver/standard-links) — the conserver ships Whisper and Deepgram links that emit WTF-shaped analysis.
* [Speech Recognition Test Set](/use-cases-studies/speech-recognition-test-set) — multi-provider benchmarking is one of WTF's design goals.


# Agent Session

Captures an AI agent's session trace — prompts, tool calls, results, artifacts — alongside the human-facing conversation.

**Draft:** [`draft-howe-vcon-agent-session`](https://datatracker.ietf.org/doc/draft-howe-vcon-agent-session/) · **Extension name:** `"agent_session"`

## What it is

A growing number of "conversations" involve an AI agent on at least one side. The human-facing surface of that interaction — the words exchanged, the time, the parties — fits cleanly into a normal vCon. The *agent's* internal session — the prompts it received, the tools it called, the responses those tools returned, the files it touched, the reasoning it produced — does not. That data is just as important for audit, debugging, training, and compliance, but it sits in a different shape.

The Agent Session extension brings that data into the vCon as a peer to the human dialog. It uses vCon's existing primitives:

* The agent is represented as a **party** with `role: "agent"` and structured metadata describing the model.
* The session trace lives in **analysis** as a Verifiable Agent Conversations (VAC) document.
* Agent-produced artifacts and environment snapshots live in **attachments**.

That means agent sessions inherit everything vCon already has — consent, signing, redaction, lifecycle, SCITT.

## When to use it

* Documenting LLM-driven contact-center deflection: the LLM's full prompt-and-tool-call trace is auditable alongside the call recording.
* Coding-agent sessions (Claude Code, Cursor, etc.) where the conversation, the tool invocations, and the file edits are all part of the same record.
* AI-assisted workflows where the agent's output needs to be reproducible: the trace tells you exactly what the agent did and why.
* Bridging vCon and [VAC (Verifiable Agent Conversations)](https://datatracker.ietf.org/doc/draft-howe-vcon-agent-session/) — the two specs are designed to compose.

## Spec surface

Agent Session adds data in three places.

### 1. Party metadata

Each agent participant is a normal party, distinguished by `role: "agent"` and a structured `meta.agent_session` block:

```json
{
  "parties": [
    {
      "name": "Customer",
      "role": "customer"
    },
    {
      "name": "Claude",
      "role": "agent",
      "meta": {
        "agent_session": {
          "model_id": "claude-opus-4-7",
          "provider": "anthropic",
          "recording_agent": "claude-code/1.5.0",
          "environment": "production"
        }
      }
    }
  ]
}
```

### 2. Session trace in `analysis[]`

The full VAC session trace is a JSON document stored as an analysis entry with `type: "agent_trace"`:

```json
{
  "analysis": [
    {
      "type": "agent_trace",
      "dialog": 0,
      "vendor": "anthropic",
      "product": "claude-opus-4-7",
      "encoding": "json",
      "schema": "https://datatracker.ietf.org/doc/draft-howe-vcon-agent-session/",
      "body": "{\"version\":\"1.0\",\"session_trace\":{\"messages\":[...],\"tool_calls\":[...]}}"
    }
  ]
}
```

The `body` is the VAC document, encoded as a JSON string. `vendor`, `product`, and `schema` identify the agent and the trace format.

### 3. Artifacts in `attachments[]`

Files the agent produced, environment snapshots, and tool-call payloads go in `attachments[]` with one of these purposes:

| Purpose             | Meaning                                                                  |
| ------------------- | ------------------------------------------------------------------------ |
| `agent_file_change` | A file the agent created, modified, or deleted                           |
| `agent_artifact`    | A standalone artifact the agent produced (e.g. a generated document)     |
| `agent_environment` | A snapshot of the agent's runtime environment at the time of the session |

```json
{
  "attachments": [
    {
      "purpose": "agent_file_change",
      "party": 1,
      "dialog": 0,
      "filename": "src/payment_handler.py",
      "mediatype": "text/x-python",
      "encoding": "none",
      "body": "def process_payment(amount, ...): ..."
    }
  ]
}
```

Declare the extension at the top level:

```json
{
  "vcon": "0.4.0",
  "extensions": ["agent_session"]
}
```

## Relationship to other extensions

* **Lawful basis.** Agent-session data is personal data when the agent participated in a conversation with a real person. Use the [Lawful Basis extension](/extensions/lawful-basis) the same way you would for the human-only case.
* **Lifecycle.** Agent traces benefit especially from a [Lifecycle](/extensions/lifecycle) ledger — being able to prove what the agent saw and did, when, is the entire compliance story for AI-assisted workflows.
* **WTF.** If the agent session also produced spoken output (TTS), that recording is normal vCon dialog, optionally transcribed via [WTF](/extensions/wtf-transcription).

## See also

* The `vcon-anthropic-chats` adapter (see [Tools](/tools)) converts Claude AI conversation exports into vCons using this extension.
* The `vcon-vac` project is the reference implementation tying VAC traces into vCons.


# SIP Signaling

SIP call signaling metadata, STIR/SHAKEN certificates, and signaling traces attached to a vCon.

**Draft:** [`draft-howe-vcon-sip-signaling`](https://datatracker.ietf.org/doc/draft-howe-vcon-sip-signaling/) · **Extension name:** `"sip-signaling"`

## What it is

A vCon built from a SIP call contains the audio (in dialog) and the parties, but by default loses everything the SIP infrastructure knew about the call — Call-IDs, From/To tags, CSeq numbers, the SDP that negotiated the media, the STIR/SHAKEN certificate chain that authenticated the caller. That metadata matters for fraud investigation, regulatory compliance (TRACED Act), and correlation with carrier-side logs.

The SIP Signaling extension carries that data into the vCon as structured party fields, dialog fields, and attachments.

## When to use it

* Carrier and enterprise SIP environments where calls flow through SIP recording (SIPREC) or media gateways
* TRACED Act / robocall mitigation: STIR/SHAKEN attestation data attached to the vCon
* Fraud investigation: correlating vCons with carrier-side CDR / call traces
* Operational debugging: keeping the signaling envelope alongside the recording when something went wrong

## Spec surface

### Party fields

Each party can carry SIP-specific identity and User-Agent info:

```json
{
  "parties": [
    {
      "tel": "+15551234567",
      "name": "Alice",
      "role": "customer",
      "sip": "alice@example.com",
      "sip_contact": "sip:alice@192.0.2.1:5060",
      "sip_user_agent": "ExamplePhone/2.1",
      "sip_display_name": "Alice Anderson"
    }
  ]
}
```

### Dialog fields

Each dialog (call leg) can carry the SIP dialog identifiers:

```json
{
  "dialog": [
    {
      "type": "recording",
      "start": "2026-05-18T14:00:00Z",
      "parties": [0, 1],
      "mediatype": "audio/x-wav",
      "url": "https://example.com/recording.wav",
      "content_hash": "sha512-...",
      "sip_call_id": "a84b4c76e66710@pc33.example.com",
      "sip_from_tag": "1928301774",
      "sip_to_tag": "a6c85cf",
      "sip_cseq": "314159 INVITE"
    }
  ]
}
```

### Signaling attachments

Raw SIP messages, SDP, and STIR/SHAKEN data attach via `purpose:`:

| `purpose` value            | Content                                   |
| -------------------------- | ----------------------------------------- |
| `sip-invite`               | The initial INVITE message                |
| `sip-response`             | A SIP response (200 OK, 4xx, etc.)        |
| `sip-message-trace`        | A full message sequence trace             |
| `sip-sdp`                  | SDP offer/answer payload                  |
| `sip-headers`              | A relevant header subset                  |
| `stir-certificate`         | STIR certificate chain (PEM or x5c)       |
| `stir-verification-report` | Verification result from a STIR validator |
| `stir-passport-extended`   | Extended STIR PASSporT                    |

```json
{
  "attachments": [
    {
      "purpose": "sip-invite",
      "party": 0,
      "dialog": 0,
      "mediatype": "message/sip",
      "encoding": "none",
      "body": "INVITE sip:bob@example.com SIP/2.0\r\nVia: SIP/2.0/UDP ...\r\n..."
    },
    {
      "purpose": "stir-passport-extended",
      "party": 0,
      "dialog": 0,
      "mediatype": "application/passport+jwt",
      "encoding": "none",
      "body": "eyJhbGciOi..."
    }
  ]
}
```

Declare the extension at the top level:

```json
{
  "vcon": "0.4.0",
  "extensions": ["sip-signaling"]
}
```

## Working with SIPREC

The [`vcon-siprec-adapter`](https://github.com/vcon-dev/vcon-siprec-adapter) tool consumes SIPREC-formatted recordings and produces vCons that already include the SIP signaling extension data. If you're ingesting from a SIP recording infrastructure, that adapter is the right starting point — it handles the field mapping for you.

## See also

* [vCon Adapter Development Guide](/vcon-adapters/vcon-adapter-development-guide) — patterns for building telephony adapters.
* [Authenticating and Certifying Conversations](/use-cases-studies/authenticating-and-certifying-conversations) — the STIR/SHAKEN integration story.


# Conserver

The Conserver — a Redis-backed conversation processing engine that ingests vCons, runs them through configurable chains of links, and writes the results to any of fourteen storage backends.

The Conserver is the runtime that turns vCons into something useful. It pulls vCons off Redis ingress queues, runs them through a configurable pipeline of **links** (transcribe → analyze → tag → notify → record audit trail → …), and writes the finished result to one or more **storages** (Postgres, S3, MongoDB, Elasticsearch, Milvus, SCITT transparency services, the vCon MCP server, and more).

It's an [open-source project](https://github.com/vcon-dev/vcon-server) — Python 3.12, FastAPI for the API tier, Redis for queuing, Docker Compose for deployment. The current build ships **22 standard links** and **14 storage backends**, plus tracers that emit a verifiable audit trail of every chain execution.

## When to use the Conserver

* You have vCons arriving from one or more adapters (phone systems, SIPREC, email, chat, LLM exports) and need to do something with them at scale.
* You need a transcribe → analyze → store pipeline that runs reliably, scales horizontally, and handles failures via dead-letter queues.
* You want a single integration point for downstream systems (CRM, data warehouse, MCP server, blockchain audit log) so adapter teams don't each build their own.
* You need to track lifecycle events — creation, enhancement, deletion, consent revocation — on a [SCITT transparency ledger](/extensions/lifecycle).

## Documentation layout

**Get started:**

* [Conserver Introduction](/conserver/conserver-introduction) — overview and design rationale
* [Quick Start](/conserver/conserver-quick-start) — Docker Compose in fifteen minutes
* [Concepts](/conserver/concepts) — Link, Chain, Storage, Tracer

**Configure:**

* [Configuring the Conserver](/conserver/configuring-the-conserver) — every env var and every YAML section
* [Standard Links](/conserver/standard-links) — reference for all 22 shipped links
* [Storage](/conserver/storage) — reference for all 14 storage backends
* [Conserver Tracers](/conserver/conserver-tracers) — audit and compliance trail

**Build:**

* [Creating Custom Links](/conserver/creating-custom-links) — write your own processing step
* [API](/conserver/api) — REST endpoints for vCon CRUD, ingress / egress, configuration, DLQ
* [Integrating Your App](/conserver/integrating-your-app) — calling the API from your code

**Operate:**

* [Production Deployment](/conserver/production-deployment) — Docker Compose, scaling, secrets, observability
* [Inside the Conserver](/conserver/inside-the-conserver) — architecture and request flow
* [Day in the Life of a vCon](/conserver/day-in-the-life-of-a-vcon) — narrative walkthrough
* [Troubleshooting](/conserver/troubleshooting) — common issues and their fixes
* [Operational Benefits](/conserver/operational-benefits-of-conservers) — federation, multi-tenancy, governance

**Source and support:**

* [GitHub: vcon-dev/vcon-server](https://github.com/vcon-dev/vcon-server) — the canonical repository


# Conserver Introduction

A data platform for gathering, creating, storing and sharing vCons

The conserver is a data platform designed to extract conversations from business phone systems, transform them into actionable insights, and send that data into common business tools such as spreadsheets, Salesforce and no code toolsets. An open core product, the conserver enables data engineering teams to supply a reliable source of information for AI, ML and operational software in cloud, premise and hybrid contexts. The core for many of the business cases enabled by the conserver is the smart capture, redaction and lifecycle management of recorded customer conversations and customer journeys, recently accelerated by FTC and GDPR regulations and by increasing investments into AI and ML.

<figure><img src="/files/8MAKPj99enmF5Aq3zoGL" alt=""><figcaption></figcaption></figure>

From a system perspective, shown above, the Conserver attaches to information systems like Web Chat and call center queues, and extracts information from them after conversations are ended. This information is then cleaned and transformed into actionable data. For instance, a distributed call center might extract conversations from a group of sales agents, convert them into text, then filter those conversations looking for times when customers objected to a sale. These objections are then pushed into database tables and Google Sheets as a data self-service option for any business team. The conserver supports multiple data pipelines, each one extracting data from a number of systems, performing transformations such as translations, transcriptions and redactions, and then pushing the prepared data into applications to be used.

In contrast to other data platforms, the Conserver is dedicated to managing the particular complexities of real time conversational sources. For instance, the amount of bandwidth and storage required to manage an hour long audio recording is an order of magnitude larger than managing a typical business object like a PDF. However, even this is just a start. Video is a few orders of magnitude greater than that, and the data creation for service providers such as Zoom and Skype are magnitudes of order still greater. From a legal perspective, regulatory compliance for customer data protections are particular for recorded conversations, and require support for tracking data’s use by automations, and for tracking deletion from a “Right to be Forgotten” request.


# Conserver Quick Start

A quick start to getting the conserver up and running

## Ubuntu Install

Based on a digital ocean install, to keep it vanilla. Created a 4 GB Memory / 2 Intel vCPUs / 120 GB Disk / NYC3 - Ubuntu 23.04 x64 droplet, logged in.

```
snap install docker
git clone https://github.com/vcon-dev/vcon.git
cd vcon/
git submodule sync
git submodule update --init --recursive
cd vcon-server/
git checkout main
cd ..
cd vcon-admin/
git checkout main
cd ..
```

## Conserver Start

The conserver repo [can be downloaded directly](https://github.com/vcon-dev/vcon-server), but is also included in the vcon repo in the von-server directory.

```
cd vcon-server
```

Create an \~/vcon/.env file. See example .env below. \*Note that the default URL for REDIS assumes it is running in a docker container, thus the hostname "redis".

```
REDIS_URL=redis://redis

# Leave this blank to disable API security
# You set this before opening the port in your firewall
CONSERVER_API_TOKEN=

# to customize the config copy example_config.yml to config.yml
# modify the values in config.yml as needed
# and set CONSERVER_CONFIG_FILE to ./config.yml below
CONSERVER_CONFIG_FILE=config.yml
```

## Example vcon-server/config.yml

Most of the configuration is done through the config.yml file. Here's a very simple one. Inbound vCons in the ingress chain cause a slack webhook into a workflow, then it will be stored in mongo.

```
links:
  tag:
    module: links.tag
    ingress-lists: []
    egress-lists: []
    options:
      tags:
      - smart_trunk_ingress
  
storages:
  mongo:
    module: storage.mongo
    options:
      url: mongodb://root:example@mongo:27017/
      database: conserver
      collection: vcons
chains:
  demo_chain:
    ingress_lists:
    - ingress
    links:
    - tag
    storages:
    - mongo
    enabled: 1
```

### Standalone Operation

When running a conserver in "standalone mode" (using vcon-admin as a simple portal, which will also provide the basic versions of all of the apps and databases), it will automatically register a domain name and generate a valid SSL certificate using LetsEncrypt, assuming that the domain name has an A record pointing to your server.

```
export DNS_REGISTRATION_EMAIL=mulligan.mccarthy@strolid.com
export DNS_HOST=mulligan.strolid.net
```

### Start the Conserver

```
docker network create conserver
docker compose build
docker compose up
docker compose up --scale conserver=4 -d
```

## Troubleshooting and Checking

You can validate that the conserver is running on the command line using "docker ps". In the example below, we can see four instances running.

```
root@partner-demo:~/vcon/vcon-server# docker ps
CONTAINER ID   IMAGE                      COMMAND                  CREATED         STATUS                   PORTS                                                 NAMES
21bc6e3aacd7   vcon-server-conserver      "/app/docker/wait_fo…"   4 minutes ago   Up 4 minutes                                                                   vcon-server-conserver-4
2e3a0341043d   vcon-server-conserver      "/app/docker/wait_fo…"   4 minutes ago   Up 4 minutes                                                                   vcon-server-conserver-2
9c699287f035   vcon-server-conserver      "/app/docker/wait_fo…"   4 minutes ago   Up 4 minutes                                                                   vcon-server-conserver-3
ffe6f68941c8   vcon-server-conserver      "/app/docker/wait_fo…"   5 minutes ago   Up 5 minutes                                                                   vcon-server-conserver-1
8136e15912c5   vcon-server-api            "/app/docker/wait_fo…"   5 minutes ago   Up 5 minutes             0.0.0.0:8000->8000/tcp, :::8000->8000/tcp             vcon-server-api-1
e3388b5f23be   redis/redis-stack:latest   "/entrypoint.sh"         5 minutes ago   Up 5 minutes (healthy)   6379/tcp, 0.0.0.0:8001->8001/tcp, :::8001->8001/tcp   vcon-server-redis-1
root@partner-demo:~/vcon/vcon-server# 
```

You can see the operational logs using "docker compose logs -f". Here's a typical log:

```
vcon-server-redis-1      | 9:C 23 Aug 2024 17:27:20.581 # WARNING Memory overcommit must be enabled! Without it, a background save or replication may fail under low memory condition. Being disabled, it can also cause failures without low memory condition, see https://github.com/jemalloc/jemalloc/issues/1328. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.
vcon-server-redis-1      | 9:C 23 Aug 2024 17:27:20.582 * oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
vcon-server-redis-1      | 9:C 23 Aug 2024 17:27:20.582 * Redis version=7.4.0, bits=64, commit=00000000, modified=0, pid=9, just started
vcon-server-redis-1      | 9:C 23 Aug 2024 17:27:20.582 * Configuration loaded
vcon-server-redis-1      | 9:M 23 Aug 2024 17:27:20.582 * Increased maximum number of open files to 10032 (it was originally set to 1024).
vcon-server-redis-1      | 9:M 23 Aug 2024 17:27:20.583 * monotonic clock: POSIX clock_gettime
vcon-server-redis-1      | 9:M 23 Aug 2024 17:27:20.584 * Running mode=standalone, port=6379.
vcon-server-redis-1      | 9:M 23 Aug 2024 17:27:20.586 * Module 'RedisCompat' loaded from /opt/redis-stack/lib/rediscompat.so
vcon-server-redis-1      | 9:M 23 Aug 2024 17:27:20.614 * <search> Redis version found by RedisSearch : 7.4.0 - oss
vcon-server-redis-1      | 9:M 23 Aug 2024 17:27:20.616 * <search> RediSearch version 2.10.5 (Git=2.10-e2f28a9)
vcon-server-redis-1      | 9:M 23 Aug 2024 17:27:20.616 * <search> Low level api version 1 initialized successfully
vcon-server-redis-1      | 9:M 23 Aug 2024 17:27:20.617 * <search> gc: ON, prefix min length: 2, min word length to stem: 4, prefix max expansions: 200, query timeout (ms): 500, timeout policy: return, cursor read size: 1000, cursor max idle (ms): 300000, max doctable size: 1000000, max number of search results:  10000, 
vcon-server-redis-1      | 9:M 23 Aug 2024 17:27:20.620 * <search> Initialized thread pools!
vcon-server-redis-1      | 9:M 23 Aug 2024 17:27:20.621 * <search> Enabled role change notification
vcon-server-redis-1      | 9:M 23 Aug 2024 17:27:20.621 * Module 'search' loaded from /opt/redis-stack/lib/redisearch.so
vcon-server-redis-1      | 9:M 23 Aug 2024 17:27:20.630 * <timeseries> RedisTimeSeries version 11202, git_sha=5643fd4d6fcb1e9cf084fb2deb9285b08f4a6672
vcon-server-redis-1      | 9:M 23 Aug 2024 17:27:20.631 * <timeseries> Redis version found by RedisTimeSeries : 7.4.0 - oss
vcon-server-redis-1      | 9:M 23 Aug 2024 17:27:20.631 * <timeseries> loaded default CHUNK_SIZE_BYTES policy: 4096
vcon-server-redis-1      | 9:M 23 Aug 2024 17:27:20.631 * <timeseries> loaded server DUPLICATE_POLICY: block
vcon-server-redis-1      | 9:M 23 Aug 2024 17:27:20.631 * <timeseries> loaded default IGNORE_MAX_TIME_DIFF: 0
vcon-server-redis-1      | 9:M 23 Aug 2024 17:27:20.631 * <timeseries> loaded default IGNORE_MAX_VAL_DIFF: 0.000000
vcon-server-redis-1      | 9:M 23 Aug 2024 17:27:20.631 * <timeseries> Setting default series ENCODING to: compressed
vcon-server-redis-1      | 9:M 23 Aug 2024 17:27:20.631 * <timeseries> Detected redis oss
vcon-server-redis-1      | 9:M 23 Aug 2024 17:27:20.631 * Module 'timeseries' loaded from /opt/redis-stack/lib/redistimeseries.so
vcon-server-redis-1      | 9:M 23 Aug 2024 17:27:20.639 * <ReJSON> Created new data type 'ReJSON-RL'
vcon-server-redis-1      | 9:M 23 Aug 2024 17:27:20.639 * <ReJSON> version: 20803 git sha: unknown branch: unknown
vcon-server-redis-1      | 9:M 23 Aug 2024 17:27:20.639 * <ReJSON> Exported RedisJSON_V1 API
vcon-server-redis-1      | 9:M 23 Aug 2024 17:27:20.639 * <ReJSON> Exported RedisJSON_V2 API
vcon-server-redis-1      | 9:M 23 Aug 2024 17:27:20.639 * <ReJSON> Exported RedisJSON_V3 API
vcon-server-redis-1      | 9:M 23 Aug 2024 17:27:20.639 * <ReJSON> Exported RedisJSON_V4 API
vcon-server-redis-1      | 9:M 23 Aug 2024 17:27:20.639 * <ReJSON> Exported RedisJSON_V5 API
vcon-server-redis-1      | 9:M 23 Aug 2024 17:27:20.639 * <ReJSON> Enabled diskless replication
vcon-server-redis-1      | 9:M 23 Aug 2024 17:27:20.639 * Module 'ReJSON' loaded from /opt/redis-stack/lib/rejson.so
vcon-server-redis-1      | 9:M 23 Aug 2024 17:27:20.639 * <search> Acquired RedisJSON_V5 API
vcon-server-redis-1      | 9:M 23 Aug 2024 17:27:20.641 * <bf> RedisBloom version 2.8.2 (Git=unknown)
vcon-server-redis-1      | 9:M 23 Aug 2024 17:27:20.641 * Module 'bf' loaded from /opt/redis-stack/lib/redisbloom.so
vcon-server-redis-1      | 9:M 23 Aug 2024 17:27:20.648 * <redisgears_2> Created new data type 'GearsType'
vcon-server-redis-1      | 9:M 23 Aug 2024 17:27:20.650 * <redisgears_2> Detected redis oss
vcon-server-redis-1      | 9:M 23 Aug 2024 17:27:20.652 # <redisgears_2> could not initialize RedisAI_InitError
vcon-server-redis-1      | 
vcon-server-redis-1      | 
vcon-server-redis-1      | 9:M 23 Aug 2024 17:27:20.652 * <redisgears_2> Failed loading RedisAI API.
vcon-server-redis-1      | 9:M 23 Aug 2024 17:27:20.652 * <redisgears_2> RedisGears v2.0.20, sha='9b737886bf825fe29ddc2f8da81f73cbe0b4e858', build_type='release', built_for='Linux-ubuntu22.04.x86_64', redis_version:'7.4.0', enterprise:'false'.
vcon-server-redis-1      | 9:M 23 Aug 2024 17:27:20.657 * <redisgears_2> Registered backend: js.
vcon-server-redis-1      | 9:M 23 Aug 2024 17:27:20.657 * Module 'redisgears_2' loaded from /opt/redis-stack/lib/redisgears.so
vcon-server-redis-1      | 9:M 23 Aug 2024 17:27:20.657 * Server initialized
vcon-server-redis-1      | 9:M 23 Aug 2024 17:27:20.657 * Ready to accept connections tcp
vcon-server-conserver-2  | Redis is ready!
vcon-server-conserver-2  | Redis is ready. Starting the dependent service...
vcon-server-conserver-2  | {"asctime": "2024-08-23 17:28:24,696", "levelname": "INFO", "name": "__main__", "message": "Starting main loop", "taskName": null}
vcon-server-conserver-4  | Redis is ready!
vcon-server-conserver-4  | Redis is ready. Starting the dependent service...
vcon-server-conserver-4  | {"asctime": "2024-08-23 17:28:24,545", "levelname": "INFO", "name": "__main__", "message": "Starting main loop", "taskName": null}
vcon-server-conserver-3  | Redis is ready!
vcon-server-conserver-3  | Redis is ready. Starting the dependent service...
vcon-server-conserver-3  | {"asctime": "2024-08-23 17:28:25,041", "levelname": "INFO", "name": "__main__", "message": "Starting main loop", "taskName": null}
vcon-server-api-1        | Redis is ready!
vcon-server-api-1        | Redis is ready. Starting the dependent service...
vcon-server-api-1        | Skipping virtualenv creation, as specified in config file.
vcon-server-api-1        | {"asctime": "2024-08-23 17:27:24,198", "levelname": "INFO", "name": "server.api", "message": "Api starting up", "taskName": "Task-1"}
vcon-server-api-1        | {"asctime": "2024-08-23 17:27:24,226", "levelname": "INFO", "name": "uvicorn.error", "message": "Started server process [1]", "taskName": "Task-1", "color_message": "Started server process [\u001b[36m%d\u001b[0m]"}
vcon-server-api-1        | {"asctime": "2024-08-23 17:27:24,226", "levelname": "INFO", "name": "uvicorn.error", "message": "Waiting for application startup.", "taskName": "Task-1"}
vcon-server-api-1        | {"asctime": "2024-08-23 17:27:24,227", "levelname": "INFO", "name": "uvicorn.error", "message": "Application startup complete.", "taskName": "Task-1"}
vcon-server-api-1        | {"asctime": "2024-08-23 17:27:24,227", "levelname": "INFO", "name": "uvicorn.error", "message": "Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)", "taskName": "Task-1", "color_message": "Uvicorn running on \u001b[1m%s://%s:%d\u001b[0m (Press CTRL+C to quit)"}
vcon-server-conserver-1  | Redis is ready!
vcon-server-conserver-1  | Redis is ready. Starting the dependent service...
vcon-server-conserver-1  | {"asctime": "2024-08-23 17:27:22,240", "levelname": "INFO", "name": "__main__", "message": "Starting main loop", "taskName": null}

```

The [vCon admin program](https://github.com/vcon-dev/vcon-admin) is a nice tool for managing the conserver.


# Configuring the Conserver

A Complete Guide

The Conserver is configured through two mechanisms:

1. **Environment Variables** - Server-level settings (Redis, API keys, paths)
2. **YAML Configuration File** - Processing configuration (chains, links, storage)

## Environment Variables

Set these in your `.env` file or system environment.

### Core Settings

| Variable                | Description                         | Default                 |
| ----------------------- | ----------------------------------- | ----------------------- |
| `REDIS_URL`             | Redis connection URL                | `redis://localhost`     |
| `CONSERVER_CONFIG_FILE` | Path to YAML config file            | `./example_config.yml`  |
| `HOSTNAME`              | Server hostname                     | `http://localhost:8000` |
| `ENV`                   | Environment name (dev/staging/prod) | `dev`                   |
| `LOG_LEVEL`             | Logging level                       | `DEBUG`                 |

### API Settings

| Variable                   | Description                                 | Default                 |
| -------------------------- | ------------------------------------------- | ----------------------- |
| `CONSERVER_API_TOKEN`      | Main API authentication token               | (none)                  |
| `CONSERVER_API_TOKEN_FILE` | Path to file with API tokens (one per line) | (none)                  |
| `CONSERVER_HEADER_NAME`    | HTTP header name for API token              | `x-conserver-api-token` |
| `API_ROOT_PATH`            | API URL prefix                              | `/api`                  |

### Redis/Caching Settings

| Variable                  | Description                                                                   | Default            |
| ------------------------- | ----------------------------------------------------------------------------- | ------------------ |
| `VCON_REDIS_EXPIRY`       | Cache TTL for vCons fetched from storage back into Redis (seconds)            | `3600` (1 hour)    |
| `VCON_INDEX_EXPIRY`       | Search index TTL (seconds)                                                    | `86400` (24 hours) |
| `VCON_CONTEXT_EXPIRY`     | Ingress context (OTEL trace context) TTL (seconds)                            | `86400` (24 hours) |
| `VCON_DLQ_EXPIRY`         | Dead-letter queue TTL (seconds). Set to `0` to keep DLQ entries indefinitely. | `604800` (7 days)  |
| `VCON_SORTED_SET_NAME`    | Name of Redis sorted set for vCons                                            | `vcons`            |
| `VCON_SORTED_FORCE_RESET` | Reset sorted set on startup                                                   | `true`             |
| `TICK_INTERVAL`           | Processing loop interval (ms)                                                 | `5000`             |

### Worker & Parallelism

These control how many worker processes the conserver runs and how storage writes are dispatched.

| Variable                     | Description                                                                                                                                                                                                             | Default |
| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
| `CONSERVER_WORKERS`          | Number of worker processes to fork. Each worker independently pulls from configured ingress queues. Scale this with CPU cores and chain CPU-intensity.                                                                  | `1`     |
| `CONSERVER_PARALLEL_STORAGE` | If `true`, storage writes for a single vCon run concurrently across the configured storages (ThreadPoolExecutor). If `false`, storages run serially.                                                                    | `true`  |
| `CONSERVER_START_METHOD`     | Multiprocessing start method: `fork`, `spawn`, or `forkserver`. Leave unset to use the platform default (typically `fork` on Linux, `spawn` on macOS). Use `spawn` if you hit fork-safety issues with native libraries. | unset   |

### External Service API Keys

| Variable         | Description                     |
| ---------------- | ------------------------------- |
| `OPENAI_API_KEY` | OpenAI API key                  |
| `DEEPGRAM_KEY`   | Deepgram speech-to-text API key |

### Example .env File

```bash
# Core
REDIS_URL=redis://localhost:6379
CONSERVER_CONFIG_FILE=./config.yml
ENV=production
LOG_LEVEL=INFO

# API
CONSERVER_API_TOKEN=your-secret-token
API_ROOT_PATH=/api

# Caching
VCON_REDIS_EXPIRY=3600
VCON_INDEX_EXPIRY=86400

# External APIs
OPENAI_API_KEY=sk-...
DEEPGRAM_KEY=...
```

***

## YAML Configuration File

The YAML configuration file defines processing chains, links, storage backends, and authentication. The Conserver looks for the path specified in `CONSERVER_CONFIG_FILE` (default: `./example_config.yml`).

### Configuration File Structure

```yaml
# External partner authentication
ingress_auth:
  ingress_list_name: "api-key" or ["key1", "key2"]

# Dynamic module imports
imports:
  import_name:
    module: module_name
    pip_name: package-name

# Processing link definitions
links:
  link_name:
    module: links.module_name
    options: {}

# Storage backend definitions
storages:
  storage_name:
    module: storage.backend_name
    options: {}

# Tracer definitions
tracers:
  tracer_name:
    module: tracers.module_name
    options: {}

# Processing chain definitions
chains:
  chain_name:
    links: [link1, link2]
    storages: [storage1]
    ingress_lists: [queue1]
    egress_lists: [queue2]
    enabled: 1
    timeout: 300

# Follower configuration (federation)
followers:
  follower_name:
    url: https://upstream-server
    egress_list: source_queue
    follower_ingress_list: target_queue
```

***

## Section Reference

### ingress\_auth

Configures API keys for external partner access to the `/vcon/external-ingress` endpoint.

```yaml
ingress_auth:
  # Single key per ingress list
  customer_data: "customer-api-key"

  # Multiple keys per ingress list
  partner_ingress:
    - "partner-key-1"
    - "partner-key-2"
    - "partner-key-3"
```

Each key grants access only to its designated ingress list. Partners cannot access other API endpoints.

***

### imports

Dynamically imports Python packages at runtime. Useful for:

* Installing missing dependencies automatically
* Using external link/storage packages
* Managing version requirements

```yaml
imports:
  # PyPI package
  custom_analysis:
    module: my_analysis_module
    pip_name: my-analysis-package>=1.0.0

  # GitHub repository
  github_link:
    module: github_link
    pip_name: git+https://github.com/org/repo.git@v2.0.0

  # GitHub with branch
  dev_link:
    module: dev_link
    pip_name: git+https://github.com/org/repo.git@main

  # Version ranges
  constrained:
    module: constrained_module
    pip_name: package>=1.0.0,<2.0.0
```

The Conserver will automatically install missing packages when first referenced.

***

### links

Links are the processing units of the conserver. Each link is a module that performs a specific operation on a vCon. See [Standard Links](/conserver/standard-links) for built-in options.

```yaml
links:
  # Built-in link with options
  deepgram:
    module: links.deepgram_link
    options:
      DEEPGRAM_KEY: your_key_here
      minimum_duration: 30
      api:
        model: "nova-2"
        smart_format: true
        detect_language: true

  # External link (auto-installed via imports)
  custom_analyzer:
    module: custom_analysis
    pip_name: my-analysis-package
    options:
      model: "gpt-4"

  # Multiple instances of same link with different configs
  summary_brief:
    module: links.analyze
    options:
      OPENAI_API_KEY: your_key_here
      prompt: "Summarize in one sentence"
      analysis_type: "brief_summary"

  summary_detailed:
    module: links.analyze
    options:
      OPENAI_API_KEY: your_key_here
      prompt: "Provide a detailed analysis"
      analysis_type: "detailed_summary"
```

Each link configuration needs:

* A unique name (e.g., 'deepgram', 'analyze')
* The module path that implements the link functionality
* An options dictionary containing the link's specific configuration

***

### storages

Storages define where vCons are saved after processing. See [Storage](/conserver/storage) for all backends.

```yaml
storages:
  postgres:
    module: storage.postgres
    options:
      user: postgres
      password: your_password
      host: your_host
      port: 5432
      database: postgres

  s3:
    module: storage.s3
    options:
      aws_access_key_id: your_key_id
      aws_secret_access_key: your_secret
      aws_bucket: your_bucket

  milvus:
    module: storage.milvus
    options:
      host: localhost
      port: "19530"
      api_key: sk-...
      embedding_model: text-embedding-3-small
```

Each storage needs:

* A unique name
* The storage module implementation
* Connection and authentication options specific to the storage type

***

### tracers

Defines tracer modules for auditing and compliance tracking.

```yaml
tracers:
  jlinc:
    module: tracers.jlinc
    options:
      data_store_api_url: http://jlinc-server:9090
      data_store_api_key: "key"
      archive_api_url: http://jlinc-server:9090
      archive_api_key: "key"
      system_prefix: "VCONProd"
      hash_event_data: true
      dlq_vcon_on_error: true
```

***

### chains

Chains are where you define your processing workflows. They connect links together and specify where the results should be stored:

```yaml
chains:
  transcription_chain:
    # Links to execute in order
    links:
      - deepgram
      - analyze
      - webhook_store

    # Input Redis lists where new vCons arrive
    ingress_lists:
      - transcription_input

    # Storage backends for processed vCons
    storages:
      - postgres
      - s3

    # Output Redis lists for downstream processing
    egress_lists:
      - transcription_output

    # Enable/disable this chain
    enabled: 1

    # Processing timeout per vCon (seconds)
    timeout: 300
```

A chain configuration includes:

* The links to execute, in order
* Input lists (ingress\_lists) where new vCons arrive
* Storage locations for the processed vCons
* Output lists (egress\_lists) for downstream processing
* An enabled flag and optional timeout

**Chain Processing Flow:**

1. vCon UUID arrives in an ingress list
2. Links execute sequentially (any can stop processing by returning `None`)
3. vCon is stored in all configured storage backends
4. UUID is added to egress lists
5. If processing fails, UUID moves to DLQ (`{ingress_list}:dlq`)

***

### followers

Followers allow one conserver to monitor and process vCons from another conserver for federated deployments:

```yaml
followers:
  remote_conserver:
    # Upstream server URL
    url: "https://remote-conserver.example.com"

    # Authentication token for upstream
    auth_token: "your_auth_token"

    # Remote egress list to pull from
    egress_list: "remote_output"

    # Local ingress list to add vCons to
    follower_ingress_list: "local_input"

    # Polling interval in seconds
    pulling_interval: 60

    # Number of vCons to fetch per request
    fetch_vcon_limit: 10
```

Each follower needs:

* The URL of the remote conserver
* Authentication credentials
* The remote list to monitor (egress\_list)
* The local list to populate (follower\_ingress\_list)
* Polling configuration (interval and batch size)

***

## Complete Example

```yaml
# External partner authentication
ingress_auth:
  partner_data:
    - "partner-key-abc"
    - "partner-key-xyz"
  internal_systems: "internal-key-123"

# Dynamic imports
imports:
  sentiment_analyzer:
    module: sentiment
    pip_name: vcon-sentiment>=1.0.0

# Links configuration
links:
  transcribe:
    module: links.deepgram_link
    options:
      DEEPGRAM_KEY: ${DEEPGRAM_KEY}
      minimum_duration: 30
      api:
        model: "nova-2"
        smart_format: true

  summarize:
    module: links.analyze
    options:
      OPENAI_API_KEY: ${OPENAI_API_KEY}
      prompt: "Summarize this conversation in 3 bullet points."
      analysis_type: "summary"
      model: "gpt-4-turbo"

  detect_complaints:
    module: links.check_and_tag
    options:
      OPENAI_API_KEY: ${OPENAI_API_KEY}
      tag_name: "complaint"
      tag_value: "detected"
      evaluation_question: "Does this contain a customer complaint?"

# Storage backends
storages:
  postgres:
    module: storage.postgres
    options:
      database: "vcons"
      user: "postgres"
      password: ${POSTGRES_PASSWORD}
      host: "postgres"
      port: 5432

  s3:
    module: storage.s3
    options:
      aws_access_key_id: ${AWS_ACCESS_KEY_ID}
      aws_secret_access_key: ${AWS_SECRET_ACCESS_KEY}
      aws_bucket: "vcon-archive"

# Processing chains
chains:
  main:
    links:
      - transcribe
      - summarize
      - detect_complaints
    storages:
      - postgres
      - s3
    ingress_lists:
      - incoming_calls
      - partner_data
    egress_lists:
      - processed
    enabled: 1
    timeout: 600
```

***

## Environment Variable Substitution

The configuration supports environment variable substitution using `${VAR_NAME}` syntax:

```yaml
links:
  analyze:
    module: links.analyze
    options:
      OPENAI_API_KEY: ${OPENAI_API_KEY}
```

This allows sensitive values to be kept in environment variables rather than the config file.

***

## Configuration Best Practices

1. **Use meaningful names** for your chains, links, and storage configurations to make the system easier to understand and maintain.
2. **Organize links logically** - arrange links in order where each step builds on the previous ones.
3. **Use multiple storage backends** when needed - for example, storing in both S3 for long-term storage and Postgres for quick querying.
4. **Configure appropriate timeouts** for your chains based on the expected processing time of your links.
5. **Use environment variables** for sensitive values like API keys and passwords.
6. **Use the follower configuration** when you need to process vCons across multiple conserver instances.

***

## Hot Reloading

The configuration file is loaded at startup and can be updated via the API endpoint `/config`. Changes take effect immediately for new vCon processing:

```bash
curl -X POST "http://localhost:8000/api/config" \
  -H "x-conserver-api-token: $TOKEN" \
  -H "Content-Type: application/json" \
  -d @new_config.json
```

Remember that the conserver uses Redis as its working storage, so all the lists referenced in ingress\_lists and egress\_lists are Redis lists.


# Concepts

The Conserver has five primitives. Everything you configure in `config.yml` is one of these.

## Link

A link is the basic unit of processing in the Conserver. A link takes a single vCon and does one thing to it — transcribe, summarize, tag, redact, route, notify. The conserver ships **22 standard links** (see [Standard Links](/conserver/standard-links)) and you can write [custom links](/conserver/creating-custom-links) just by implementing a `run(vcon_uuid, link_name, opts) -> str | None` function.

Examples:

* **Transcription** (`deepgram_link`, `openai_transcribe`, `wtf_transcribe`): convert audio to text.
* **Analysis** (`analyze`, `analyze_vcon`, `check_and_tag`): apply an LLM to extract summaries, sentiment, labels.
* **Routing & filtering** (`sampler`, `jq_link`, `tag_router`): decide which vCons proceed.
* **Integration** (`webhook`, `post_analysis_to_slack`): notify external systems.
* **Audit** (`scitt`, `datatrails`): record proof on a transparency ledger.

Links are described by Redis keys with a `link:` prefix, loaded on startup from `config.yml`. A link can be a member of multiple chains.

## Chain

A chain is a sequence of links applied to a vCon. The conserver iterates over each configured chain in the main event loop, popping vCon UUIDs from the chain's **ingress lists** (Redis lists), running them through every link in order, and either writing the result to the chain's **storages**, pushing the UUID onto **egress lists** for downstream consumers, or both.

If any link in a chain returns `None`, processing stops for that vCon — that's how `sampler`, `jq_link`, and `tag_router` (with `forward_original: false`) filter vCons mid-chain.

Chains are described by Redis keys with a `chain:` prefix and configured in the `chains:` section of `config.yml`. There is no practical limit to the number of chains or links per chain.

## Storage

A storage is the durable destination for processed vCons. After a chain's last link runs, every storage configured for that chain receives the vCon (in parallel by default — controlled by `CONSERVER_PARALLEL_STORAGE`).

The conserver ships **14 storage backends** (see [Storage](/conserver/storage)):

* **Document stores:** `mongo`, `redis_storage`, `chatgpt_files`, `dataverse`
* **Relational:** `postgres`
* **Object stores / file systems:** `s3`, `file`, `sftp`
* **Search:** `elasticsearch`, `milvus`
* **Transparency & audit:** `scitt`, `spaceandtime`
* **Pipelines / proxies:** `vcon_mcp`, `webhook`

Different chains can target different storages — that's how you route, say, transcribed customer-service calls to Postgres while sending the redacted public-facing version to S3.

## Tracer

A tracer is the audit-trail counterpart to a link. While links *change* the vCon, tracers *record* what happened to it. The conserver invokes every configured tracer before the first link runs, after each link runs, and at chain completion — so the trail captures who processed what, when, and with what configuration.

Tracers are typically backed by a verifiable ledger (JLINC, DataTrails, or a [SCITT transparency service](/extensions/lifecycle)). They're optional but recommended for any deployment that has to satisfy GDPR / CCPA right-to-know or right-to-erasure requirements. See [Conserver Tracers](/conserver/conserver-tracers) for the full lifecycle and per-tracer reference.

## Follower

A follower is a *second* conserver that polls another conserver via the REST API and mirrors its vCons. Use followers for federation, geo-distribution, or low-latency read replicas. See the `followers:` section of [Configuring the Conserver](/conserver/configuring-the-conserver) for the configuration shape.


# Operational Benefits of Conservers

What Conservers actually buy you in production, grounded in deployments that exist and public commentary on why they matter.

A Conserver is not a recording platform. It is infrastructure for conversational artifacts: ingesting vCons, enriching them through a chain of links, governing them, signing them, and storing or forwarding them. The operational case for running one rests on a small set of properties that are now visible in production. This page lays them out and points at the public material behind each claim.

The framing is not original to Conserver. Jeff Pulver describes vCon as the equivalent of PDF for conversations ([Telecom Reseller, Jun 2025](https://telecomreseller.com/2025/06/02/the-vcon-revolution-jeff-pulver-on-the-file-format-transforming-business-conversations-podcast/)) and as the missing structured-data layer for enterprise AI ([Telecom Reseller, Mar 2026](https://telecomreseller.com/2026/03/12/vcon-foundation-jeff-pulver-on-structuring-conversations-for-the-ai-era-podcast/)). CRM analyst Thomas Wieberneit makes the same case from the buy-side in [The vCon Reality Check](https://aheadcrm.medium.com/the-vcon-reality-check-moving-beyond-generative-hype-to-actual-conversational-architecture-41197017fb9b): durable conversational architecture is the part of the AI story that does not show up in demos but determines whether the demos hold up in production. Conservers are how that architecture gets operated.

## What is already in production

The volumes below are reported in the [vCon Progress Report (TADSummit, Aug 2025)](https://blog.tadsummit.com/2025/08/20/vcon-progress-report/) and on [Strolid's vCon Conserver page](https://strolid.ai/vcon-conservers/).

* **Roughly a quarter million vCons per month** at the BPO that incubated the technology, with volume roughly doubling year over year.
* **Millions of vCons per day** at a large financial institution on a path to a million per hour. Same deployment is also the first production instance of real-time vCons.
* **United Way 211 routing prototype**, where the Conserver chain listens for context (food-banking call versus crisis disclosure) and changes routing in flight.
* **Around thirty to forty companies actively building** on vCons, with telecom and contact-center vendors leaning in first. The [VCONIC TADHack 2026](https://blog.tadhack.com/2025/12/19/vconic-tadhack/) hackathon produced sixteen submissions in a weekend across the ecosystem.

The shape of the operational benefit below is what these deployments have in common, not a forecast.

## Deployment flexibility

Conservers run as a chain of stateless Python processes against Redis-backed queues and configurable storage backends, deployable in cloud, on-prem, hybrid, or edge configurations. The architecture is documented in [Conserver Introduction](/conserver/conserver-introduction) and the [storage backends reference](/conserver/storage). Strolid's public description of the same pattern is on [strolid.ai/vcon-conservers](https://strolid.ai/vcon-conservers/).

This is what enables the same Conserver code to run inside a financial institution's perimeter and inside a BPO's multi-tenant cloud without forking. Wieberneit calls this out specifically in [The vCon Reality Check](https://aheadcrm.medium.com/the-vcon-reality-check-moving-beyond-generative-hype-to-actual-conversational-architecture-41197017fb9b) as the property enterprises ask about first.

## Choice of AI per step, not per platform

Because Conservers process vCons through a chain of independent links, the transcription model, the redaction step, the summarizer, and the embedding generator are independently configurable. A single chain can mix cloud and local inference on a per-link basis. The catalog of available links and storages is in [Standard Links](/conserver/standard-links) and the [Adapter docs](/vcon-adapters).

Pulver's [Structuring Conversations for the AI Era](https://telecomreseller.com/2026/03/12/vcon-foundation-jeff-pulver-on-structuring-conversations-for-the-ai-era-podcast/) episode is the elevator-pitch version of why this matters: AI cost, accuracy, and regulatory posture differ by step, so the unit of choice has to be the step, not the platform.

## Provenance and an immutable audit trail

Conservers can register each finalized vCon on a SCITT transparency service as a COSE-signed statement, producing a tamper-evident record of what existed at what point and who signed it. The integration is documented in [SCITT storage](/conserver/storage) and in [Standard Links — SCITT](/conserver/standard-links). The public case for SCITT alongside vCon is Steve Lasker's [TADSummit Innovators Ep 85](https://blog.tadsummit.com/2024/08/20/steve-lasker/) and his TADSummit 2024 keynote alongside Thomas Howe at [The Rise and Rise of vCon](https://blog.tadsummit.com/2024/10/29/the-rise-and-rise-of-vcon/).

This is the property that makes Right-to-Know and Right-to-Erasure requests answerable years later, rather than reconstructible from logs. Wieberneit treats it as the defensible-AI part of the architecture in [The vCon Reality Check](https://aheadcrm.medium.com/the-vcon-reality-check-moving-beyond-generative-hype-to-actual-conversational-architecture-41197017fb9b).

## Multi-tenant and federated operation

The same Conserver image runs as the engine for a UCaaS or BPO platform offering vCon services to many customers, and as a single-tenant deployment inside one enterprise. Federation across multiple Conservers, including across security or jurisdictional boundaries, is achieved by passing the vCon as an artifact between them rather than by sharing state. The Strolid / Frontline alliance noted in the [vCon Progress Report](https://blog.tadsummit.com/2025/08/20/vcon-progress-report/) is one public example.

J Arnold & Associates frame this in [Next Stop, Fall '25 vCon](https://www.jarnoldassociates.com/blog/search/2025/12/1/next-stop-fall-25-vcon) as the part of the UC/CX trend that lets the conversation move between vendors instead of being locked into the platform that captured it.

## Storage tiers and resilience

Storage is configurable per deployment, with Redis as the hot queue, PostgreSQL for structured query and reporting, and S3-class object storage for long-term retention. Backends are documented in [storage backends](/conserver/storage). Failed processing is captured via dead-letter queues and retried with backoff; vCons can be replicated across multiple stores in the same chain run. None of this is novel; what is novel is that the same vCon artifact survives all of these without re-serialization.

## Beyond customer experience

The case is not specific to contact centers. Matthew Smith's [vCon + UNS for Manufacturing](https://blog.tadsummit.com/2025/12/17/matthew-smith-vcon-and-uns/) talk applies the same Conserver pattern to operator-machine conversations in process industries. [Telecom Reseller — From Voice to Data](https://telecomreseller.com/2026/02/17/vcons-changing-business-communication/) makes the broader adoption argument: once conversations have a portable format, the operational substrate shifts.

## What this means for someone deciding whether to run one

Three operational properties tend to decide it.

1. **The conversation survives the platform.** Move vendors, change AI providers, restructure the team. The vCon remains the artifact and the Conserver chain is reconfigured around it. The financial-institution and BPO deployments above are the existence proof.
2. **AI choices stay reversible.** Because the chain is composed of links, swapping a transcription engine or a redaction strategy is a configuration change, not a re-platforming.
3. **Compliance is a property of the artifact, not the system.** Lawful basis, lifecycle, signatures, and SCITT receipts travel with the vCon. A regulator asking "what did you do with this conversation" gets answered from the file, not reconstructed from logs.

## Read more

* [Conserver Introduction](/conserver/conserver-introduction) — what a Conserver actually is and how a chain runs
* [Day in the Life of a vCon](/conserver/day-in-the-life-of-a-vcon) — the same story end to end
* [Standard Links](/conserver/standard-links) and [Storage Backends](/conserver/storage) — the catalog behind the claims above
* [Articles & Press](/talks-articles-press/articles-and-press) and [Conference Keynotes](/talks-articles-press/conference-keynotes) — the third-party material cited on this page, plus the rest of the public record


# Inside the Conserver

The Machinery of the Conserver

<figure><img src="/files/8MAKPj99enmF5Aq3zoGL" alt=""><figcaption><p>The System view of the Conserver</p></figcaption></figure>

The Conserver processes vCons, storing them locally, and projecting them into the third party information services. The building blocks of the Conserver are links, which have an interface to accept a single vCon, and can then forward that vCon, or create new ones, to other links for further processing. Links are formed into chains, designed to apply a series of analysis and transformation to the vCons. Chains are executed by the conserver periodically on a timer, or on request from a third party system.

## Links: The Fundamental Building Block

The heart of the conserver functionality is the "link". A link is a Python module that takes a single vCon and processes it. Chains are ultimately created by combining links in serial. All links have the same interface. Using links has multiple advantages:

* **Configurable**: Uses a flexible options system for customization.
* **Retry Mechanism**: Implements exponential backoff for API call retries.
* **Caching**: Avoids redundant analysis by checking existing data.
* **Metrics**: Tracks performance and error metrics.
* **Modular**: Designed to be part of a larger system, likely for processing voice conversations.

As an example, let's look at [the analyze link.](https://github.com/vcon-dev/vcon-server/blob/86f26ecb1ec01877586c712921c564f6241b2d6c/server/links/analyze/__init__.py) This link takes a vCon, applies a prompt to it, then adds an analysis to the vCon with the result.

<figure><img src="/files/myWLh6fDjXyjGrMffZI9" alt=""><figcaption><p>A Conserver Link</p></figcaption></figure>

### Main Function: run

The `run` function is the entry point for links. [For the analysis link](https://github.com/vcon-dev/vcon-server/blob/86f26ecb1ec01877586c712921c564f6241b2d6c/server/links/analyze/__init__.py#L62), it performs the following steps:

1. Merges provided options in the "config.yml" file with default options.
2. Retrieves the vCon (voice conversation) object from Redis.
3. Applies inclusion filters and sampling.
4. Iterates through dialog entries in the vCon:
   * Retrieves the source text for analysis.
   * Checks if analysis already exists.
   * Generates new analysis using OpenAI if needed.
   * Adds the generated analysis to the vCon object.
5. Stores the updated vCon back in Redis.

### Default Options

A `default_options` dictionary defines the options for the link, and are overridden by the configuration file. For instance, the analysis link is defined with the following options:

* Prompt for summarization
* The value to set as the analysis type when added to the vCon (default: "summary")
* GPT model (default: "gpt-3.5-turbo-16k")
* Sampling rate and temperature
* Source configuration for transcript analysis, for instance "transcript" or "summary"

### Error Handling and Metrics

The module includes error handling for API calls and retries. It also tracks metrics such as analysis time and failures using custom metric functions.

### Return Values

Links can return one of two kinds of values. Links can return a vCon UUID, or None. Typically, it would be the vCon UUID that was passed in. However, if the link created a new vCon, as would be required for creating a new, redacted vCon, the new UUID would be returned by the link. To stop chain processing, a link could return None. This is useful for links that filter vCons out, only allowing certain ones down the chain, and stopping the processing of links downstream of the chain.

## Chains: Links for Workflow

The fundamental implementation of workflow is created by a series of links. These chains take vCon uuids from REDIS lists, runs the chain of links on the vCon, stores it, then places the uuids in egress REDIS links.

<figure><img src="/files/ONh7lz148H2YyKV5vVbv" alt=""><figcaption><p>A Chain</p></figcaption></figure>

### Chain Processing, Link by Link

The [main loop of the conserver](https://github.com/vcon-dev/vcon-server/blob/86f26ecb1ec01877586c712921c564f6241b2d6c/server/main.py#L139) processes vCons:

* Loads configuration and sets up the ingress chain map.
* Enters a loop that continuously checks for new items in the ingress lists using Redis.
* When an item (vCon ID) is found, it creates a VconChainRequest and processes it.
* Handles exceptions by moving problematic vCons to a Dead Letter Queue.

Step by step:

1. Processing starts when vCon UUIDs are placed into a ingress list. Chains may have several ingress lists, and have to have at least one to kick off processing. Lists are implemented as REDIS lists, and processing is controlled at the thread layer by blocking until the a new element is placed on the list. UUIDs can be added to the ingress list by other chains, allowing them to be placed in series, from links that can request processing, or from the API. A typical pattern is to create the vCon using the API, then inserting the UUID into the desired ingress list.
2. For each vCon taken from the ingress list, it is processed by each link in the chain. This `_process_link` function handles the execution of a single link in the processing chain for a vCon. Here's a summary of its functionality:

   This enables flexible and dynamic execution of different processing steps (links) in the vCon processing chain, with built-in logging and timing measurements.

   1. It logs the start of processing for the specific link and vCon.
   2. It retrieves the link configuration from the global `config`.
   3. It dynamically imports the module specified for this link if it hasn't been imported before, caching it for future use.
   4. It retrieves any options specified for the link.
   5. It logs the execution of the link's module.
   6. It measures the execution time of the module's `run` method, which is called with the vCon ID, link name, and options.
   7. After execution, it logs the completion of the link processing, including the time taken.
   8. Finally, it returns the result from the module's `run` method, which determines whether the chain should continue processing or stop.
3. After the links have been processed, assuming that none of the links returned None, the vCon UUID is pushed into the chain's egress lists. Finally, the vCon is then stored in the storages (S3, Mongo, File, etc.) specified for that link.
4. In case of an error in any of these links, the vCon UUID will be pushed into the dead letter queue of the original ingress list.

## Tech Stack

The Conserver is built off of two core platforms: a python API framework FASTAPI, and a REDIS real time database. The conserver itself is written in Python, and uses the standard vCon Python library to create and modify vCons.

REDIS is responsible for storing the conversations, while FAST API coordinates the application software that manages them. Each conversation is stored as a REDIS JSON object in the standard vCon format. In practice, each vCon is stored in REDIS by the UUID of the vCon, making them easy to discover and fast to process. Instead of copying the conversation as it’s built and transformed, it stays stored in REDIS, and the ID to the vCon is passed, optimizing processing efficiency even at very large data sizes. REDIS also provides inter task communication using a series of PUB/SUB channels, coordinating the activities of the conserver for both local software (that inside the conserver itself) but also for external software such as Lambdas or exporting onto other systems like Apache Kafka. Also, third party and hardware enabled systems can use REDIS as a data interchange system, loading and unloading large media files in coordination with the data pipeline.

Each vcon is stored in REDIS using JSON and named with a regular key: vcon:{{vcon-uuid}}, as are chains "chains:{{name}}", links "link:{{name}}" and storages "storage:{{name}}}". REDIS allows for the addition of dedicated hardware to accelerate long running and high compute use cases such as transcription and video redaction, as these systems can connect directly to REDIS relieving scale issues from general purpose hardware, while managing the overhead of moving large amounts of data. Links take a vCon ID as inputs, and bear the responsibility of reading vCons if required, or giving them the option to hand off to optimized hardware.

FAST API provides the application infrastructure for the conserver. The transformation steps are developed as Python modules and loaded as tasks managed by FAST API. As each task finishes, it notifies other system elements by publishing UUID of the vCon. Other tasks wait on these notifications, and when they receive the notification, they can act on that same vCon for whatever purpose they may have. In addition, FAST API provides a REST API to the store of vCons, and a simple UI to manage the conserver.


# Standard Links

Links are the processing units of the Conserver. Each link performs a specific operation on a vCon as it flows through a chain. Links can analyze content, transform data, route vCons, integrate with external services, and more.

The conserver currently ships **22 standard links**. They are organized in this page by what they do:

| Category                | Links                                                                                                        |
| ----------------------- | ------------------------------------------------------------------------------------------------------------ |
| **Transcription**       | `deepgram_link`, `groq_whisper`, `hugging_face_whisper`, `openai_transcribe`, `transcribe`, `wtf_transcribe` |
| **Analysis**            | `analyze`, `analyze_vcon`, `analyze_and_label`, `check_and_tag`, `detect_engagement`, `hugging_llm_link`     |
| **Routing & filtering** | `sampler`, `jq_link`, `tag_router`                                                                           |
| **Data management**     | `tag`, `diet`, `expire_vcon`                                                                                 |
| **Integration**         | `webhook`, `post_analysis_to_slack`                                                                          |
| **Audit & compliance**  | `scitt`, `datatrails`                                                                                        |

All links emit OpenTelemetry metrics (latency, error counts, cache hits where applicable) and trace spans. If you've wired up the [`vcon-mcp-adapters`](/tools/vcon-mcp-adapters) OTEL collector, you'll see per-link spans automatically.

## Link Interface

All links implement the same interface:

```python
def run(vcon_uuid: str, link_name: str, opts: dict = default_options) -> str | None:
    """
    Process a vCon through this link.

    Args:
        vcon_uuid: UUID of the vCon to process
        link_name: Name of this link in the configuration
        opts: Configuration options merged with defaults

    Returns:
        vcon_uuid: Continue processing with this vCon UUID
        None: Stop chain processing (filter out this vCon)
    """
```

## Available Links

### Transcription Links

These links convert audio recordings in vCon dialogs to text transcripts.

***

#### deepgram\_link

Speech-to-text transcription using the Deepgram API with automatic language detection and confidence scoring.

```yaml
links:
  deepgram:
    module: links.deepgram_link
    options:
      DEEPGRAM_KEY: "your-api-key"
      minimum_duration: 30
      api:
        model: "nova-2"
        smart_format: true
        detect_language: true
```

| Option                | Description                                     | Default    |
| --------------------- | ----------------------------------------------- | ---------- |
| `DEEPGRAM_KEY`        | Deepgram API key                                | (required) |
| `minimum_duration`    | Minimum audio duration in seconds to transcribe | `30`       |
| `api.model`           | Deepgram model to use                           | `nova-2`   |
| `api.smart_format`    | Enable smart formatting                         | `true`     |
| `api.detect_language` | Enable automatic language detection             | `true`     |

***

#### groq\_whisper

Speech-to-text transcription using Groq's implementation of the Whisper ASR model.

```yaml
links:
  groq_whisper:
    module: links.groq_whisper
    options:
      GROQ_API_KEY: "your-api-key"
      model: "whisper-large-v3"
      minimum_duration: 3
```

| Option             | Description                       | Default            |
| ------------------ | --------------------------------- | ------------------ |
| `GROQ_API_KEY`     | Groq API key                      | (required)         |
| `model`            | Whisper model to use              | `whisper-large-v3` |
| `minimum_duration` | Minimum audio duration in seconds | `3`                |

***

#### hugging\_face\_whisper

Speech-to-text transcription using Hugging Face's Whisper implementation, supporting both API-based and local inference.

```yaml
links:
  hf_whisper:
    module: links.hugging_face_whisper
    options:
      model: "openai/whisper-large-v3"
      minimum_duration: 3
```

| Option             | Description                       | Default                   |
| ------------------ | --------------------------------- | ------------------------- |
| `model`            | Hugging Face model identifier     | `openai/whisper-large-v3` |
| `minimum_duration` | Minimum audio duration in seconds | `3`                       |

***

#### openai\_transcribe

Speech-to-text transcription using OpenAI's Whisper API or Azure OpenAI. Supports automatic chunking for long audio files.

```yaml
links:
  openai_transcribe:
    module: links.openai_transcribe
    options:
      # Public OpenAI
      OPENAI_API_KEY: "sk-..."

      # Or Azure OpenAI
      AZURE_OPENAI_API_KEY: "your-key"
      AZURE_OPENAI_ENDPOINT: "https://your-resource.openai.azure.com"
      AZURE_OPENAI_API_VERSION: "2024-10-21"

      model: "gpt-4o-transcribe"
      language: "en"
      minimum_duration: 3
      max_chunk_duration: 480
      use_silence_chunking: true
      silence_thresh: -40
      silence_len: 2000
```

| Option                  | Description                          | Default             |
| ----------------------- | ------------------------------------ | ------------------- |
| `OPENAI_API_KEY`        | OpenAI API key                       | (none)              |
| `AZURE_OPENAI_API_KEY`  | Azure OpenAI API key                 | (none)              |
| `AZURE_OPENAI_ENDPOINT` | Azure OpenAI endpoint URL            | (none)              |
| `model`                 | Model to use                         | `gpt-4o-transcribe` |
| `language`              | Language code                        | `en`                |
| `minimum_duration`      | Minimum audio duration in seconds    | `3`                 |
| `max_chunk_duration`    | Maximum chunk duration for splitting | `480` (8 min)       |
| `use_silence_chunking`  | Split at silence points              | `true`              |
| `silence_thresh`        | Silence threshold in dBFS            | `-40`               |
| `silence_len`           | Minimum silence length in ms         | `2000`              |

***

#### transcribe

Local transcription using the vCon library's built-in transcription capabilities.

```yaml
links:
  transcribe:
    module: links.transcribe
    options:
      transcribe_options:
        model_size: "base"
        output_options: ["vendor"]
```

| Option                              | Description           | Default      |
| ----------------------------------- | --------------------- | ------------ |
| `transcribe_options.model_size`     | Model size            | `base`       |
| `transcribe_options.output_options` | Output format options | `["vendor"]` |

***

#### wtf\_transcribe

Transcribes dialog recordings via the `vfun` transcription service and writes a [WTF (World Transcription Format)](/extensions/wtf-transcription)-shaped analysis entry. Refactored in May 2026 to decompose `run()` and normalize timeout option names.

```yaml
links:
  wtf:
    module: links.wtf_transcribe
    options:
      vfun-server-url: "https://wtf.example.com/transcribe"
      api-key: "your-vfun-key"
      language: "en"
      diarize: true
      vfun-timeout: 300
      url-timeout: 60
```

| Option            | Description                       | Default              |
| ----------------- | --------------------------------- | -------------------- |
| `vfun-server-url` | vfun transcription endpoint       | (required)           |
| `api-key`         | Service API key                   | `None`               |
| `language`        | BCP-47 language hint              | `None` (auto-detect) |
| `diarize`         | Emit speaker labels               | `false`              |
| `vfun-timeout`    | Transcription request timeout (s) | `300`                |
| `url-timeout`     | Media-fetch timeout (s)           | `60`                 |

Writes an `analysis[]` entry with `type: "wtf_transcription"`, `vendor` inferred from the service response, `encoding: "json"`, and a WTF document in `body`. See [WTF Transcription extension](/extensions/wtf-transcription) for the body shape.

***

### Analysis Links

These links use AI to analyze and extract insights from vCon content.

***

#### analyze

OpenAI-powered analysis of vCon transcripts with customizable prompts, sampling, and retry mechanisms.

```yaml
links:
  analyze:
    module: links.analyze
    options:
      OPENAI_API_KEY: "sk-..."
      prompt: "Summarize this transcript in 3 bullet points"
      analysis_type: "summary"
      model: "gpt-4-turbo"
      sampling_rate: 1
      temperature: 0.3
      source:
        analysis_type: "transcript"
        text_location: "body.text"
```

| Option                 | Description                        | Default             |
| ---------------------- | ---------------------------------- | ------------------- |
| `OPENAI_API_KEY`       | OpenAI API key                     | (required)          |
| `prompt`               | Analysis prompt                    | (required)          |
| `analysis_type`        | Type label for the analysis        | `summary`           |
| `model`                | OpenAI model                       | `gpt-3.5-turbo-16k` |
| `sampling_rate`        | Fraction of vCons to analyze (0-1) | `1`                 |
| `temperature`          | Model temperature                  | `0.3`               |
| `source.analysis_type` | Source analysis type to analyze    | `transcript`        |
| `source.text_location` | Path to text within source         | `body.text`         |

***

#### analyze\_vcon

AI analysis of entire vCon objects, returning structured JSON output.

```yaml
links:
  analyze_vcon:
    module: links.analyze_vcon
    options:
      OPENAI_API_KEY: "sk-..."
      system_prompt: "You are a conversation analyst."
      prompt: "Analyze this vCon and return insights as JSON."
      analysis_type: "vcon_analysis"
      model: "gpt-4-turbo"
```

| Option           | Description                 | Default         |
| ---------------- | --------------------------- | --------------- |
| `OPENAI_API_KEY` | OpenAI API key              | (required)      |
| `system_prompt`  | System prompt for the model | (optional)      |
| `prompt`         | Analysis prompt             | (required)      |
| `analysis_type`  | Type label for the analysis | `vcon_analysis` |
| `model`          | OpenAI model                | `gpt-4-turbo`   |

***

#### detect\_engagement

Detects whether both parties actively engaged in a conversation.

```yaml
links:
  engagement:
    module: links.detect_engagement
    options:
      OPENAI_API_KEY: "sk-..."
      prompt: "Did both the customer and the agent speak? Respond with 'true' or 'false'."
      analysis_type: "engagement_analysis"
      model: "gpt-4.1"
      source:
        analysis_type: "transcript"
        text_location: "body.paragraphs.transcript"
```

| Option           | Description         | Default                       |
| ---------------- | ------------------- | ----------------------------- |
| `OPENAI_API_KEY` | OpenAI API key      | (required)                    |
| `prompt`         | Evaluation prompt   | (engagement detection prompt) |
| `analysis_type`  | Type label          | `engagement_analysis`         |
| `model`          | OpenAI model        | `gpt-4.1`                     |
| `sampling_rate`  | Fraction to process | `1`                           |

Adds an `engagement` tag with value `true` or `false`.

***

#### analyze\_and\_label

Combined analysis that extracts labels/categories and applies them as tags.

```yaml
links:
  labeler:
    module: links.analyze_and_label
    options:
      OPENAI_API_KEY: "sk-..."
      prompt: "Analyze this transcript and provide relevant labels."
      analysis_type: "labeled_analysis"
      model: "gpt-4-turbo"
      response_format:
        type: "json_object"
```

| Option            | Description             | Default                   |
| ----------------- | ----------------------- | ------------------------- |
| `OPENAI_API_KEY`  | OpenAI API key          | (required)                |
| `prompt`          | Label extraction prompt | (categorization prompt)   |
| `analysis_type`   | Type label              | `labeled_analysis`        |
| `model`           | OpenAI model            | `gpt-4-turbo`             |
| `response_format` | Response format         | `{"type": "json_object"}` |

Returns JSON with `labels` array and applies each label as a tag.

***

#### check\_and\_tag

Evaluates a condition using AI and applies a tag if the condition is met.

```yaml
links:
  check_complaint:
    module: links.check_and_tag
    options:
      OPENAI_API_KEY: "sk-..."
      tag_name: "complaint"
      tag_value: "detected"
      evaluation_question: "Does this conversation contain a customer complaint?"
      model: "gpt-5"
      source:
        analysis_type: "transcript"
        text_location: "body"
```

| Option                | Description          | Default    |
| --------------------- | -------------------- | ---------- |
| `OPENAI_API_KEY`      | OpenAI API key       | (required) |
| `tag_name`            | Tag name to apply    | (required) |
| `tag_value`           | Tag value to apply   | (required) |
| `evaluation_question` | Question to evaluate | (required) |
| `model`               | OpenAI model         | `gpt-5`    |

***

#### hugging\_llm\_link

AI analysis using Hugging Face language models, supporting both API and local inference.

```yaml
links:
  hf_analysis:
    module: links.hugging_llm_link
    options:
      model: "mistralai/Mistral-7B-Instruct-v0.2"
      prompt: "Summarize this conversation."
      analysis_type: "hf_summary"
```

| Option          | Description                   | Default       |
| --------------- | ----------------------------- | ------------- |
| `model`         | Hugging Face model identifier | (required)    |
| `prompt`        | Analysis prompt               | (required)    |
| `analysis_type` | Type label                    | `hf_analysis` |

***

### Routing and Filtering Links

These links control vCon flow through chains.

***

#### sampler

Selectively processes vCons based on various sampling methods.

```yaml
links:
  sampler:
    module: links.sampler
    options:
      method: "percentage"  # percentage, rate, modulo, time
      percentage: 10        # For percentage method
      rate: 100            # For rate method (1 per N)
      modulo: 5            # For modulo method
```

| Option       | Description                     | Default      |
| ------------ | ------------------------------- | ------------ |
| `method`     | Sampling method                 | `percentage` |
| `percentage` | Percentage to process (0-100)   | `100`        |
| `rate`       | Process 1 out of N              | `1`          |
| `modulo`     | Process if UUID modulo equals 0 | `1`          |

Returns `None` for filtered vCons, stopping their chain processing.

***

#### jq\_link

Filters vCons using jq expressions for complex content-based filtering.

```yaml
links:
  filter_sales:
    module: links.jq_link
    options:
      expression: '.parties[] | select(.role == "agent")'
      forward_on_match: true
      forward_list: "sales_ingress"
```

| Option             | Description                          | Default    |
| ------------------ | ------------------------------------ | ---------- |
| `expression`       | jq expression to evaluate            | (required) |
| `forward_on_match` | Continue chain if expression matches | `true`     |
| `forward_list`     | Alternative ingress list for matches | (none)     |

***

#### tag\_router

Routes vCons to additional Redis lists based on tags attached to the vCon. The vCon is pushed onto every matching target list; processing in the current chain continues unless `forward_original` is set to `false`.

```yaml
links:
  router:
    module: links.tag_router
    options:
      tag_routes:
        priority: "priority_queue"
        urgent: "urgent_queue"
        complaint: "complaint_review_queue"
      forward_original: true
```

| Option             | Description                                                                                                                                 | Default |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
| `tag_routes`       | Dict mapping tag value → target Redis list name. The link checks tags in the vCon's `attachments[]` of type `tags` against the keys here.   | `{}`    |
| `forward_original` | If `true`, continue the current chain after routing. If `false`, return `None` to stop the chain (vCon proceeds only on the routed queues). | `true`  |

Returns `vcon_uuid` (chain continues) or `None` (chain stops) per `forward_original`.

***

### Data Management Links

These links modify vCon content.

***

#### tag

Adds configurable tags to vCons.

```yaml
links:
  add_tags:
    module: links.tag
    options:
      tags:
        - name: "source"
          value: "phone"
        - name: "processed"
          value: "true"
```

| Option         | Description         | Default    |
| -------------- | ------------------- | ---------- |
| `tags`         | List of tags to add | `[]`       |
| `tags[].name`  | Tag name            | (required) |
| `tags[].value` | Tag value           | (required) |

***

#### diet

Reduces vCon size by removing or redirecting elements. Useful for data minimization and privacy.

```yaml
links:
  slim_down:
    module: links.diet
    options:
      remove_dialog_bodies: true
      remove_attachments: true
      remove_analysis_types:
        - "raw_transcript"
      redirect_media_to_storage: "s3"
      remove_system_prompts: true
```

| Option                      | Description                | Default |
| --------------------------- | -------------------------- | ------- |
| `remove_dialog_bodies`      | Remove dialog body content | `false` |
| `remove_attachments`        | Remove all attachments     | `false` |
| `remove_analysis_types`     | Analysis types to remove   | `[]`    |
| `redirect_media_to_storage` | Move media to storage      | (none)  |
| `remove_system_prompts`     | Remove system prompts      | `false` |

***

#### expire\_vcon

Sets a Redis TTL on the vCon key so the working copy is cleaned up automatically. The vCon stays in any storage backends configured on the chain — this only affects the Redis hot cache.

```yaml
links:
  set_expiry:
    module: links.expire_vcon
    options:
      seconds: 86400  # 24 hours
```

| Option    | Description                                     | Default            |
| --------- | ----------------------------------------------- | ------------------ |
| `seconds` | TTL in seconds applied via `EXPIRE vcon:{uuid}` | `86400` (24 hours) |

***

### Integration Links

These links connect to external services.

***

#### webhook

POSTs the current vCon as JSON to one or more webhook URLs. The same module is available as a [storage backend](/conserver/storage#webhook) if you'd rather invoke webhooks after the chain rather than mid-chain.

```yaml
links:
  notify:
    module: links.webhook
    options:
      webhook-urls:
        - "https://api.example.com/vcon-webhook"
        - "https://backup.example.com/vcon-webhook"
      headers:
        Authorization: "Bearer token123"
        x-conserver-api-token: "your-api-token"
```

| Option         | Description                           | Default |
| -------------- | ------------------------------------- | ------- |
| `webhook-urls` | List of URLs to POST the vCon JSON to | `[]`    |
| `headers`      | Headers to attach to each request     | `{}`    |

Each URL is called sequentially with a `POST` containing the full vCon JSON. Per-call latency and status codes are recorded as OTEL metrics.

***

#### post\_analysis\_to\_slack

Posts vCon analysis results to Slack channels.

```yaml
links:
  slack_notify:
    module: links.post_analysis_to_slack
    options:
      webhook_url: "https://hooks.slack.com/services/..."
      channel: "#vcon-alerts"
      analysis_type: "summary"
      template: "New conversation summary: {body}"
      condition:
        tag_name: "priority"
        tag_value: "high"
```

| Option          | Description            | Default    |
| --------------- | ---------------------- | ---------- |
| `webhook_url`   | Slack webhook URL      | (required) |
| `channel`       | Slack channel          | (required) |
| `analysis_type` | Analysis type to post  | `summary`  |
| `template`      | Message template       | `{body}`   |
| `condition`     | Optional tag condition | (none)     |

***

### Audit and Compliance Links

These links provide integrity and audit trail capabilities.

***

#### datatrails

Creates [DataTrails](https://app.datatrails.ai) Events for each vCon, producing a tamper-evident audit trail via OIDC-authenticated calls. DataTrails statements map onto SCITT envelopes — if you want a vendor-neutral transparency service, prefer the [`scitt`](#scitt) link instead.

```yaml
links:
  audit:
    module: links.datatrails
    options:
      api_url: "https://app.datatrails.ai/archivist"
      auth_url: "https://app.datatrails.ai/archivist/iam/v1/appidp/token"
      client_id: "${DATATRAILS_CLIENT_ID}"
      client_secret: "${DATATRAILS_CLIENT_SECRET}"
      partner_id: "your-partner-id"
      asset_attributes:
        arc_display_type: "vcon_droid"
        conserver_link_version: "auto"
```

| Option                        | Description                                  | Default                                                   |
| ----------------------------- | -------------------------------------------- | --------------------------------------------------------- |
| `api_url`                     | DataTrails Archivist API root                | `https://app.datatrails.ai/archivist`                     |
| `auth_url`                    | OIDC client-credentials token endpoint       | `https://app.datatrails.ai/archivist/iam/v1/appidp/token` |
| `client_id` / `client_secret` | OIDC client credentials                      | (required)                                                |
| `partner_id`                  | Partner identifier used in event attribution | `not-set`                                                 |
| `asset_attributes`            | Initial attributes for the DataTrails asset  | DataTrails-recommended defaults                           |

DataTrails is the durable store for the audit data — the vCon itself is not modified.

***

#### scitt

Registers a COSE-signed statement about the current vCon on a [SCRAPI](https://datatracker.ietf.org/doc/draft-ietf-scitt-scrapi/)-compatible SCITT transparency service (such as [scittles](https://github.com/vcon-dev/scittles)), then verifies the returned COSE receipt and (optionally) stores it as an analysis entry on the vCon.

Added in May 2026 (SCITT v0.3.0). The lifecycle event recorded is controlled by `vcon_operation`; combine multiple instances of this link in a chain to record `vcon_created` early and `vcon_enhanced` after transcription.

```yaml
links:
  scitt_created:
    module: links.scitt
    options:
      scrapi_url: "http://scittles:8000"
      signing_key_pem: "${SCITT_SIGNING_KEY_PEM}"   # base64-encoded PEM (preferred for k8s/containers)
      # OR for local development:
      # signing_key_path: "/etc/scitt/signing-key.pem"
      issuer: "conserver"
      key_id: "conserver-key-1"
      vcon_operation: "vcon_created"
      store_receipt: true
```

| Option             | Description                                                         | Default                      |
| ------------------ | ------------------------------------------------------------------- | ---------------------------- |
| `scrapi_url`       | SCRAPI endpoint for the SCITT transparency service                  | `http://scittles:8000`       |
| `signing_key_pem`  | Base64-encoded PEM. Preferred for containers / k8s deployments.     | `None`                       |
| `signing_key_path` | Filesystem path to the signing key (fallback for local development) | `/etc/scitt/signing-key.pem` |
| `issuer`           | COSE issuer identifier                                              | `conserver`                  |
| `key_id`           | Key identifier                                                      | `conserver-key-1`            |
| `vcon_operation`   | Lifecycle event recorded (e.g. `vcon_created`, `vcon_enhanced`)     | `vcon_created`               |
| `store_receipt`    | Append the COSE receipt as an analysis entry on the vCon            | `true`                       |

Writes an `analysis[]` entry with `type: "scitt_receipt"`, `vendor: "scittles"`, and a body containing `entry_id`, `cose_receipt`, and `subject`. See [Lifecycle extension](/extensions/lifecycle) for how this composes with the vCon lifecycle audit story.

***

## Using Links in Chains

Links are combined into chains in the configuration:

```yaml
chains:
  main_pipeline:
    links:
      - deepgram           # Transcribe audio
      - analyze            # Generate summary
      - check_complaint    # Check for complaints
      - router             # Route based on tags
    storages:
      - postgres
      - s3
    ingress_lists:
      - incoming_calls
    egress_lists:
      - processed_calls
    enabled: 1
    timeout: 300
```

Links execute in order. If any link returns `None`, chain processing stops for that vCon.

## Common Patterns

### Conditional Processing

Use `sampler` or `jq_link` to process only certain vCons:

```yaml
chains:
  sample_analysis:
    links:
      - sampler    # Process 10% of vCons
      - analyze
```

### Multi-stage Analysis

Chain multiple analysis links for comprehensive processing:

```yaml
chains:
  full_analysis:
    links:
      - deepgram           # Step 1: Transcribe
      - analyze            # Step 2: Summarize
      - detect_engagement  # Step 3: Check engagement
      - check_complaint    # Step 4: Detect complaints
      - analyze_and_label  # Step 5: Categorize
```

### Tag-based Routing

Use tags to route vCons to different downstream chains:

```yaml
chains:
  intake:
    links:
      - deepgram
      - analyze
      - tag_router
    ingress_lists: [incoming]
    # No egress_lists - router handles distribution

  priority_handling:
    links:
      - notify_slack
      - priority_storage
    ingress_lists: [priority_queue]
```


# Storage

The standard storages supported by the conserver

The Conserver supports multiple storage backends for persisting vCons after processing. Each storage backend can be configured in the `config.yml` file and assigned to one or more chains.

## How Storage Works

When a chain finishes processing a vCon, it stores the result in all configured storage backends for that chain. This allows you to:

* Store vCons in multiple locations simultaneously (e.g., S3 for archival and PostgreSQL for querying)
* Choose the right storage for your use case
* Implement backup and redundancy strategies

## Storage Interface

All storage backends implement a standard interface:

```python
def save(vcon_uuid: str, opts: dict) -> None
def get(vcon_uuid: str, opts: dict) -> Optional[dict]
def delete(vcon_uuid: str, opts: dict) -> bool  # Some backends
```

## Available Storage Backends

### MongoDB

Document-oriented NoSQL database, ideal for storing vCons in their native JSON structure.

```yaml
storages:
  mongo:
    module: storage.mongo
    options:
      url: "mongodb://localhost:27017/"
      database: "conserver"
      collection: "vcons"
```

| Option       | Description            | Default                      |
| ------------ | ---------------------- | ---------------------------- |
| `url`        | MongoDB connection URL | `mongodb://localhost:27017/` |
| `database`   | Database name          | `conserver`                  |
| `collection` | Collection name        | `vcons`                      |

***

### PostgreSQL

Relational database with JSON support, ideal for complex queries and reporting.

```yaml
storages:
  postgres:
    module: storage.postgres
    options:
      database: "vcon_db"
      user: "postgres"
      password: "your_password"
      host: "localhost"
      port: 5432
      table_name: "vcons"
```

| Option       | Description          | Default     |
| ------------ | -------------------- | ----------- |
| `database`   | Database name        | `vcon_db`   |
| `user`       | Database username    | `postgres`  |
| `password`   | Database password    | (required)  |
| `host`       | Database host        | `localhost` |
| `port`       | Database port        | `5432`      |
| `table_name` | Table name for vCons | `vcons`     |

The PostgreSQL storage automatically creates the table if it doesn't exist, with columns for:

* `id` (UUID, primary key)
* `vcon` (text)
* `uuid` (UUID)
* `created_at` (datetime)
* `updated_at` (datetime)
* `subject` (text)
* `vcon_json` (JSONB for querying)

***

### Elasticsearch

Full-text search and analytics engine. Indexes vCon components (parties, dialog, analysis, attachments) into separate indices for powerful searching.

```yaml
storages:
  elasticsearch:
    module: storage.elasticsearch
    options:
      # Option 1: Elastic Cloud
      cloud_id: "your_cloud_id"
      api_key: "your_api_key"

      # Option 2: Self-hosted
      url: "https://localhost:9200"
      username: "elastic"
      password: "your_password"
      ca_certs: "/path/to/ca.crt"  # Optional

      index_prefix: "myapp_"  # Optional prefix for index names
```

| Option         | Description                   | Default |
| -------------- | ----------------------------- | ------- |
| `cloud_id`     | Elastic Cloud deployment ID   | (empty) |
| `api_key`      | Elastic Cloud API key         | (empty) |
| `url`          | Self-hosted Elasticsearch URL | (none)  |
| `username`     | Basic auth username           | (none)  |
| `password`     | Basic auth password           | (none)  |
| `ca_certs`     | Path to CA certificate        | (none)  |
| `index_prefix` | Prefix for all index names    | (empty) |

Creates multiple indices:

* `vcon_parties_{role}` - Party information by role
* `vcon_attachments_{type}` - Attachments by type
* `vcon_analysis_{type}` - Analysis results by type
* `vcon_dialog` - Dialog entries

***

### Milvus

Vector database for semantic search. Stores vCon content as embeddings for similarity-based retrieval.

```yaml
storages:
  milvus:
    module: storage.milvus
    options:
      host: "localhost"
      port: "19530"
      collection_name: "vcons"

      # OpenAI embedding configuration
      api_key: "sk-your-openai-key"
      organization: "org-xxx"  # Optional
      embedding_model: "text-embedding-3-small"
      embedding_dim: 1536

      # Collection settings
      create_collection_if_missing: true
      skip_if_exists: true

      # Index configuration
      index_type: "IVF_FLAT"  # IVF_FLAT, IVF_SQ8, IVF_PQ, HNSW, ANNOY, FLAT
      metric_type: "L2"       # L2, IP (Inner Product), COSINE
      nlist: 128              # For IVF indexes
      m: 16                   # For HNSW: edges per node
      ef_construction: 200    # For HNSW: construction candidate list size
```

| Option                         | Description                   | Default                  |
| ------------------------------ | ----------------------------- | ------------------------ |
| `host`                         | Milvus server hostname        | `localhost`              |
| `port`                         | Milvus server port            | `19530`                  |
| `collection_name`              | Collection name               | `vcons`                  |
| `api_key`                      | OpenAI API key for embeddings | (required)               |
| `organization`                 | OpenAI organization ID        | (none)                   |
| `embedding_model`              | OpenAI embedding model        | `text-embedding-3-small` |
| `embedding_dim`                | Embedding vector dimensions   | `1536`                   |
| `create_collection_if_missing` | Auto-create collection        | `false`                  |
| `skip_if_exists`               | Skip storing existing vCons   | `true`                   |
| `index_type`                   | Vector index type             | `IVF_FLAT`               |
| `metric_type`                  | Distance metric               | `L2`                     |
| `nlist`                        | IVF cluster count             | `128`                    |
| `m`                            | HNSW edges per node           | `16`                     |
| `ef_construction`              | HNSW construction list size   | `200`                    |

The Milvus storage extracts text from transcripts, summaries, party info, and dialog to create searchable embeddings.

***

### Amazon S3

Object storage for cloud-native archival and backup.

```yaml
storages:
  s3:
    module: storage.s3
    options:
      aws_access_key_id: "AKIAIOSFODNN7EXAMPLE"
      aws_secret_access_key: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
      aws_bucket: "my-vcon-bucket"
      s3_path: "vcons"  # Optional subdirectory
```

| Option                  | Description                        | Default    |
| ----------------------- | ---------------------------------- | ---------- |
| `aws_access_key_id`     | AWS access key ID                  | (required) |
| `aws_secret_access_key` | AWS secret access key              | (required) |
| `aws_bucket`            | S3 bucket name                     | (required) |
| `s3_path`               | Optional path prefix within bucket | (none)     |

Files are stored as `{s3_path}/{YYYY/MM/DD}/{uuid}.vcon` based on the vCon's creation date.

***

### SFTP

Secure file transfer to remote servers.

```yaml
storages:
  sftp:
    module: storage.sftp
    options:
      url: "sftp.example.com"
      port: 22
      username: "vcon_user"
      password: "your_password"
      path: "/uploads/vcons"
      filename: "vcon"
      extension: "json"
      add_timestamp_to_filename: true
```

| Option                      | Description                  | Default     |
| --------------------------- | ---------------------------- | ----------- |
| `url`                       | SFTP server hostname         | `localhost` |
| `port`                      | SFTP server port             | `22`        |
| `username`                  | SFTP username                | (required)  |
| `password`                  | SFTP password                | (required)  |
| `path`                      | Remote directory path        | `.`         |
| `filename`                  | Base filename                | `vcon`      |
| `extension`                 | File extension               | `json`      |
| `add_timestamp_to_filename` | Append timestamp to filename | `true`      |

***

### File System

Local filesystem storage for development or simple deployments.

```yaml
storages:
  file:
    module: storage.file
    options:
      path: "/var/vcons"
      filename: "vcon"
      extension: "json"
      add_timestamp_to_filename: true
```

| Option                      | Description                  | Default |
| --------------------------- | ---------------------------- | ------- |
| `path`                      | Directory to store files     | `.`     |
| `filename`                  | Base filename                | `vcon`  |
| `extension`                 | File extension               | `json`  |
| `add_timestamp_to_filename` | Append timestamp to filename | `true`  |

***

### Redis Storage

Dedicated Redis storage with custom prefix and expiration, separate from the main Redis working storage.

```yaml
storages:
  redis_storage:
    module: storage.redis_storage
    options:
      redis_url: "redis://localhost:6379"
      prefix: "vcon_archive"
      expires: 604800  # 7 days in seconds
```

| Option      | Description                 | Default                  |
| ----------- | --------------------------- | ------------------------ |
| `redis_url` | Redis connection URL        | `redis://localhost:6379` |
| `prefix`    | Key prefix for stored vCons | `vcon_storage`           |
| `expires`   | TTL in seconds              | `604800` (7 days)        |

***

### ChatGPT Files

Store vCons in OpenAI's file storage for use with ChatGPT assistants and vector stores.

```yaml
storages:
  chatgpt_files:
    module: storage.chatgpt_files
    options:
      organization_key: "org-xxxxx"
      project_key: "proj_xxxxxxx"
      api_key: "sk-proj-xxxxxx"
      vector_store_id: "vs_xxxxxx"
      purpose: "assistants"
```

| Option             | Description                     | Default      |
| ------------------ | ------------------------------- | ------------ |
| `organization_key` | OpenAI organization ID          | (required)   |
| `project_key`      | OpenAI project ID               | (required)   |
| `api_key`          | OpenAI API key                  | (required)   |
| `vector_store_id`  | Vector store ID to add files to | (required)   |
| `purpose`          | File purpose                    | `assistants` |

***

### Microsoft Dataverse

Integration with Microsoft Dynamics 365 / Dataverse for enterprise CRM scenarios.

```yaml
storages:
  dataverse:
    module: storage.dataverse
    options:
      url: "https://org.crm.dynamics.com"
      api_version: "9.2"
      tenant_id: "your-tenant-id"
      client_id: "your-client-id"
      client_secret: "your-client-secret"
      entity_name: "vcon_storage"
      uuid_field: "vcon_uuid"
      data_field: "vcon_data"
      subject_field: "vcon_subject"
      created_at_field: "vcon_created_at"
```

| Option             | Description                | Default           |
| ------------------ | -------------------------- | ----------------- |
| `url`              | Dataverse/Dynamics 365 URL | (required)        |
| `api_version`      | Dataverse API version      | `9.2`             |
| `tenant_id`        | Azure AD tenant ID         | (required)        |
| `client_id`        | Azure AD application ID    | (required)        |
| `client_secret`    | Azure AD client secret     | (required)        |
| `entity_name`      | Custom entity name         | `vcon_storage`    |
| `uuid_field`       | Field for vCon UUID        | `vcon_uuid`       |
| `data_field`       | Field for vCon JSON data   | `vcon_data`       |
| `subject_field`    | Field for vCon subject     | `vcon_subject`    |
| `created_at_field` | Field for creation date    | `vcon_created_at` |

Requires a custom entity to be created in Dataverse with the specified fields.

***

### Space and Time

Blockchain-based verifiable storage using Space and Time's decentralized database.

```yaml
storages:
  spaceandtime:
    module: storage.spaceandtime
    options:
      name: "spaceandtime"
```

Configuration is done via environment variables:

| Environment Variable           | Description                           |
| ------------------------------ | ------------------------------------- |
| `SXT_API_KEY`                  | Space and Time API key                |
| `SXT_VCON_TABLENAME`           | Table name (provided by SXT)          |
| `SXT_VCON_TABLE_WRITE_BISCUIT` | Write biscuit token (provided by SXT) |

***

### SCITT Transparency

**Added May 2026 (SCITT v0.3.0).** Registers each finalized vCon on a [SCRAPI](https://datatracker.ietf.org/doc/draft-ietf-scitt-scrapi/)-compatible SCITT transparency service as a COSE-signed statement. Unlike most storages, the SCITT receipt is **not written back to the vCon** — the transparency service is the authoritative store. Use this when you need a post-chain immutable proof that a vCon existed in a given state at a given time.

```yaml
storages:
  scitt_transparency:
    module: storage.scitt
    options:
      scrapi_url: "http://scittles:8000"
      signing_key_pem: "${SCITT_SIGNING_KEY_PEM}"  # base64-encoded PEM
      # OR for local development:
      # signing_key_path: "/etc/scitt/signing-key.pem"
      issuer: "conserver"
      key_id: "conserver-key-1"
      operations:
        - "vcon_enhanced"
```

| Option             | Description                                                                      | Default                      |
| ------------------ | -------------------------------------------------------------------------------- | ---------------------------- |
| `scrapi_url`       | SCRAPI endpoint of the transparency service                                      | `http://scittles:8000`       |
| `signing_key_pem`  | Base64-encoded PEM (preferred for containers / k8s)                              | `None`                       |
| `signing_key_path` | Filesystem path to the signing key (fallback)                                    | `/etc/scitt/signing-key.pem` |
| `issuer`           | COSE issuer identifier                                                           | `conserver`                  |
| `key_id`           | Key identifier                                                                   | `conserver-key-1`            |
| `operations`       | List of lifecycle event types to register (e.g. `vcon_created`, `vcon_enhanced`) | `["vcon_enhanced"]`          |

For the in-chain version that *does* attach a receipt to the vCon, see the [`scitt` link](/conserver/standard-links#scitt). The choice depends on whether you want the receipt to travel with the vCon (link) or live only on the transparency service (storage). Both compose with the [Lifecycle extension](/extensions/lifecycle).

***

### vCon MCP

Proxies storage writes to a running [vCon MCP server](/mcp-server) via its REST API. Use this when you want vCons to land in an MCP-backed database (typically Supabase) so they're immediately queryable by LLM agents through the MCP contract tools.

```yaml
storages:
  mcp_store:
    module: storage.vcon_mcp
    options:
      base_url: "http://vcon-mcp:3000/api/v1"
      api_key: "${VCON_MCP_API_KEY}"
      timeout: 30
```

| Option     | Description                                                         | Default                        |
| ---------- | ------------------------------------------------------------------- | ------------------------------ |
| `base_url` | Root of the vCon MCP REST API                                       | `http://127.0.0.1:3000/api/v1` |
| `api_key`  | Bearer token for the MCP server (optional, depending on deployment) | `None`                         |
| `timeout`  | HTTP timeout in seconds                                             | `30`                           |

Behavior: `save()` POSTs to `/vcons`, `get()` GETs from `/vcons/{uuid}`, `delete()` DELETEs `/vcons/{uuid}`. The MCP server is the source of truth — there's no local copy beyond the conserver's Redis hot cache.

***

### Webhook

POSTs the finalized vCon JSON to one or more webhook URLs after the chain completes. Mirrors the [`webhook` link](/conserver/standard-links#webhook) but runs in the storage phase (in parallel with other storages) rather than mid-chain.

```yaml
storages:
  notify:
    module: storage.webhook
    options:
      webhook-urls:
        - "https://api.example.com/vcons"
        - "https://archive.example.com/vcons"
      headers:
        Authorization: "Bearer your-token"
```

| Option         | Description                      | Default |
| -------------- | -------------------------------- | ------- |
| `webhook-urls` | List of URLs to POST the vCon to | `[]`    |
| `headers`      | Headers attached to every call   | `{}`    |

Use the storage form when the webhook is the durable destination (e.g. another system's ingestion endpoint); use the link form when the webhook is an interactive side-effect mid-chain (e.g. alerting). Per-call latency and status are emitted as OTEL metrics.

***

## Using Multiple Storages

You can configure multiple storage backends and assign them to different chains:

```yaml
storages:
  archive_s3:
    module: storage.s3
    options:
      aws_bucket: "vcon-archive"
      # ... other options

  query_postgres:
    module: storage.postgres
    options:
      database: "vcon_analytics"
      # ... other options

  search_milvus:
    module: storage.milvus
    options:
      collection_name: "vcon_search"
      # ... other options

chains:
  main_pipeline:
    links: [transcribe, analyze]
    storages:
      - archive_s3      # Long-term storage
      - query_postgres  # Querying
      - search_milvus   # Semantic search
    ingress_lists: [incoming]
    egress_lists: [processed]
    enabled: 1
```

## Redis Caching

The Conserver uses Redis as its primary working storage. When a vCon is requested via the API but isn't in Redis, the system will:

1. Check each configured storage backend
2. If found, return the vCon to Redis with a configurable TTL
3. Serve subsequent requests from Redis

Configure the cache TTL with the `VCON_REDIS_EXPIRY` environment variable (default: 3600 seconds / 1 hour).


# Day In the Life of a vCon

To illustrate the normal operation of the conserver, let’s follow along as a conversation is extracted, transformed and the data is provided to a business team. For this example, we’ll assume the Conserver is started and configured to take conversations from a Freeswitch system, transcribe them, look for a particular subject (recalls) and send those to a PostGres table for the operations team.

1. A customer and an agent has a conversation using Freeswitch. A Freeswitch adapter is running that monitors calls and requesting recordings. For context, refer to <https://developer.signalwire.com/compatibility-api/xml/> to see the kinds of call events and recording options.
2. When the call on Freeswitch ends, the adapter uses the data from the call (parties, recordings) to create a vCon. This vCon is then sent to the Conserver in a POST to the conserver's API, naming the REDIS lists that feed each conserver chain. Alternatively, the vCon could also be inserted into REDIS directly, then adding the vCon UUID to each chain's ingress list.
3. This vCon is stored in REDIS as a JSON object under the UUID of the newly created vCon. By convention, the key is named with the pattern “vcon:uuid” (like vcon:7665-343535-58575-333).
4. In addition to the standard parts of a vCon, the dialog and parties, the adapter adds a new attachment (to the attachments section of the vCon standard) that details what adapter created the vCon, details important for debugging, etc. This attachment travels inside the vCon throughout it’s life, unless it is explicitly stripped off later on.
5. Based on a periodic timer, or triggered by an external API call, the conserver iterates over all of the processing chains. Each chain has a REDIS list that contains the vCons to be processed. On each tick, the conserver creates a task for each ID that is read from the list. Horizontal scaling is enabled by having a single REDIS cluster connected to multiple conservers. Each task iterates the vCon over the series of links in the chain.
6. In this example chain, the first link is called “transcription”, and unsurprisingly, transcribes conversations. Links expect a vCon UUID as an input, and return vCon UUIDs as outputs. This allows configurations of chains of links, the output of one feeding the input of the next, freely interchangeable in order, or vendor.
7. The transcription link (currently there are two versions to choose from, [Whisper.ai](http://whisper.ai/) and Deepgram) take the dialog section of the vCon (which holds the recorded voice) and transcribe them. This transcription is added to the vCon in the “analysis” section, and normally contains information like a complete transcription, and a confidence score and a time stamp for every word transcribed. The link then updates the stored vCon with this new analysis, using REDIS to avoid reading or copying the large data objects in the dialog.
8. The conserver is responsible for the ordering and execution of each link in the chain. It is not a requirement that a link be used once; it may be repeated several times within and between chains.
9. The second link in the chain is called “recall finder”, and uses the output of the transcription link. When it is called by the conserver, it loads the transcription attachment and looks for the word “recall” in the conversation. If it does not find the word, it can simply exit without creating any message for the downstream plugin, effectively ending the processing of that chain of links.
10. At this point, the vCon has been created, captured, transcribed and identified as having the information we want: it’s a recall conversation. For information systems that want a native JSON representative, the vCon can now be sent for consumption. For instance, it could now be sent via a web hook (HTTP POST) to any API endpoint. In like manner, it can be stored in any of the storage blocks, current options include the Mongo Database, REDIS, S3, PostGres or a local file system.
11. If the final destination has a fixed schema, like a Postgres database, a Google Spreadsheet or a no code tool, we need to create a “projection” for this data before the “recall finder” is done. A projection is a simple key-value store of the important information, determined by the use case. For illustration, assume we are interested in sending the transcription, the identity of the agent and the customer, and when the conversation happened. This projection, which directly corresponds to a single row in a database, with four columns (transcription, agent, customer, created at), will be added to the vCon, just as the transcription analysis was. At this point, the original vCon now has an attachment from the adapter, an analysis by the transcriber, and this new transcription analysis.
12. The final link is a PostGres projection. When it runs, it looks for projections on a vCon, then takes that information and uses it to add (or upsert) a new row with the information from the projection into a configured PostGres table. From the perspective of the business users of the data, they simply see rows of transcribed conversations that match a criterion. Data projections, like adapters, handle the differences between destinations: unique data projections are required for different kinds of relational database, no code tools, Google Sheets, etc.


# API

The Conserver provides a REST API built on FastAPI for managing vCons, chains, configuration, and more. The API supports both internal operations and external partner integrations with scoped authentication.

## Base URL

The API is served at the path configured by the `API_ROOT_PATH` environment variable (default: `/api`).

## Authorization

The Conserver API supports two authentication models:

### Internal API Authentication

For full system access, use the main API token via the `x-conserver-api-token` header (or custom header name via `CONSERVER_HEADER_NAME`).

```bash
curl -H "x-conserver-api-token: your-api-token" \
  "http://localhost:8000/api/vcon"
```

Configure the API token via environment variables:

| Variable                   | Description                                           |
| -------------------------- | ----------------------------------------------------- |
| `CONSERVER_API_TOKEN`      | Single API token for authentication                   |
| `CONSERVER_API_TOKEN_FILE` | Path to file containing API tokens (one per line)     |
| `CONSERVER_HEADER_NAME`    | Custom header name (default: `x-conserver-api-token`) |

When neither `CONSERVER_API_TOKEN` nor `CONSERVER_API_TOKEN_FILE` is set, authentication is disabled.

### External Ingress Authentication

For external partners, use ingress-specific API keys that only allow access to designated ingress lists. Configure in `config.yml`:

```yaml
ingress_auth:
  partner_ingress:
    - "partner-key-1"
    - "partner-key-2"
  customer_data: "single-customer-key"
```

External partners can only use the `/vcon/ingress` endpoint with their scoped keys (one ingress key per `ingress_list`).

***

## System Endpoints (no auth)

A small set of system endpoints are mounted at the application root (not under `API_ROOT_PATH`) and require **no authentication**, so they can be used by monitoring tooling, load balancers, and autoscalers.

### Version

```
GET /version
```

Returns build metadata.

**Response:** `200 OK`

```json
{
  "version": "2026.05.18",
  "git_commit": "5bc6b6e2c5c3a577d8295c1cb88f83d989f5db58",
  "build_time": "2026-05-18T10:00:00Z"
}
```

The version follows CalVer (`YYYY.MM.DD`). `git_commit` is the source-of-truth commit deployed; useful for confirming what's running after a roll-out.

***

### Health

```
GET /health
```

Health-check endpoint.

**Response:** `200 OK`

```json
{
  "status": "healthy",
  "version": { "version": "2026.05.18", "git_commit": "...", "build_time": "..." }
}
```

***

### Queue Depth

```
GET /stats/queue?list_name=<redis-list-name>
```

Returns the current depth of a Redis list (queue). Useful for backpressure-aware autoscaling and dashboards.

**Query Parameters:**

| Parameter   | Type | Description                       |
| ----------- | ---- | --------------------------------- |
| `list_name` | str  | Name of the Redis list to measure |

**Response:** `200 OK`

```json
{ "list_name": "incoming_calls", "depth": 127 }
```

***

## vCon Management

### List vCon UUIDs

```
GET /vcon
```

Retrieves a paginated list of vCon UUIDs, sorted by timestamp (newest first).

**Query Parameters:**

| Parameter | Type     | Description                           | Default |
| --------- | -------- | ------------------------------------- | ------- |
| `page`    | int      | Page number (1-indexed)               | `1`     |
| `size`    | int      | Items per page                        | `50`    |
| `since`   | datetime | Filter vCons created after this date  | (none)  |
| `until`   | datetime | Filter vCons created before this date | (none)  |

**Response:** `200 OK`

```json
["uuid-1", "uuid-2", "uuid-3"]
```

**Example:**

```bash
curl -H "x-conserver-api-token: $TOKEN" \
  "http://localhost:8000/api/vcon?page=1&size=10&since=2024-01-01"
```

***

### Get vCon by UUID

```
GET /vcon/{vcon_uuid}
```

Retrieves a single vCon by its UUID. First checks Redis, then falls back to configured storage backends.

**Response:** `200 OK` - Returns the full vCon JSON

**Response:** `404 Not Found` - vCon not found

**Example:**

```bash
curl -H "x-conserver-api-token: $TOKEN" \
  "http://localhost:8000/api/vcon/550e8400-e29b-41d4-a716-446655440000"
```

***

### Get Multiple vCons

```
GET /vcons
```

Retrieves multiple vCons by their UUIDs in a single request.

**Query Parameters:**

| Parameter    | Type        | Description                    |
| ------------ | ----------- | ------------------------------ |
| `vcon_uuids` | List\[UUID] | List of vCon UUIDs to retrieve |

**Response:** `200 OK`

```json
[
  { "uuid": "...", "vcon": "0.4.0", ... },
  { "uuid": "...", "vcon": "0.4.0", ... }
]
```

**Example:**

```bash
curl -H "x-conserver-api-token: $TOKEN" \
  "http://localhost:8000/api/vcons?vcon_uuids=uuid1&vcon_uuids=uuid2"
```

***

### Create vCon

```
POST /vcon
```

Stores a new vCon in Redis and indexes it for searching.

**Query Parameters:**

| Parameter       | Type       | Description                                |
| --------------- | ---------- | ------------------------------------------ |
| `ingress_lists` | List\[str] | Optional ingress queues to add the vCon to |

**Request Body:** Full vCon JSON object

**Response:** `201 Created` - Returns the stored vCon

**Example:**

```bash
curl -X POST "http://localhost:8000/api/vcon?ingress_lists=main_chain" \
  -H "x-conserver-api-token: $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "vcon": "0.4.0",
    "uuid": "550e8400-e29b-41d4-a716-446655440000",
    "created_at": "2024-01-15T10:30:00Z",
    "parties": [],
    "dialog": []
  }'
```

***

### Delete vCon

```
DELETE /vcon/{vcon_uuid}
```

Removes a vCon from Redis and all configured storage backends.

**Response:** `204 No Content`

**Example:**

```bash
curl -X DELETE -H "x-conserver-api-token: $TOKEN" \
  "http://localhost:8000/api/vcon/550e8400-e29b-41d4-a716-446655440000"
```

***

### Search vCons

```
GET /vcons/search
```

Search for vCons by party information (phone, email, name). At least one parameter is required. Multiple parameters use AND logic.

**Query Parameters:**

| Parameter | Type   | Description                 |
| --------- | ------ | --------------------------- |
| `tel`     | string | Phone number to search for  |
| `mailto`  | string | Email address to search for |
| `name`    | string | Party name to search for    |

**Response:** `200 OK`

```json
["uuid-1", "uuid-2"]
```

**Example:**

```bash
curl -H "x-conserver-api-token: $TOKEN" \
  "http://localhost:8000/api/vcons/search?tel=%2B1234567890&name=John"
```

***

## Chain Management

### Add to Ingress

```
POST /vcon/ingress
```

Adds vCon UUIDs to a processing chain's ingress list. This is the **only** ingress endpoint; external partners use the same path with a scoped key configured under `ingress_auth` in `config.yml`.

**Authentication:**

* **Internal use:** The main `x-conserver-api-token` grants access to any ingress list.
* **External partners:** A scoped key configured under `ingress_auth` for a specific ingress list. The key only authorizes that list — attempts to write to other lists return `403 Forbidden`. Configure in `config.yml`:

  ```yaml
  ingress_auth:
    partner_ingress:
      - "partner-key-1"
      - "partner-key-2"
    customer_data: "single-customer-key"
  ```

**Query Parameters:**

| Parameter      | Type   | Description              |
| -------------- | ------ | ------------------------ |
| `ingress_list` | string | Name of the ingress list |

**Request Body:**

```json
["uuid-1", "uuid-2", "uuid-3"]
```

**Response:** `204 No Content`

**Example:**

```bash
curl -X POST "http://localhost:8000/api/vcon/ingress?ingress_list=main_chain" \
  -H "x-conserver-api-token: $TOKEN" \
  -H "Content-Type: application/json" \
  -d '["uuid-1", "uuid-2"]'
```

***

### Get from Egress

```
GET /vcon/egress
```

Removes and returns vCon UUIDs from a chain's egress list.

**Query Parameters:**

| Parameter     | Type   | Description             | Default    |
| ------------- | ------ | ----------------------- | ---------- |
| `egress_list` | string | Name of the egress list | (required) |
| `limit`       | int    | Maximum UUIDs to remove | `1`        |

**Response:** `204 No Content` with body:

```json
["uuid-1", "uuid-2"]
```

**Example:**

```bash
curl -H "x-conserver-api-token: $TOKEN" \
  "http://localhost:8000/api/vcon/egress?egress_list=processed&limit=10"
```

***

### Count Egress Queue

```
GET /vcon/count
```

Returns the number of vCons in an egress list.

**Query Parameters:**

| Parameter     | Type   | Description             |
| ------------- | ------ | ----------------------- |
| `egress_list` | string | Name of the egress list |

**Response:** `200 OK`

```json
42
```

***

## Configuration

### Get Configuration

```
GET /config
```

Returns the current system configuration from the YAML file.

**Response:** `200 OK` - Returns full configuration as JSON

***

### Update Configuration

```
POST /config
```

Updates the system configuration file.

**Request Body:** Full configuration as JSON

**Response:** `204 No Content`

**Note:** Changes take effect immediately for new chain processing.

***

## Dead Letter Queue

When vCon processing fails, the vCon UUID is moved to a Dead Letter Queue (DLQ). Each ingress list has an associated DLQ named `{ingress_list}:dlq`.

### Get DLQ Contents

```
GET /dlq
```

Returns all vCon UUIDs in a dead letter queue.

**Query Parameters:**

| Parameter      | Type   | Description              |
| -------------- | ------ | ------------------------ |
| `ingress_list` | string | Name of the ingress list |

**Response:** `200 OK`

```json
["failed-uuid-1", "failed-uuid-2"]
```

**Example:**

```bash
curl -H "x-conserver-api-token: $TOKEN" \
  "http://localhost:8000/api/dlq?ingress_list=main_chain"
```

***

### Reprocess DLQ

```
POST /dlq/reprocess
```

Moves all items from a DLQ back to the original ingress list for reprocessing.

**Query Parameters:**

| Parameter      | Type   | Description              |
| -------------- | ------ | ------------------------ |
| `ingress_list` | string | Name of the ingress list |

**Response:** `200 OK`

```json
5  // Number of items moved
```

**Example:**

```bash
curl -X POST -H "x-conserver-api-token: $TOKEN" \
  "http://localhost:8000/api/dlq/reprocess?ingress_list=main_chain"
```

***

## Lifecycle

### Rebuild Search Index

```
GET /index_vcons
```

Rebuilds the search index for all vCons in Redis. Useful after bulk imports or to refresh expired indices.

**Response:** `200 OK`

```json
150  // Number of vCons indexed
```

***

## Redis Caching Behavior

When a vCon is requested but not found in Redis:

1. The API checks each configured storage backend
2. If found, the vCon is stored back in Redis with TTL (`VCON_REDIS_EXPIRY`, default 1 hour)
3. The vCon is added to the sorted set for timestamp-based retrieval
4. Subsequent requests are served from Redis

Configure caching with environment variables:

| Variable            | Description                 | Default            |
| ------------------- | --------------------------- | ------------------ |
| `VCON_REDIS_EXPIRY` | Redis cache TTL in seconds  | `3600` (1 hour)    |
| `VCON_INDEX_EXPIRY` | Search index TTL in seconds | `86400` (24 hours) |

***

## Error Responses

All endpoints return standard HTTP error codes:

| Code  | Description                                |
| ----- | ------------------------------------------ |
| `400` | Bad Request - Invalid parameters           |
| `403` | Forbidden - Invalid or missing API key     |
| `404` | Not Found - Resource doesn't exist         |
| `500` | Internal Server Error - Processing failure |

Error response format:

```json
{
  "detail": "Error message describing the issue"
}
```


# Integrating Your App

How an application talks to the Conserver — via the REST API for control, and via storage backends for read paths.

Most applications integrate with the Conserver in three places:

1. **Send vCons in** through the REST API (`POST /vcon` and `POST /vcon/ingress`).
2. **Get processed vCons out** by configuring a storage backend that your application can read (Postgres, MongoDB, S3, the [vCon MCP server](/mcp-server), etc.) or by configuring a [webhook](/conserver/standard-links#webhook) at the end of the chain.
3. **Operate** — health checks, queue depth, DLQ inspection, configuration changes — through the system endpoints and `/config` API.

<figure><img src="/files/Fo23sRuZHnTVOAB7RdTN" alt=""><figcaption><p>Application Integration with the Conserver</p></figcaption></figure>

## Two integration patterns

### Pattern A: API-only

Your application talks to the Conserver exclusively via the REST API:

```
your-app ──HTTP──▶ Conserver API ──▶ Redis ──▶ Workers ──▶ Storages
   ▲                                                          │
   └──────────── REST GET /vcon/{uuid} ◀──────────────────────┘
```

Use this when:

* Your application doesn't already have a database you'd want vCons in.
* You want a single source of truth for vCon access (the API enforces auth, logs reads, and orchestrates expiry).
* You're calling from many languages or platforms — REST is the lowest common denominator.

### Pattern B: Direct storage read, API-only writes

Your application reads vCons from a shared database that the Conserver also writes to. Writes still go through the API so the Conserver can index, queue, and track them.

```
your-app ──HTTP write──▶ Conserver API ──▶ Redis ──▶ Workers ──▶ Postgres / Mongo / S3
   ▲                                                                  │
   └────────────── direct read (SQL / Mongo / S3 GET) ◀───────────────┘
```

Use this when:

* You already have a Postgres / Mongo / S3 cluster and want vCons there too.
* You want to query vCons with native database tools (SQL joins, Mongo aggregation pipelines, etc.).
* You want change-data-capture: tap Mongo's oplog, Postgres logical replication, or S3 events instead of webhooks.

## Step-by-step lifecycle

1. **Create.** `POST /vcon` with the full vCon JSON. The vCon is written to Redis under the key `vcon:{uuid}` (Redis JSON). At this point it's stored but unprocessed.
2. **Queue.** `POST /vcon/ingress?ingress_list=<chain_name>` with the UUID. The chain's workers pick it up in FIFO order.
3. **Process.** The chain runs every configured link in sequence. Each link reads the vCon from Redis, modifies it, writes it back. If any link returns `None`, the chain stops for that vCon.
4. **Store.** After the last link, every configured storage runs in parallel (controlled by `CONSERVER_PARALLEL_STORAGE`). The vCon lands in your durable store.
5. **Egress (optional).** If the chain has `egress_lists` configured, the UUID is also pushed onto those lists. Downstream chains or external consumers can pop them via `GET /vcon/egress`.
6. **Expire.** The Redis copy expires per `VCON_REDIS_EXPIRY` (default 1 hour). Subsequent reads via `GET /vcon/{uuid}` fall back to storage and re-populate Redis.
7. **Audit (optional).** If [tracers](/conserver/conserver-tracers) are configured, every link execution is recorded — either via JLINC, DataTrails, SCITT, or your own custom tracer.

## A minimal Python client

```python
import requests

CONSERVER = "http://conserver.internal:8000/api"
TOKEN = "your-api-token"
HEADERS = {"x-conserver-api-token": TOKEN, "Content-Type": "application/json"}

def create_and_process(vcon_json):
    # 1. Create
    r = requests.post(f"{CONSERVER}/vcon", json=vcon_json, headers=HEADERS)
    r.raise_for_status()
    uuid = vcon_json["uuid"]

    # 2. Queue for processing
    requests.post(
        f"{CONSERVER}/vcon/ingress",
        json=[uuid],
        params={"ingress_list": "main_chain"},
        headers=HEADERS,
    ).raise_for_status()

    return uuid

def fetch(uuid):
    r = requests.get(f"{CONSERVER}/vcon/{uuid}", headers=HEADERS)
    r.raise_for_status()
    return r.json()
```

For the full endpoint list and parameters, see [API](/conserver/api).

## When to use webhooks vs storage-side change capture

| Need                                                           | Use                                                                               |
| -------------------------------------------------------------- | --------------------------------------------------------------------------------- |
| Notify a Slack channel when a vCon hits a chain                | [`post_analysis_to_slack` link](/conserver/standard-links#post_analysis_to_slack) |
| Push every finished vCon to an HTTP endpoint your app controls | [`webhook` link or storage](/conserver/standard-links#webhook)                    |
| React to vCons landing in Postgres                             | Postgres logical replication or `LISTEN/NOTIFY`                                   |
| React to vCons landing in Mongo                                | Mongo change streams (`db.collection.watch()`)                                    |
| React to vCons landing in S3                                   | S3 event notifications → SQS / Lambda / EventBridge                               |
| Subscribe to MCP-server changes                                | Use the [vCon MCP Server](/mcp-server) directly                                   |

## Security

* Use a separate API token per integrating system (`CONSERVER_API_TOKEN_FILE` accepts one per line). Rotate them on a schedule.
* For external partners, give them an `ingress_auth` scoped key. Those keys can only write to their assigned ingress list — they can't read, delete, or hit `/config`.
* Run the API tier behind a reverse proxy with TLS terminated outside the conserver container. See [Production Deployment](/conserver/production-deployment) for the nginx pattern.


# Conserver Tracers

Tracer Functionality in vCon Server

### Overview

Tracer functionality in the vCon Server provides a powerful mechanism for observability, auditing, and compliance tracking as vCons (virtual conversations) flow through processing chains. Unlike processing links that transform or analyze vCon data, tracers are non-intrusive monitoring components that observe and record data flow without modifying the vCon content itself.

### Key Concepts

#### What are Tracers?

Tracers are side-effect modules that execute at specific points in the vCon processing pipeline to:

* Monitor data flow between processing links
* Create audit trails for compliance and security
* Track provenance and data lineage
* Generate observability metrics without affecting the main processing flow

#### Tracers vs. Links

| Aspect            | Links                          | Tracers                             |
| ----------------- | ------------------------------ | ----------------------------------- |
| Purpose           | Transform/process vCon data    | Observe/monitor data flow           |
| Data Modification | Can modify vCon content        | Never modify vCon content           |
| Execution Timing  | Sequential in processing chain | Execute before/after each link      |
| Return Value      | Return vCon UUID for chaining  | Return boolean success status       |
| Failure Impact    | Can stop processing chain      | Failures don't stop main processing |

### Execution Model

#### Tracer Invocation Points

Tracers are executed at three critical points in the processing pipeline:

1. Before First Link (link\_index = -1)

* Executes when a vCon enters the processing chain
* Records initial state and metadata

2. After Each Link (link\_index = 0, 1, 2, ...)

* Executes after each processing link completes
* Captures data transformations and flow

3. Chain Completion

* Executes when the entire processing chain finishes
* Records final state and completion metrics

#### Execution Flow

```
# Simplified execution flow
for link_index, link_name in enumerate(links):
    if link_index == 0:
        # Execute tracers before first link
        _process_tracers(vcon_id, vcon_id, links, -1)
    
    # Execute the processing link
    result = execute_link(link_name, vcon_id)
    
    # Execute tracers after link completion
    _process_tracers(result, vcon_id, links, link_index)

```

### Available Tracer Modules

#### 1. JLINC Zero-Knowledge Auditing

The JLINC tracer provides cryptographic signing and zero-knowledge audit capabilities for tamper-proof data provenance.

**Features**

* Cryptographic Signing: Creates tamper-proof signatures for vCon data
* Zero-Knowledge Auditing: Enables secure third-party auditing without exposing sensitive data
* Entity Management: Automatically creates and manages JLINC entities for each processing stage
* Data Hashing: Optionally hash vCon data for privacy-preserving audit trails
* Archive Integration: Stores audit records in external archive systems

#### Configuration

```
tracers:
  jlinc:
    module: tracers.jlinc
    options:
      data_store_api_url: http://jlinc-server:9090
      data_store_api_key: your_data_store_api_key
      archive_api_url: http://jlinc-server:9090
      archive_api_key: your_archive_api_key
      system_prefix: VCONTest
      agreement_id: 00000000-0000-0000-0000-000000000000
      hash_event_data: True
      dlq_vcon_on_error: True
```

**How JLINC Tracer Works**

**Entity Creation**: Creates JLINC entities for each processing stage

* System entity: {system\_prefix}-system@{domain}
* Link entities: {system\_prefix}-{link\_name}@{domain}

**Event Processing**: For each vCon transition:

* Retrieves vCon data from Redis
* Creates sender/recipient entities based on link context
* Optionally hashes vCon data for privacy
* Sends event to JLINC API for cryptographic signing

**Audit Trail**: Creates immutable audit records with:

* Cryptographic signatures
* Data hashes (if enabled)
* Metadata (vCon UUIDs, link information)
* Timestamps and provenance information

### Configuration

#### Basic Tracer Configuration

```yaml
tracers:
  tracer_name:
    module: tracers.module_name
    options:
      # Tracer-specific configuration options
```

#### Multiple Tracers

You can configure multiple tracers to run simultaneously:

```yaml
tracers:
  jlinc_audit:
    module: tracers.jlinc
    options:
      # JLINC configuration
      
  compliance_logger:
    module: tracers.compliance
    options:
      # Compliance logging configuration
      
  metrics_collector:
    module: tracers.metrics
    options:
```

### Tracer Interface

#### Required Function Signature

All tracer modules must implement a run function with this signature:

```python
def run(
    in_vcon_uuid: str,      # Input vCon UUID
    out_vcon_uuid: str,     # Output vCon UUID  
    tracer_name: str,       # Name of this tracer instance
    links: list[str],       # List of all links in the chain
    link_index: int,        # Current link index (-1 for pre-chain)
    opts: dict = {}         # Tracer configuration options
) -> bool:
    """
    Execute tracer logic for a vCon processing step.
    
    Args:
        in_vcon_uuid: UUID of vCon entering the processing step
        out_vcon_uuid: UUID of vCon exiting the processing step
        tracer_name: Name of this tracer instance from config
        links: Complete list of links in the processing chain
        link_index: Index of current link (-1 for pre-chain execution)
        opts: Tracer-specific configuration options
        
    Returns:
        bool: True if tracer executed successfully, False otherwise
    """
```

#### Implementation Example

```python
from lib.logging_utils import init_logger
from lib.vcon_redis import VconRedis

logger = init_logger(__name__)

default_options = {
    "api_url": "http://example.com/api",
    "api_key": "",
    "enabled": True
}

def run(in_vcon_uuid, out_vcon_uuid, tracer_name, links, link_index, opts=default_options):
    """Example tracer implementation"""
    
    if not opts.get("enabled", True):
        logger.debug(f"Tracer {tracer_name} is disabled")
        return True
    
    try:
        # Get vCon data
        vcon_redis = VconRedis()
        vcon_obj = vcon_redis.get_vcon(out_vcon_uuid)
        
        if not vcon_obj:
            logger.error(f"Could not retrieve vCon {out_vcon_uuid}")
            return False
        
        # Process tracer logic
        logger.info(f"Executing {tracer_name} tracer for vCon {out_vcon_uuid}")
        
        # Your tracer logic here
        # - Send data to external systems
        # - Create audit records
        # - Generate metrics
        # - Log compliance information
        
        return True
        
    except Exception as e:
        logger.error(f"Tracer {tracer_name} failed: {e}")
        return False
        
        
```

### Use Cases

#### 1. Compliance and Auditing

* GDPR Compliance: Track data processing for privacy regulations
* SOX Compliance: Audit financial conversation processing
* HIPAA Compliance: Monitor healthcare conversation handling

#### 2. Security and Integrity

* Data Provenance: Track data lineage and transformations
* Tamper Detection: Cryptographic verification of data integrity
* Access Logging: Record who accessed what data when

#### 3. Observability and Monitoring

* Performance Metrics: Track processing times and throughput
* Error Tracking: Monitor failures and exceptions
* Business Metrics: Count conversations, analyze patterns

#### 4. Data Governance

* Retention Tracking: Monitor data lifecycle and expiration
* Data Classification: Track sensitive data handling
* Cross-Border Transfers: Monitor international data flows

### Best Practices

#### 1. Non-Blocking Design

* Tracers should never block the main processing flow
* Handle errors gracefully without affecting vCon processing
* Use asynchronous operations where possible

#### 2. Performance Considerations

* Keep tracer execution time minimal
* Cache frequently accessed data
* Use efficient data serialization

#### 3. Error Handling

* Log errors but don't raise exceptions
* Return boolean status for success/failure
* Implement retry logic for external API calls

#### 4. Privacy and Security

* Hash or encrypt sensitive data before external transmission
* Follow data minimization principles
* Implement proper authentication and authorization

#### 5. Configuration Management

* Provide sensible defaults
* Validate configuration options
* Support environment-specific settings

### Monitoring and Debugging

#### Logging

Tracers automatically log their execution with structured logging:

```python
logger.info(
    "Completed tracer %s (module: %s) for vCon: %s in %s seconds",
    tracer_name,
    tracer_module_name,
    out_vcon_uuid,
    tracer_processing_time,
    extra={
        "tracer_processing_time": tracer_processing_time,
        "tracer_name": tracer_name,
        "tracer_module_name": tracer_module_name
    }
)
```

#### Metrics

Tracer execution is automatically tracked with:

* Processing time per tracer
* Success/failure rates
* vCon throughput metrics

#### Debugging

* Enable debug logging for detailed tracer execution
* Use tracer-specific configuration for testing
* Monitor external API responses and errors

### Future Extensions

The tracer system is designed to be extensible. Potential future tracer modules could include:

* DataTrails Integration: Blockchain-based audit trails
* SIEM Integration: Security information and event management
* Custom Analytics: Business intelligence and reporting
* Data Loss Prevention: Monitor for sensitive data exposure
* Performance Profiling: Detailed performance analysis

### Conclusion

Tracer functionality provides a powerful, non-intrusive way to add observability, compliance, and security monitoring to vCon processing pipelines. By executing alongside the main processing flow without affecting it, tracers enable comprehensive data governance and audit capabilities while maintaining system performance and reliability.The modular design allows for easy extension with custom tracer implementations, making it possible to integrate with any external system or compliance framework while maintaining the core principle of non-interference with vCon processing.


# Creating Custom Links

How to create custom processing links

Links are the modular processing units of the Conserver. This guide explains how to create your own custom links to extend the system's functionality.

## Link Interface

Every link must implement a `run` function with this signature:

```python
def run(
    vcon_uuid: str,
    link_name: str,
    opts: dict = default_options
) -> str | None:
    """
    Process a vCon through this link.

    Args:
        vcon_uuid: UUID of the vCon to process
        link_name: Name of this link instance in config
        opts: Configuration options merged with defaults

    Returns:
        str: vCon UUID to continue chain (usually same as input)
        None: Stop chain processing for this vCon
    """
```

## Basic Link Template

```python
from lib.logging_utils import init_logger
from lib.vcon_redis import VconRedis

# Initialize logger
logger = init_logger(__name__)

# Default options - these can be overridden in config
default_options = {
    "enabled": True,
    "api_key": "",
    "timeout": 30,
}


def run(vcon_uuid, link_name, opts=default_options):
    """Main entry point for the link."""

    # Merge config options with defaults
    merged_opts = default_options.copy()
    merged_opts.update(opts)
    opts = merged_opts

    logger.info(f"Starting {link_name} for vCon: {vcon_uuid}")

    # Check if link is enabled
    if not opts.get("enabled", True):
        logger.info(f"Link {link_name} is disabled, skipping")
        return vcon_uuid

    try:
        # Get vCon from Redis
        vcon_redis = VconRedis()
        vcon = vcon_redis.get_vcon(vcon_uuid)

        if not vcon:
            logger.error(f"vCon not found: {vcon_uuid}")
            return None  # Stop chain processing

        # ========================================
        # YOUR PROCESSING LOGIC HERE
        # ========================================

        # Example: Add an analysis
        vcon.add_analysis(
            type="my_analysis",
            dialog=0,
            vendor="my_company",
            body={"result": "processed"},
        )

        # Example: Add a tag
        vcon.add_tag(tag_name="processed_by", tag_value=link_name)

        # ========================================
        # END PROCESSING LOGIC
        # ========================================

        # Save updated vCon back to Redis
        vcon_redis.store_vcon(vcon)

        logger.info(f"Completed {link_name} for vCon: {vcon_uuid}")
        return vcon_uuid  # Continue chain

    except Exception as e:
        logger.error(f"Error in {link_name}: {e}", exc_info=True)
        raise  # Re-raise to move vCon to DLQ
```

## Working with vCon Objects

### Reading vCon Data

```python
# Get the vCon object
vcon_redis = VconRedis()
vcon = vcon_redis.get_vcon(vcon_uuid)

# Access vCon properties
print(vcon.uuid)        # vCon UUID
print(vcon.created_at)  # Creation timestamp
print(vcon.parties)     # List of parties
print(vcon.dialog)      # List of dialog entries
print(vcon.analysis)    # List of analysis results
print(vcon.attachments) # List of attachments
print(vcon.tags)        # Dictionary of tags
```

### Processing Dialogs

```python
for index, dialog in enumerate(vcon.dialog):
    # Check dialog type
    if dialog["type"] != "recording":
        logger.info(f"Skipping non-recording dialog {index}")
        continue

    # Check for URL
    if not dialog.get("url"):
        logger.info(f"Dialog {index} has no URL")
        continue

    # Get duration
    duration = dialog.get("duration", 0)
    if duration < 30:
        logger.info(f"Skipping short dialog {index}")
        continue

    # Process the dialog
    result = process_audio(dialog["url"])

    # Add analysis result
    vcon.add_analysis(
        type="my_analysis",
        dialog=index,
        vendor="my_vendor",
        body=result,
    )
```

### Checking Existing Analysis

```python
def get_analysis_for_type(vcon, dialog_index, analysis_type):
    """Check if analysis already exists for a dialog."""
    for analysis in vcon.analysis:
        if (analysis.get("dialog") == dialog_index and
            analysis.get("type") == analysis_type):
            return analysis
    return None

# Skip if already processed
if get_analysis_for_type(vcon, index, "my_analysis"):
    logger.info(f"Dialog {index} already has my_analysis")
    continue
```

### Adding Results

```python
# Add analysis
vcon.add_analysis(
    type="summary",           # Analysis type
    dialog=0,                 # Dialog index
    vendor="openai",          # Vendor name
    body="Summary text...",   # Analysis content
    encoding="none",          # Encoding (none, json, base64)
    extra={                   # Additional metadata
        "model": "gpt-4",
        "prompt": "Summarize this",
    },
)

# Add tag
vcon.add_tag(tag_name="category", tag_value="sales")

# Add attachment
vcon.add_attachment(
    type="application/json",
    body={"key": "value"},
    encoding="json",
)
```

## Filtering and Routing

### Stopping Chain Processing

Return `None` to stop processing for this vCon:

```python
def run(vcon_uuid, link_name, opts=default_options):
    vcon_redis = VconRedis()
    vcon = vcon_redis.get_vcon(vcon_uuid)

    # Filter condition
    if "do_not_process" in vcon.tags:
        logger.info(f"Filtering out vCon {vcon_uuid}")
        return None  # Stop chain processing

    # Continue processing
    return vcon_uuid
```

### Routing to Different Queues

```python
import redis

def run(vcon_uuid, link_name, opts=default_options):
    vcon_redis = VconRedis()
    vcon = vcon_redis.get_vcon(vcon_uuid)

    # Determine destination based on tags
    if vcon.tags.get("priority") == "high":
        destination = "priority_queue"
    else:
        destination = "normal_queue"

    # Add to destination queue
    r = redis.Redis.from_url(os.getenv("REDIS_URL"))
    r.rpush(destination, vcon_uuid)

    logger.info(f"Routed {vcon_uuid} to {destination}")

    return None  # Stop current chain (routed elsewhere)
```

## External API Integration

### With Retry Logic

```python
from tenacity import (
    retry,
    stop_after_attempt,
    wait_exponential,
    before_sleep_log,
)

@retry(
    wait=wait_exponential(multiplier=2, min=1, max=60),
    stop=stop_after_attempt(5),
    before_sleep=before_sleep_log(logger, logging.INFO),
)
def call_external_api(url, data, api_key):
    """Call external API with retries."""
    response = requests.post(
        url,
        json=data,
        headers={"Authorization": f"Bearer {api_key}"},
        timeout=30,
    )
    response.raise_for_status()
    return response.json()


def run(vcon_uuid, link_name, opts=default_options):
    vcon_redis = VconRedis()
    vcon = vcon_redis.get_vcon(vcon_uuid)

    try:
        result = call_external_api(
            opts["api_url"],
            {"vcon": vcon.to_json()},
            opts["api_key"],
        )

        vcon.add_analysis(
            type="external_analysis",
            dialog=0,
            vendor="external_service",
            body=result,
        )
        vcon_redis.store_vcon(vcon)

    except Exception as e:
        logger.error(f"API call failed after retries: {e}")
        raise

    return vcon_uuid
```

## Metrics and Monitoring

```python
from lib.metrics import init_metrics, stats_gauge, stats_count
import time

init_metrics()

def run(vcon_uuid, link_name, opts=default_options):
    start_time = time.time()

    try:
        # Your processing logic
        result = process_vcon(vcon_uuid)

        # Track success
        stats_count(
            "conserver.link.my_link.success",
            tags=[f"link:{link_name}"],
        )

        return vcon_uuid

    except Exception as e:
        # Track failure
        stats_count(
            "conserver.link.my_link.failure",
            tags=[f"link:{link_name}", f"error:{type(e).__name__}"],
        )
        raise

    finally:
        # Track processing time
        elapsed = time.time() - start_time
        stats_gauge(
            "conserver.link.my_link.processing_time",
            elapsed,
            tags=[f"link:{link_name}"],
        )
```

## Project Structure

Organize your link as a Python package:

```
my_custom_link/
├── __init__.py      # Contains run() function
├── utils.py         # Helper functions
├── models.py        # Data models
└── tests/
    ├── __init__.py
    └── test_link.py
```

### `__init__.py`

```python
from lib.logging_utils import init_logger
from lib.vcon_redis import VconRedis
from .utils import process_transcript

logger = init_logger(__name__)

default_options = {
    "model": "default",
    "threshold": 0.5,
}

def run(vcon_uuid, link_name, opts=default_options):
    # Implementation using utils
    ...
```

## Configuration

Configure your link in `config.yml`:

```yaml
links:
  my_custom_link:
    module: links.my_custom_link
    options:
      api_key: ${MY_API_KEY}
      model: "advanced"
      threshold: 0.7
      enabled: true

chains:
  main:
    links:
      - transcribe
      - my_custom_link  # Your link
      - analyze
    # ...
```

## Distribution

### As a PyPI Package

```toml
# pyproject.toml
[project]
name = "conserver-my-link"
version = "1.0.0"
dependencies = [
    "requests>=2.28.0",
]

[project.entry-points."conserver.links"]
my_link = "my_link:run"
```

### As a GitHub Repository

Reference directly in config:

```yaml
imports:
  my_custom_link:
    module: my_custom_link
    pip_name: git+https://github.com/myorg/my-link.git@v1.0.0

links:
  my_link:
    module: my_custom_link
    options:
      api_key: ${API_KEY}
```

## Testing

```python
import pytest
from unittest.mock import Mock, patch

def test_link_processes_vcon():
    # Mock VconRedis
    with patch('my_link.VconRedis') as mock_redis:
        mock_vcon = Mock()
        mock_vcon.dialog = [{"type": "recording", "url": "http://..."}]
        mock_vcon.analysis = []
        mock_redis.return_value.get_vcon.return_value = mock_vcon

        # Run link
        from my_link import run
        result = run("test-uuid", "my_link", {"enabled": True})

        # Verify
        assert result == "test-uuid"
        mock_vcon.add_analysis.assert_called_once()
        mock_redis.return_value.store_vcon.assert_called_once()


def test_link_filters_when_disabled():
    with patch('my_link.VconRedis') as mock_redis:
        from my_link import run
        result = run("test-uuid", "my_link", {"enabled": False})

        assert result == "test-uuid"
        mock_redis.return_value.get_vcon.assert_not_called()
```

## Best Practices

1. **Always merge options with defaults** - Ensures your link works even with partial configuration
2. **Check if processing already done** - Avoid reprocessing if analysis already exists
3. **Use structured logging** - Include vCon UUID and link name in all log messages
4. **Handle errors appropriately** - Raise exceptions to send vCons to DLQ, or return UUID to continue
5. **Add metrics** - Track processing time, success/failure rates
6. **Document your options** - Make it clear what configuration your link accepts
7. **Test thoroughly** - Unit test your processing logic
8. **Be idempotent** - Running twice should produce the same result


# Production Deployment

Deploying the Conserver in Production

This guide covers deploying the Conserver in production environments with considerations for scalability, reliability, and security.

## Prerequisites

* Docker and Docker Compose
* Redis server (or Redis cluster for high availability)
* Storage backends configured (PostgreSQL, S3, etc.)
* Domain name and TLS certificates
* Monitoring infrastructure (optional but recommended)

## Image strategy: two Dockerfiles

As of the May 2026 image optimization (`docker/Dockerfile.api` and `docker/Dockerfile.conserver`), the conserver ships **two separate images**:

| Image                      | What it contains                                                                 | Use it for                                                                       |
| -------------------------- | -------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
| **`Dockerfile.api`**       | FastAPI app, storage backends, vCon library — no audio/ML stack                  | The API tier. Light, starts fast, scales horizontally.                           |
| **`Dockerfile.conserver`** | Everything in the API image *plus* transformers, openai, deepgram, ffmpeg, pydub | The worker tier. Heavy but only needed where audio processing and LLM calls run. |

Both images use [**uv**](https://github.com/astral-sh/uv) (Astral's Python package manager) for reproducible builds and place the virtualenv at `/opt/venv` so it survives volume mounts.

In production, deploy the API image for the `conserver-api` service and the conserver image for the `conserver-worker` service. The example below does exactly that.

## Architecture Overview

```
                    ┌─────────────────┐
                    │   Load Balancer │
                    │    (nginx/ALB)  │
                    └────────┬────────┘
                             │
         ┌───────────────────┼───────────────────┐
         │                   │                   │
    ┌────▼────┐        ┌────▼────┐        ┌────▼────┐
    │Conserver│        │Conserver│        │Conserver│
    │   API   │        │   API   │        │   API   │
    └────┬────┘        └────┬────┘        └────┬────┘
         │                   │                   │
         └───────────────────┼───────────────────┘
                             │
                    ┌────────▼────────┐
                    │      Redis      │
                    │  (Queues/Cache) │
                    └────────┬────────┘
                             │
         ┌───────────────────┼───────────────────┐
         │                   │                   │
    ┌────▼────┐        ┌────▼────┐        ┌────▼────┐
    │PostgreSQL│        │   S3    │        │ Milvus  │
    └─────────┘        └─────────┘        └─────────┘
```

## Docker Compose Production Setup

### docker-compose.yml

```yaml
version: '3.8'

services:
  conserver-api:
    image: your-registry/conserver-api:latest    # built from docker/Dockerfile.api
    command: uvicorn api:app --host 0.0.0.0 --port 8000
    deploy:
      replicas: 3
      resources:
        limits:
          cpus: '2'
          memory: 4G
        reservations:
          cpus: '1'
          memory: 2G
    environment:
      - REDIS_URL=redis://redis:6379
      - CONSERVER_CONFIG_FILE=/app/config.yml
      - CONSERVER_API_TOKEN_FILE=/run/secrets/api_tokens
      - LOG_LEVEL=INFO
      - ENV=production
    volumes:
      - ./config.yml:/app/config.yml:ro
    secrets:
      - api_tokens
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s
    depends_on:
      redis:
        condition: service_healthy

  conserver-worker:
    image: your-registry/conserver:latest        # built from docker/Dockerfile.conserver
    command: python main.py
    deploy:
      replicas: 5
      resources:
        limits:
          cpus: '4'
          memory: 8G
        reservations:
          cpus: '2'
          memory: 4G
    environment:
      - REDIS_URL=redis://redis:6379
      - CONSERVER_CONFIG_FILE=/app/config.yml
      - LOG_LEVEL=INFO
      - ENV=production
      - CONSERVER_WORKERS=4              # forked worker processes per container
      - CONSERVER_PARALLEL_STORAGE=true  # write to storages concurrently
      - OPENAI_API_KEY_FILE=/run/secrets/openai_key
      - DEEPGRAM_KEY_FILE=/run/secrets/deepgram_key
    volumes:
      - ./config.yml:/app/config.yml:ro
    secrets:
      - openai_key
      - deepgram_key
    depends_on:
      redis:
        condition: service_healthy

  redis:
    image: redis:7-alpine
    command: redis-server --appendonly yes --maxmemory 2gb --maxmemory-policy allkeys-lru
    volumes:
      - redis_data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5

  nginx:
    image: nginx:alpine
    ports:
      - "443:443"
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./certs:/etc/nginx/certs:ro
    depends_on:
      - conserver-api

volumes:
  redis_data:

secrets:
  api_tokens:
    file: ./secrets/api_tokens.txt
  openai_key:
    file: ./secrets/openai_key.txt
  deepgram_key:
    file: ./secrets/deepgram_key.txt
```

### nginx.conf

```nginx
upstream conserver {
    least_conn;
    server conserver-api:8000;
}

server {
    listen 80;
    server_name your-domain.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name your-domain.com;

    ssl_certificate /etc/nginx/certs/fullchain.pem;
    ssl_certificate_key /etc/nginx/certs/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
    ssl_prefer_server_ciphers off;

    # Security headers
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;

    # Rate limiting
    limit_req_zone $binary_remote_addr zone=api:10m rate=100r/s;

    location /api/ {
        limit_req zone=api burst=200 nodelay;

        proxy_pass http://conserver;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # Timeouts for long-running requests
        proxy_connect_timeout 60s;
        proxy_send_timeout 300s;
        proxy_read_timeout 300s;

        # Buffer settings
        proxy_buffer_size 128k;
        proxy_buffers 4 256k;
        proxy_busy_buffers_size 256k;
    }

    # Health check endpoint (no auth required)
    location /health {
        proxy_pass http://conserver/health;
        proxy_http_version 1.1;
    }
}
```

***

## Scaling Considerations

### Horizontal Scaling

The Conserver supports horizontal scaling because:

* All state is stored in Redis
* Multiple instances can process from the same queues
* API requests are stateless

Scale workers based on queue depth:

```bash
# Monitor queue depth
redis-cli LLEN incoming_calls

# Scale workers
docker compose up -d --scale conserver-worker=10
```

### Redis Configuration

For production Redis deployments:

```conf
# redis.conf
maxmemory 4gb
maxmemory-policy allkeys-lru
appendonly yes
appendfsync everysec

# For Redis Cluster
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
```

### Queue Monitoring

Monitor queue lengths to detect backlogs:

```python
import redis

r = redis.Redis(host='localhost', port=6379)

# Check ingress queue depth
queue_length = r.llen('incoming_calls')
print(f"Queue depth: {queue_length}")

# Check DLQ for failures
dlq_length = r.llen('incoming_calls:dlq')
print(f"DLQ depth: {dlq_length}")
```

***

## Security Hardening

### API Token Management

1. **Use token files instead of environment variables:**

   ```yaml
   environment:
     - CONSERVER_API_TOKEN_FILE=/run/secrets/api_tokens
   ```
2. **Rotate tokens regularly:**

   ```bash
   # Generate new token
   openssl rand -hex 32 > secrets/api_tokens.txt

   # Restart API containers
   docker compose restart conserver-api
   ```
3. **Use separate tokens for different purposes:**
   * Internal API token for system operations
   * Partner-specific tokens via `ingress_auth`

### Network Security

1. **Isolate Redis:**

   ```yaml
   networks:
     internal:
       internal: true
     external:

   services:
     redis:
       networks:
         - internal
     conserver-api:
       networks:
         - internal
         - external
   ```
2. **Enable Redis AUTH:**

   ```yaml
   redis:
     command: redis-server --requirepass ${REDIS_PASSWORD}
   ```
3. **Use TLS for external connections**

### Secret Management

Consider using:

* Docker secrets (as shown above)
* HashiCorp Vault
* AWS Secrets Manager
* Kubernetes secrets

***

## Monitoring and Observability

### Health Checks

The Conserver exposes three public system endpoints (no auth required) for monitoring:

```bash
# Health check — basic up/down + version
curl http://localhost:8000/health

# Build metadata — version, git commit, build time
curl http://localhost:8000/version

# Queue depth (point load balancers / autoscalers at this)
curl "http://localhost:8000/stats/queue?list_name=incoming_calls"

# Redis liveness
redis-cli ping
```

These endpoints intentionally live at the application root (not under `API_ROOT_PATH`), so they're stable regardless of how you've configured the API prefix.

### Metrics Integration

Every standard link and storage emits OpenTelemetry spans and metrics (latency, errors, cache hits where applicable). Wire up an OTLP collector — see [vCon MCP Adapters](/tools/vcon-mcp-adapters) for a turnkey integration — and the conserver will populate your dashboards out of the box.

Per-link metrics include:

```python
# Automatic OTEL attributes per link:
# - link_name
# - vcon_uuid (when relevant)
# - status (ok | error | skipped)
# - duration_ms
# - cache_hit (for transcription links with caching)
# - Storage operation latency
```

### Log Aggregation

Configure structured JSON logging:

```yaml
environment:
  - LOG_LEVEL=INFO
  - LOG_FORMAT=json
```

Logs include:

* Request IDs for tracing
* Processing times
* Error details with stack traces
* vCon UUIDs for correlation

### Alerting

Set up alerts for:

| Metric               | Threshold | Action               |
| -------------------- | --------- | -------------------- |
| Queue depth > 1000   | Warning   | Scale workers        |
| DLQ depth > 100      | Critical  | Investigate failures |
| API latency p99 > 5s | Warning   | Check resources      |
| Error rate > 5%      | Critical  | Check logs           |

***

## Graceful Shutdown

The Conserver handles SIGTERM for graceful shutdown:

1. Stops accepting new vCons
2. Completes in-flight processing
3. Returns unprocessed items to queues
4. Closes connections cleanly

Configure Docker stop timeout:

```yaml
services:
  conserver-worker:
    stop_grace_period: 5m  # Allow time for long transcriptions
```

***

## Backup and Recovery

### Redis Persistence

Enable AOF for durability:

```yaml
redis:
  command: redis-server --appendonly yes
  volumes:
    - redis_data:/data
```

### Backup Strategy

1. **Redis RDB snapshots:**

   ```bash
   redis-cli BGSAVE
   ```
2. **Storage backend backups:**
   * PostgreSQL: pg\_dump
   * S3: Enable versioning
   * Elasticsearch: Snapshot API
3. **Configuration backup:**

   ```bash
   # Backup config via API
   curl -H "x-conserver-api-token: $TOKEN" \
     http://localhost:8000/api/config > config_backup.json
   ```

### Disaster Recovery

1. Deploy Redis with persistence
2. Use storage backends with replication
3. Keep configuration in version control
4. Document recovery procedures

***

## Deployment Checklist

### Pre-deployment

* [ ] Configure TLS certificates
* [ ] Set up API tokens securely
* [ ] Configure storage backends
* [ ] Test configuration locally
* [ ] Set up monitoring/alerting
* [ ] Document rollback procedures

### Deployment

* [ ] Deploy Redis first
* [ ] Deploy workers
* [ ] Deploy API servers
* [ ] Verify health checks pass
* [ ] Test sample vCon processing

### Post-deployment

* [ ] Monitor queue depths
* [ ] Check error rates
* [ ] Verify storage writes
* [ ] Test API endpoints
* [ ] Confirm metrics flowing

***

## Troubleshooting

### Common Issues

**Workers not processing:**

```bash
# Check Redis connectivity
docker exec conserver-worker redis-cli -h redis ping

# Check queue contents
docker exec conserver-worker redis-cli -h redis LRANGE incoming_calls 0 10
```

**High DLQ count:**

```bash
# Check DLQ contents
curl -H "x-conserver-api-token: $TOKEN" \
  "http://localhost:8000/api/dlq?ingress_list=incoming_calls"

# Reprocess after fixing issues
curl -X POST -H "x-conserver-api-token: $TOKEN" \
  "http://localhost:8000/api/dlq/reprocess?ingress_list=incoming_calls"
```

**Memory issues:**

```bash
# Check Redis memory
redis-cli INFO memory

# Check container memory
docker stats
```

See [Troubleshooting](/conserver/troubleshooting) for more detailed solutions.


# Troubleshooting

Common issues and solutions

This guide covers common issues when running the Conserver and how to resolve them.

## Diagnostic Commands

### Check System Health

```bash
# Redis connectivity
redis-cli ping
# Expected: PONG

# API health
curl -H "x-conserver-api-token: $TOKEN" http://localhost:8000/api/config

# Queue status
redis-cli LLEN incoming_calls
redis-cli LLEN incoming_calls:dlq
```

### View Logs

```bash
# Docker Compose logs
docker compose logs -f conserver-api
docker compose logs -f conserver-worker

# Filter for errors
docker compose logs conserver-worker 2>&1 | grep -i error
```

***

## Connection Issues

### Redis Connection Failed

**Symptoms:**

* Workers won't start
* "Connection refused" errors
* API returns 500 errors

**Diagnosis:**

```bash
# Check Redis is running
docker compose ps redis

# Check Redis connectivity
redis-cli -h localhost -p 6379 ping

# Check Redis logs
docker compose logs redis
```

**Solutions:**

1. **Verify Redis URL:**

   ```bash
   # In .env
   REDIS_URL=redis://localhost:6379

   # For Docker networking
   REDIS_URL=redis://redis:6379
   ```
2. **Check Docker networking:**

   ```bash
   docker network ls
   docker network inspect vcon-server_default
   ```
3. **Redis memory full:**

   ```bash
   redis-cli INFO memory
   # If used_memory > maxmemory, increase or clear old data
   ```

### API Authentication Failed

**Symptoms:**

* 403 Forbidden responses
* "Invalid API Key" errors

**Solutions:**

1. **Check token configuration:**

   ```bash
   # Verify environment variable
   echo $CONSERVER_API_TOKEN

   # Check token file exists
   cat /path/to/api_tokens.txt
   ```
2. **Check header name:**

   ```bash
   # Default header
   curl -H "x-conserver-api-token: $TOKEN" ...

   # Custom header (if CONSERVER_HEADER_NAME is set)
   curl -H "x-custom-header: $TOKEN" ...
   ```
3. **For external ingress, check ingress\_auth config:**

   ```yaml
   ingress_auth:
     my_ingress_list: "correct-api-key"
   ```

***

## Processing Issues

### vCons Not Being Processed

**Symptoms:**

* Queue depth keeps growing
* No worker activity in logs
* vCons stuck in ingress list

**Diagnosis:**

```bash
# Check queue depth
redis-cli LLEN incoming_calls

# Check workers are running
docker compose ps

# Check worker logs
docker compose logs conserver-worker --tail 100
```

**Solutions:**

1. **Chain not enabled:**

   ```yaml
   chains:
     main:
       enabled: 1  # Must be 1, not 0 or false
   ```
2. **Ingress list mismatch:**

   ```yaml
   chains:
     main:
       ingress_lists:
         - incoming_calls  # Must match where vCons are pushed
   ```
3. **Workers crashed:**

   ```bash
   docker compose restart conserver-worker
   ```
4. **Configuration not loaded:**

   ```bash
   # Check current config
   curl -H "x-conserver-api-token: $TOKEN" http://localhost:8000/api/config
   ```

### High DLQ Count

**Symptoms:**

* vCons accumulating in dead letter queue
* Processing errors in logs

**Diagnosis:**

```bash
# Check DLQ depth
redis-cli LLEN incoming_calls:dlq

# Get DLQ contents
curl -H "x-conserver-api-token: $TOKEN" \
  "http://localhost:8000/api/dlq?ingress_list=incoming_calls"

# Check recent errors
docker compose logs conserver-worker 2>&1 | grep -i error | tail 50
```

**Solutions:**

1. **Identify the failing link:**
   * Check logs for which link is failing
   * Look for exceptions and stack traces
2. **Common link failures:**

   **API key missing/invalid:**

   ```yaml
   links:
     analyze:
       options:
         OPENAI_API_KEY: ${OPENAI_API_KEY}  # Check env var is set
   ```

   **External service down:**

   ```bash
   # Test connectivity
   curl https://api.openai.com/v1/models -H "Authorization: Bearer $OPENAI_API_KEY"
   ```

   **Timeout:**

   ```yaml
   chains:
     main:
       timeout: 600  # Increase for long transcriptions
   ```
3. **Reprocess after fixing:**

   ```bash
   curl -X POST -H "x-conserver-api-token: $TOKEN" \
     "http://localhost:8000/api/dlq/reprocess?ingress_list=incoming_calls"
   ```

### Link Timeout

**Symptoms:**

* "Processing timeout" errors
* vCons moving to DLQ after timeout period

**Solutions:**

1. **Increase chain timeout:**

   ```yaml
   chains:
     main:
       timeout: 600  # Seconds
   ```
2. **Optimize processing:**
   * Use faster models
   * Skip unnecessary processing steps
   * Add sampling to process fewer vCons
3. **Check external API performance:**

   ```bash
   # Time an API call
   time curl https://api.example.com/endpoint
   ```

***

## Transcription Issues

### No Transcription Generated

**Symptoms:**

* vCon has no transcript analysis
* Transcription link completes but adds nothing

**Diagnosis:**

```bash
# Check vCon dialogs
curl -H "x-conserver-api-token: $TOKEN" \
  "http://localhost:8000/api/vcon/$VCON_UUID" | jq '.dialog'
```

**Solutions:**

1. **Check dialog type:**

   ```json
   // Dialog must be type "recording"
   {"type": "recording", "url": "https://..."}
   ```
2. **Check duration requirement:**

   ```yaml
   links:
     transcribe:
       options:
         minimum_duration: 30  # Audio must be >= 30 seconds
   ```
3. **Check audio URL is accessible:**

   ```bash
   curl -I "https://your-audio-url.mp3"
   ```
4. **Check API key:**

   ```yaml
   links:
     deepgram:
       options:
         DEEPGRAM_KEY: ${DEEPGRAM_KEY}  # Verify env var
   ```

### Transcription Already Exists

**Symptoms:**

* "Dialog already transcribed" in logs
* Link skipping dialogs

**Explanation:** Links skip processing if analysis already exists (idempotency).

**If you need to re-transcribe:**

1. Delete the vCon and re-submit
2. Or create a new link with different `analysis_type`

***

## Storage Issues

### Storage Write Failed

**Symptoms:**

* "Failed to save" errors
* vCon not appearing in storage backend

**Diagnosis:**

```bash
# Check storage backend connectivity
# For PostgreSQL:
psql -h localhost -U postgres -d vcons -c "SELECT 1"

# For S3:
aws s3 ls s3://your-bucket/
```

**Solutions:**

1. **Check credentials:**

   ```yaml
   storages:
     postgres:
       options:
         password: ${POSTGRES_PASSWORD}  # Verify env var
   ```
2. **Check network connectivity:**

   ```bash
   # From container
   docker exec conserver-worker ping postgres
   ```
3. **Check permissions:**
   * Database user has write permissions
   * S3 IAM role allows PutObject

### vCon Not Found

**Symptoms:**

* 404 when fetching vCon
* "vCon not found" errors

**Diagnosis:**

```bash
# Check Redis
redis-cli EXISTS vcon:$VCON_UUID

# Check sorted set
redis-cli ZSCORE vcons vcon:$VCON_UUID
```

**Solutions:**

1. **vCon expired from Redis:**
   * Check `VCON_REDIS_EXPIRY` setting
   * vCon should be auto-fetched from storage if configured
2. **vCon never stored:**
   * Check chain has storage backends configured
   * Check storage write didn't fail
3. **Wrong UUID format:**
   * Ensure UUID includes hyphens
   * Check for typos

***

## Configuration Issues

### Configuration Not Loading

**Symptoms:**

* Changes to config.yml not taking effect
* Wrong settings being used

**Solutions:**

1. **Check config file path:**

   ```bash
   echo $CONSERVER_CONFIG_FILE
   ```
2. **Validate YAML syntax:**

   ```bash
   python -c "import yaml; yaml.safe_load(open('config.yml'))"
   ```
3. **Restart workers:**

   ```bash
   docker compose restart conserver-worker
   ```
4. **Use API to update:**

   ```bash
   curl -X POST -H "x-conserver-api-token: $TOKEN" \
     -H "Content-Type: application/json" \
     -d @config.json \
     http://localhost:8000/api/config
   ```

### Import/Module Not Found

**Symptoms:**

* "ModuleNotFoundError" in logs
* Dynamic imports failing

**Solutions:**

1. **Check imports section:**

   ```yaml
   imports:
     my_module:
       module: my_module
       pip_name: my-package>=1.0.0  # Correct package name
   ```
2. **Check pip\_name format:**

   ```yaml
   # PyPI
   pip_name: package-name>=1.0.0

   # GitHub
   pip_name: git+https://github.com/org/repo.git@tag
   ```
3. **Check network access:**
   * Container can reach PyPI/GitHub
   * No firewall blocking

***

## Performance Issues

### Slow Processing

**Symptoms:**

* High processing times
* Queue backing up

**Solutions:**

1. **Scale workers:**

   ```bash
   docker compose up -d --scale conserver-worker=5
   ```
2. **Use sampling:**

   ```yaml
   links:
     analyze:
       options:
         sampling_rate: 0.1  # Process 10%
   ```
3. **Use faster models:**

   ```yaml
   links:
     analyze:
       options:
         model: "gpt-3.5-turbo"  # Faster than gpt-4
   ```
4. **Skip unnecessary links:**

   ```yaml
   chains:
     fast_chain:
       links:
         - transcribe
         # Skip heavy analysis for speed
   ```

### Memory Issues

**Symptoms:**

* OOM errors
* Container restarts

**Solutions:**

1. **Increase container memory:**

   ```yaml
   services:
     conserver-worker:
       deploy:
         resources:
           limits:
             memory: 8G
   ```
2. **Configure Redis maxmemory:**

   ```bash
   redis-cli CONFIG SET maxmemory 2gb
   redis-cli CONFIG SET maxmemory-policy allkeys-lru
   ```
3. **Use diet link to reduce vCon size:**

   ```yaml
   links:
     slim_down:
       module: links.diet
       options:
         remove_dialog_bodies: true
   ```

***

## Getting Help

If you can't resolve an issue:

1. **Check logs carefully** - Most errors have clear messages
2. **Search GitHub issues** - Someone may have had the same problem
3. **Create a GitHub issue** with:
   * Conserver version
   * Configuration (sanitized)
   * Error messages
   * Steps to reproduce


# MCP Server

The vCon MCP Server — an open-source Model Context Protocol server that gives AI assistants standardized access to vCon conversation data.

The vCon MCP Server is the canonical way for AI assistants — Claude, Cursor, custom agents — to read, write, and reason about vCon conversation data. It speaks the [Model Context Protocol](https://modelcontextprotocol.io) over stdio or Streamable HTTP, exposes **37 tools** grouped into seven categories, and is backed by a Supabase Postgres deployment with optional Redis caching and pgvector-based semantic search.

The code lives at [vcon-dev/vcon-mcp](https://github.com/vcon-dev/vcon-mcp). It's MIT-licensed, TypeScript, and ships as both an npm package (`vcon-mcp`) and a Docker image (`public.ecr.aws/r4g1k2s3/vcon-dev/vcon-mcp:main`).

## When to use it

* You want Claude Desktop (or any MCP client) to query a vCon database in natural language.
* You're building an LLM agent that needs durable, structured access to conversation history.
* You have vCons in a [conserver](/conserver)-managed Supabase backend and want a query layer that's safe to expose to language models.
* You need the May 2026 [contract / discovery surface](/mcp-server/contract-tools) — stable response envelopes, cursor pagination, byte-budget enforcement — for production LLM workflows.

## Documentation layout

**Get started:**

* [What is the vCon MCP Server?](/mcp-server/what-is-the-vcon-mcp-server) — overview, requirements, getting started
* [MCP and AI](/mcp-server/mcp-and-ai) — how MCP and AI assistants connect
* [Business Cases](/mcp-server/business-cases-for-mcp-servers-and-vcon) — when to reach for this

**Use it:**

* [What the vCon MCP Server Can Do](/mcp-server/what-the-vcon-mcp-server-can-do) — narrative overview of capabilities
* [Tool Reference](/mcp-server/tool-reference) — every tool, grouped by purpose
* [Contract Tools](/mcp-server/contract-tools) — the May 2026 LLM-facing surface (vcon\_fetch, vcon\_search, etc.)

**Run it:**

* [Transport and Deployment](/mcp-server/transport-and-deployment) — auth, transport modes, environment variables, Docker, npm, Claude Desktop config
* [Field-Name Migration](/mcp-server/field-name-migration) — how the server handles legacy `appended` / `must_support` field names

**Understand it:**

* [How the vCon MCP Server is Built](/mcp-server/how-the-vcon-mcp-server-is-built) — architecture, schema, request flow
* [MCP and Conserver Together](/mcp-server/mcp-and-conserver-together) — how the MCP server and conserver compose

**External:**

* [GitHub: vcon-dev/vcon-mcp](https://github.com/vcon-dev/vcon-mcp) — source
* [mcp.conserver.io](https://mcp.conserver.io/) — auto-generated technical reference


# What is the vCon MCP Server?

Have you ever wanted to ask an AI assistant about past conversations? Maybe you want to find all the times a customer called about billing issues, or analyze patterns in support calls, or track what happened in a sales meeting. The vCon MCP Server makes this possible.

This post explains what the vCon MCP Server is, what problem it solves, and why it might be useful for you.

### The Problem with Conversation Data

Most businesses have conversations happening everywhere. Phone calls, video meetings, chat messages, emails. These conversations contain valuable information, but they are usually scattered across different systems. Each system stores data in its own format. This makes it hard to:

* Search across different types of conversations
* Analyze patterns over time
* Share conversation data between tools
* Work with AI assistants on conversation history
* Maintain privacy and compliance standards

You might have customer support calls in one system, sales meetings in another, and email threads in yet another. To get a complete picture, you would need to check all three systems separately. That takes time and effort.

### What is vCon?

vCon stands for Virtual Conversation. It is an IETF standard format for representing conversations. Think of it like PDF for conversations. Just as PDF is a standard format that works across different computers and programs, vCon is a standard format that works across different systems.

A vCon file can contain:

* The actual conversation content, whether it came from voice, video, text, or email
* Information about who participated in the conversation
* Analysis results from AI, like transcripts, sentiment scores, or summaries
* Attachments like documents or images related to the conversation
* Privacy markers that track consent and can hide sensitive information

The key benefit is portability. If you store conversations in vCon format, you can move them between systems without losing data. You are not locked into one vendor's system. You own your conversation data in a standard format.

### What is MCP?

MCP stands for Model Context Protocol. It is a way for AI assistants to use external tools and data sources. Without MCP, AI assistants can only work with the information they learned during training. They cannot access your live data or perform actions in your systems.

With MCP, an AI assistant can:

* Read data from your databases
* Perform actions using your tools
* Access real-time information
* Maintain context about what you are working on

Think of MCP like giving an AI assistant access to your toolbox. The assistant can see what tools are available, understand what each tool does, and use them when you ask. This makes AI assistants much more useful for real work.

### What is the vCon MCP Server?

The vCon MCP Server combines these two ideas. It is a server that lets AI assistants work with conversation data stored in vCon format. You connect the server to an AI assistant like Claude, and then the assistant can:

* Create new conversation records
* Search through historical conversations
* Analyze conversations for insights
* Organize conversations with tags
* Answer questions about your conversation data

The server speaks the MCP protocol, which AI assistants understand. When you ask the assistant to do something with conversation data, it uses the server's tools to get the job done.

### What Can It Do?

Here are the main capabilities:

**Store conversations** - The server can store conversations in vCon format, following the IETF standard exactly.

**Search conversations** - You can search in four different ways:

* Basic filtering by subject, participants, or dates
* Keyword search that looks for exact words
* Semantic search that finds conversations by meaning, even if the exact words are different
* Hybrid search that combines keyword and semantic approaches

**Organize with tags** - You can add tags to conversations for easy organization and filtering. Tags work like labels you might put on file folders.

**Analyze and monitor** - The server can provide analytics about your conversation database, showing growth trends, content patterns, and health metrics.

**Manage components** - You can add or update different parts of a conversation, like adding analysis results or attaching files, without recreating the whole conversation.

**Use templates** - The server includes templates for common conversation types, making it easier to create new records.

**Extend with plugins** - The server supports plugins that can add custom functionality, like privacy controls or compliance features.

### Who Would Use This?

Several groups of people might find this useful:

**Customer support teams** - Store and search support calls, track issues, analyze agent performance, and maintain compliance records.

**Sales teams** - Record sales conversations, extract action items, analyze what works, and generate meeting summaries.

**Compliance and legal teams** - Maintain conversation archives, apply privacy controls, track consent, and generate audit reports.

**Researchers** - Collect conversation datasets, study communication patterns, and build training data for machine learning models.

**Developers** - Build applications that work with conversation data using a standard format and API.

**Business analysts** - Search across conversations to find insights, track trends, and answer questions about customer interactions.

### A Simple Example

Imagine you run a customer support team. You have thousands of support calls stored in a system. You want to know: "What are customers complaining about most this month?"

Without the vCon MCP Server, you might need to:

1. Export data from your phone system
2. Load it into a spreadsheet or database
3. Write queries or scripts to analyze it
4. Create reports manually

With the vCon MCP Server, you can simply ask your AI assistant: "What are customers complaining about most this month?" The assistant uses the server's search tools to find relevant conversations, analyzes them, and gives you an answer. If you want more detail, you can ask follow-up questions. The assistant has access to all your conversation data through the server.

### Why Standards Matter

Both vCon and MCP are open standards. This means:

* They are not controlled by a single company
* Anyone can implement them
* They work across different systems
* They evolve through community input
* They are documented publicly

Using standards gives you options. If you build on top of the vCon MCP Server and later want to switch to a different system, your data is in a standard format. You are not locked in. You also benefit from the work others do with these standards. New tools and integrations appear as the standards grow.

### Getting Started

The vCon MCP Server is open source ([vcon-dev/vcon-mcp](https://github.com/vcon-dev/vcon-mcp), MIT licensed) and free to use. The repo ships a TypeScript implementation that targets Node.js 20+, with a published Docker image and an installable `vcon-mcp` npm package.

Minimum requirements:

* A Supabase Postgres deployment for the database (free tier is fine to start)
* An MCP-aware client — Claude Desktop, the MCP inspector CLI, or any custom client that speaks MCP
* For semantic search: an OpenAI API key (or another embedding provider — the model is configurable)
* For caching, observability, or HTTP transport: see [Transport and Deployment](/mcp-server/transport-and-deployment)

See [Transport and Deployment](/mcp-server/transport-and-deployment) for the full environment-variable reference and three deployment recipes (local development, Docker, Claude Desktop).

### What's Next?

This was a high-level overview. The other pages in this section go deeper:

* [MCP and AI](/mcp-server/mcp-and-ai) — how MCP works with AI assistants in more detail
* [What the vCon MCP Server Can Do](/mcp-server/what-the-vcon-mcp-server-can-do) — the complete scope of capabilities
* [How the vCon MCP Server is Built](/mcp-server/how-the-vcon-mcp-server-is-built) — internal architecture
* [Tool Reference](/mcp-server/tool-reference) — every tool, grouped by purpose
* [Contract Tools](/mcp-server/contract-tools) — the May 2026 LLM-facing surface
* [Transport and Deployment](/mcp-server/transport-and-deployment) — auth, transport modes, Docker, npm
* [MCP and Conserver Together](/mcp-server/mcp-and-conserver-together) — how the MCP server and conserver compose
* [Business Cases](/mcp-server/business-cases-for-mcp-servers-and-vcon) — when to reach for this


# MCP and AI

How MCP Gives AI Assistants Real Capabilities

AI assistants are powerful, but they have limits. They know a lot about the world from their training, but they cannot access your live data or use your tools. The Model Context Protocol, or MCP, changes that. This post explains how MCP works and why it matters.

### The Limitation of Training Data

When you talk to an AI assistant like Claude or ChatGPT, it responds based on what it learned during training. That training data is a snapshot of information from when the model was created. It is like reading a book that was published last year. The information might be good, but it does not include anything that happened after publication.

This creates several problems:

* The assistant cannot see your current data
* The assistant cannot perform actions in your systems
* The assistant cannot access real-time information
* The assistant cannot work with your specific tools and workflows

You might ask the assistant to check your customer support calls from yesterday. Without MCP, the assistant cannot do that. It does not have access to your systems. It can only work with the information in its training data.

### What is Model Context Protocol?

Model Context Protocol, or MCP, is an open standard that lets AI assistants interact with external tools and data sources. Think of it as a common language that AI assistants and your systems can both understand.

MCP defines three main ways for assistants to interact with external systems:

**Tools** - These are actions the assistant can perform. A tool might create a new record, search a database, or update information. Tools are like functions the assistant can call.

**Resources** - These are read-only data sources the assistant can access. A resource might be a specific file, a database record, or a web page. Resources are like URLs the assistant can fetch.

**Prompts** - These are templates that guide the assistant on how to do something. A prompt might explain how to search effectively or what information to include. Prompts are like instructions or recipes.

Together, these three mechanisms give the assistant capabilities it did not have before.

### A Simple Analogy

Imagine you have a smart assistant in your office. Without MCP, the assistant only knows what was in the training materials. It is like having someone who read a manual but has never actually used your office equipment.

With MCP, you give the assistant:

* Tools it can use, like your phone system, your database, and your file system
* Resources it can read, like your customer records and your company documents
* Prompts that explain how your office works, like how to file paperwork or who to contact for different issues

Now the assistant can actually do work, not just answer questions about what it read in a manual. It can look things up, perform actions, and work with your actual systems.

### How MCP Extends AI Capabilities

Let us look at each part of MCP in more detail.

#### Tools: Actions the Assistant Can Perform

Tools are executable operations. When you ask the assistant to do something, it can choose a tool that performs that action. For example, if you ask the assistant to find customer support calls about billing, it might use a search tool provided by your conversation database.

Each tool has:

* A name that describes what it does
* A description that explains when to use it
* Input parameters that define what information it needs
* Output that describes what it returns

The assistant understands these tool definitions and can decide when to use each one. It is like giving the assistant a toolbox where each tool is labeled and has instructions.

#### Resources: Data the Assistant Can Read

Resources are URI-based data access points. The assistant can request a resource and get data back. For example, if you ask about a specific conversation, the assistant might fetch a resource that points to that conversation's data.

Resources are read-only. The assistant cannot change data through resources. It can only read. This makes resources safe for the assistant to explore your data without accidentally modifying anything.

Resources are discoverable. The assistant can ask what resources are available and then access them. It is like giving the assistant a catalog of all the data it can read.

#### Prompts: Guidance for the Assistant

Prompts are templates that help the assistant understand how to accomplish tasks. They provide context and step-by-step guidance. For example, a prompt might explain how to search for conversations by tags, or what information to include when creating a new conversation record.

Prompts are not just instructions. They can include examples, best practices, and warnings about common mistakes. They help the assistant work more effectively with your specific systems and requirements.

### How the vCon MCP Server Uses MCP

The vCon MCP Server implements MCP to give AI assistants access to conversation data. It provides:

**37 tools** for managing conversations — see [Tool Reference](/mcp-server/tool-reference) for the full list grouped by category. They cover vCon CRUD, dialog/analysis/attachment management, four kinds of search (metadata, keyword, semantic, hybrid), the May 2026 [contract / discovery surface](/mcp-server/contract-tools), tag management, analytics, and database introspection.

**Resources** that let the assistant directly read conversation data using URI paths. For example, the assistant can request a resource like `vcon://uuid/abc123` to get a specific conversation, or `vcon://uuid/abc123/parties` to get just the participant information.

**Prompts** that guide the assistant on common workflows — when to use semantic vs keyword search, how to structure tag filters, what to include when creating new vCons. They are templates, not policies; the assistant adapts them to your specific query.

When you ask the assistant to work with conversation data, it uses these tools, resources, and prompts. The assistant does not need to know the technical details of how your database works. It just needs to know which tools to use and how to use them.

### An Example Conversation

Let us see how this works in practice. You are talking to your AI assistant:

**You:** "Find all the customer support calls from last week where the customer was frustrated."

**Assistant:** The assistant sees you want to search for conversations. It looks at the available tools and sees several search options. It decides to use semantic search because you mentioned "frustrated," which is about sentiment rather than exact words. The assistant uses the `search_vcons_semantic` tool with parameters like the date range and a query about customer frustration.

**Assistant:** "I found 12 support calls from last week where customers showed frustration. Would you like me to summarize the common issues, or show you specific calls?"

**You:** "What were the main issues?"

**Assistant:** The assistant uses the search results it already has and analyzes them. It might use additional tools to get more details about specific conversations, or it might analyze the data directly from the search results.

**Assistant:** "The main issues were: delivery delays (5 calls), billing errors (4 calls), and product defects (3 calls). Should I get more details on any of these?"

This conversation flows naturally, but behind the scenes the assistant is using MCP tools to access your actual conversation data. It is not making things up based on training data. It is working with your real data.

### Why Standards Matter

MCP is an open standard, not a proprietary system. This means:

* Any AI assistant can implement MCP support
* Any system can provide MCP tools, resources, and prompts
* You are not locked into one vendor
* The community can improve and extend the standard
* Documentation is public and accessible

This is important because it means MCP will work with future AI assistants, not just the ones available today. If a new assistant comes along that you prefer, it can still use the same MCP servers you have set up.

It also means you can build your own MCP servers for your specific needs. You are not limited to what vendors provide. You can create tools that match exactly what your business needs.

### Benefits of the MCP Approach

Using MCP with AI assistants provides several benefits:

**Real-time access** - The assistant can work with your current data, not just historical training data.

**Actionable capabilities** - The assistant can perform actions, not just answer questions.

**System integration** - The assistant can work with your existing tools and databases.

**Natural interaction** - You talk to the assistant in plain language, and it figures out which tools to use.

**Extensibility** - You can add new tools, resources, and prompts as your needs grow.

**Security** - The assistant only has access to what you explicitly provide through MCP. You control what it can see and do.

### How It Differs from Traditional APIs

You might wonder how MCP differs from traditional APIs. Traditional APIs require you to know specific endpoints, parameters, and response formats. You need to write code or configure integrations.

MCP works at a higher level. The assistant understands what tools are available and how to use them. You do not need to write code or configure complex integrations. You just talk to the assistant, and it handles the details.

This does not mean MCP replaces APIs. MCP often uses APIs under the hood. But it presents them to the assistant in a way the assistant can understand and use intelligently.

### The Future of AI Integration

MCP represents a new way of integrating AI assistants into your work. Instead of treating the assistant like a separate tool, MCP lets you treat it like a team member who has access to your systems.

As MCP grows and more systems adopt it, AI assistants will become more capable. They will be able to work with more types of data and perform more types of actions. The vCon MCP Server is one example of this future. It gives assistants the ability to work with conversation data in a standard way.

### Conclusion

MCP bridges the gap between AI assistants and your systems. It gives assistants real capabilities by providing tools, resources, and prompts they can understand and use. The vCon MCP Server implements MCP to make conversation data accessible to AI assistants.

The next post in this series covers the complete scope of what the vCon MCP Server can do. It goes into detail about all the features and capabilities available.


# What the vCon MCP Server Can Do

The vCon MCP Server provides **37 tools** that let AI assistants work with conversation data. This page is the narrative overview; for the exhaustive list grouped by purpose see [Tool Reference](/mcp-server/tool-reference), and for the contract / discovery surface added in May 2026 see [Contract Tools](/mcp-server/contract-tools).

### Core Operations: Managing Conversations

The server can create, read, update, and delete conversation records. These are the basic operations you need for managing any conversation data.

#### Creating Conversations

You can create new conversation records in several ways:

**Create from scratch** - Provide all the conversation data in vCon format, including participants, dialog content, analysis results, and attachments. The server validates everything to ensure it follows the IETF vCon standard before storing it.

**Create from templates** - Use predefined templates for common conversation types. The server includes templates for phone calls, chat conversations, email threads, and video meetings. Templates set up the basic structure, and you add the specific details.

When you create a conversation, the server automatically generates a unique identifier if you do not provide one. It also records when the conversation was created and can track when it was last updated.

#### Reading Conversations

You can retrieve conversations by their unique identifier. The server returns the complete conversation record, including all participants, dialog content, analysis results, and attachments.

You can also request only specific parts of a conversation. For example, you might want just the participant information, or just the analysis results. This is useful when you do not need the entire conversation and want to reduce the amount of data transferred.

The server also provides resources, which are URI-based ways to access conversation data. This lets you access specific parts of conversations directly without going through the full tool interface.

#### Updating Conversations

You can update the metadata of conversations, such as the subject line or custom extensions. The server supports different update strategies. You can replace existing values, merge new values with existing ones, or append to existing arrays.

Updates are validated to ensure the conversation still follows the vCon standard after changes. This prevents accidental corruption of your data.

#### Deleting Conversations

You can delete conversations and all their related data. The server requires explicit confirmation to prevent accidental deletions. When you delete a conversation, it removes all associated participants, dialog entries, analysis results, and attachments.

### Component Management: Adding Parts to Conversations

A conversation record can have several types of components. You can add or update these components without recreating the entire conversation.

#### Dialog Entries

Dialog entries are the actual conversation content. This might be a phone call recording, a text chat, an email message, or a video call segment. Each dialog entry includes information about when it occurred, who participated, and the content itself.

The server supports different types of dialog:

* Recordings of audio or video
* Text-based conversations
* Transfers where a call was moved between agents
* Incomplete conversations where the recording was cut off

You can add multiple dialog entries to a single conversation, which is useful for conversations that happened across multiple sessions or channels.

#### Analysis Results

Analysis results are insights generated by AI or other processing tools. Examples include transcripts of audio recordings, sentiment analysis, summaries, topic extraction, and named entity recognition.

Each analysis entry includes information about what tool created it, what version or model was used, and when it was created. This makes it possible to track how analysis results were generated and compare results from different tools.

The server can store analysis results in different formats. Some might be plain text, while others might be structured JSON data. The server handles both types correctly.

#### Attachments

Attachments are files or documents related to the conversation. This might be an invoice discussed during a call, a screenshot shared during a chat, or a contract sent via email.

Attachments can be stored in different ways. They can be embedded directly in the conversation record, or they can be referenced by URL if stored elsewhere. The server tracks what type of file each attachment is and can link attachments to specific dialog entries or participants.

Tags are a special type of attachment used for organization, which we will cover in more detail later.

#### Participant Management

Participants, called parties in the vCon standard, are the people or entities involved in a conversation. Each party has information like name, email address, phone number, role, and organization.

The server lets you add, update, and manage participants. You can link dialog entries and attachments to specific participants, making it easy to see what each person said or contributed.

### Search Capabilities: Finding What You Need

The server provides four different ways to search conversations. Each method works better for different types of queries.

#### Basic Filter Search

Basic filter search lets you find conversations by metadata like subject line, participant names, email addresses, phone numbers, or date ranges. This is the fastest search method and works well when you know specific details about what you are looking for.

For example, you could search for all conversations from last week where a specific customer participated. The search returns complete conversation records that match your criteria.

#### Keyword Search

Keyword search looks for specific words or phrases within the conversation content. It searches through subject lines, dialog text, analysis results, and participant information. This is useful when you remember a specific word or phrase but do not remember other details about the conversation.

The search uses full-text indexing, which makes it fast even with large numbers of conversations. It also handles minor typos and variations in spelling. Results show snippets of where the keywords were found, making it easy to see the relevant parts.

You can combine keyword search with filters like date ranges or tags to narrow down results.

#### Semantic Search

Semantic search finds conversations by meaning rather than exact words. It uses AI embeddings to understand what conversations are about and can find related content even when the exact words are different.

For example, if you search for "customer frustration," semantic search might find conversations where customers were "upset," "angry," "disappointed," or "unhappy," even if those exact words were not used. This makes semantic search useful when you are looking for concepts rather than specific phrases.

Semantic search requires that conversations have been processed to generate embeddings. The server can generate these embeddings when conversations are created or updated, or you can process existing conversations in batch.

#### Hybrid Search

Hybrid search combines keyword search and semantic search. It uses both methods and combines the results, giving you the benefits of both approaches. Keyword search finds exact matches, while semantic search finds conceptually related content.

You can control how much weight each method has in the results. For example, you might want exact keyword matches to rank higher than semantic matches, or vice versa.

### Tag System: Organizing Conversations

Tags are key-value pairs that you can attach to conversations for organization and filtering. Tags are flexible. You can use them however makes sense for your workflow.

#### Adding and Managing Tags

You can add tags individually or in groups. Tags can have string, number, or boolean values. For example, you might tag conversations with department names, priority levels, resolution status, or customer IDs.

The server provides tools to add, update, get, and remove tags. You can also bulk update tags across multiple conversations, which is useful when you need to reorganize or reclassify conversations.

#### Searching by Tags

All search methods support filtering by tags. You can require conversations to have specific tags, or you can search for conversations that have any of several tags. This makes it easy to narrow down results to the conversations you care about.

For example, you might search for high-priority sales conversations from last month. The tag system makes this type of query simple and fast.

#### Tag Analytics

The server can analyze your tag usage to show you what tags are used most often, what values are most common, and how tags are distributed across your conversations. This helps you understand how conversations are organized and can guide decisions about tag structure.

### Database Analytics: Understanding Your Data

The server provides comprehensive analytics about your conversation database. These tools help you understand what you have, how it is growing, and how it is structured.

#### Overall Analytics

You can get analytics about database size, growth trends, content distribution, and health metrics. This includes information like total number of conversations, how many have been added over time, what types of content are stored, and how the database is performing.

#### Growth Analytics

The server can analyze growth patterns over time, showing monthly trends and projections. This helps you understand if your conversation data is growing steadily, if there are seasonal patterns, or if growth is accelerating.

#### Content Analytics

Content analytics show what types of conversations you have, what types of dialog entries are stored, what analysis has been performed, and how participants are distributed. This helps you understand the composition of your conversation data.

#### Attachment Analytics

Attachment analytics show what types of files are stored, how much storage they use, and how they are distributed across conversations. This helps you understand storage usage and plan for capacity.

#### Health Metrics

Health metrics show database performance indicators like query speed, index usage, and cache hit rates. This helps you identify performance issues and optimize your database configuration.

### Database Tools: Inspection and Optimization

The server includes tools for inspecting and optimizing your database. These are useful for understanding your database structure and ensuring it performs well.

#### Database Shape

You can inspect the database structure to see what tables exist, what indexes are defined, how large tables are, and how tables relate to each other. This is useful for understanding the database schema and debugging issues.

#### Database Statistics

You can get statistics about database usage, including how often tables are accessed, how indexes are being used, and cache performance. This helps identify optimization opportunities.

#### Smart Limits

For large databases, the server can recommend search limits based on database size. This prevents queries from using too much memory or taking too long. The server analyzes your database and suggests appropriate limits for different types of searches.

### Templates and Schemas: Working with Standards

The server includes support for templates and schemas that make it easier to work with the vCon standard.

#### Templates

Templates provide pre-configured structures for common conversation types. Instead of building everything from scratch, you can start with a template and fill in the specifics. This reduces errors and speeds up creation of new conversation records.

The server includes templates for phone calls, chat conversations, email threads, video meetings, and custom scenarios.

#### Schemas

You can request the vCon schema in different formats, including JSON Schema and TypeScript types. This is useful when building integrations or writing code that works with vCon data. The schema shows exactly what fields are required, what fields are optional, and what types of values are allowed.

#### Examples

The server can provide example vCon records in different formats. Examples include minimal records, phone calls, chat conversations, emails, video meetings, and full-featured records. These examples serve as references when creating your own conversations.

### Plugin Architecture: Extending the Server

The server supports plugins that can add custom functionality. Plugins can:

* Add new tools that the assistant can use
* Add new resources for data access
* Intercept operations to add custom logic
* Implement privacy and compliance features
* Add integrations with other systems

Plugins use hooks that fire at different points in the request lifecycle. For example, a plugin might run code before a conversation is created, after it is created, before it is read, or after it is updated. This lets plugins add logging, access control, data transformation, or other functionality.

The core server focuses on standard conversation management. Plugins extend it for specific needs like compliance, privacy, or integrations.

### What is Included vs What is Not

The server is open source and includes all the features described above. However, some advanced features are available only through proprietary plugins.

#### Included in Open Source

The open source version includes:

* Complete CRUD operations for conversations
* All four search modes
* Tag management
* Component management
* Database analytics
* Templates and schemas
* Basic plugin architecture

#### Available Through Plugins

Advanced features available through plugins include:

* Consent management and tracking
* Privacy request handling for regulations like GDPR and CCPA
* Automatic compliance checking
* Advanced audit logging
* PII detection and masking
* Data retention enforcement
* Regulatory compliance workflows

The open source version is fully functional for general conversation data management. The proprietary plugins add features needed for regulated industries or strict compliance requirements.

### Practical Examples

Here are some practical examples of how you might use these features:

**Example 1: Customer Support Team** A support team uses the server to store all customer calls. They tag conversations with issue types, customer IDs, and resolution status. They use semantic search to find similar past issues when helping customers. They add analysis results that include transcripts and sentiment scores.

**Example 2: Sales Team** A sales team records all sales calls and meetings. They tag conversations with product names, deal stages, and customer segments. They use keyword search to find conversations where specific products were discussed. They generate summaries automatically and attach them as analysis results.

**Example 3: Compliance Team** A compliance team stores all regulated communications. They use tags to track consent status and retention periods. They use database analytics to monitor data growth and ensure compliance with retention policies. They use plugins to add automatic compliance checking and audit logging.

**Example 4: Research Team** A research team collects conversation datasets for analysis. They use the server to store conversations in a standard format that works across different tools. They use semantic search to find conversations by research themes. They export data for use in machine learning pipelines.

### Integration Points

The server integrates with several systems:

**Supabase** - The database backend. Supabase provides PostgreSQL with additional features like real-time subscriptions and REST APIs.

**Redis** - Optional caching layer. Redis can be added to dramatically speed up reads of frequently accessed conversations.

**OpenAI** - Optional for semantic search. The server can use OpenAI's API to generate embeddings for semantic search, though local models can also be used.

**AI Assistants** - Any assistant that supports MCP can connect to the server. Currently this includes Claude Desktop, with more assistants adding support over time.

### Conclusion

The vCon MCP Server provides a comprehensive set of tools for managing conversation data. It covers the basics of creating and managing conversations, advanced search capabilities, organization through tags, analytics for understanding your data, and extensibility through plugins.

The next post in this series covers the architecture of the server, explaining how it is built and why it is designed the way it is.


# How the vCon MCP Server is Built

The vCon MCP Server is built in layers, with each layer handling a specific responsibility. This architecture makes the server reliable, performant, and extensible. This page is the narrative architecture overview; for the per-tool surface see [Tool Reference](/mcp-server/tool-reference).

> **May 2026 redesign.** The server now exposes 37 tools, including a new contract / discovery family (`vcon_fetch`, `vcon_search`, `vcon_capabilities`, `vcon_taxonomy`, `describe_response_shape`) designed specifically for LLM clients that don't know the server's shape in advance. See [Contract Tools](/mcp-server/contract-tools). The same release added a field-name migration handling `appended → amended` and `must_support → critical`; see [Field-Name Migration](/mcp-server/field-name-migration).

### The Three-Layer Architecture

The server has three main layers:

**MCP Server Layer** - Handles communication with AI assistants using the Model Context Protocol. This layer exposes tools, resources, and prompts that assistants can use.

**Business Logic Layer** - Contains the core functionality for managing conversations. This includes query engines, validation, and plugin systems.

**Database Layer** - Stores and retrieves conversation data. This layer uses Supabase and PostgreSQL with extensions for vector search.

Requests flow from top to bottom. An AI assistant sends a request to the MCP Server Layer. That layer processes it and passes it to the Business Logic Layer. The Business Logic Layer validates and processes the request, then uses the Database Layer to store or retrieve data. Responses flow back up the same path.

### The MCP Server Layer

The MCP Server Layer is the interface between AI assistants and the server. It speaks the Model Context Protocol, which is a standard way for assistants to interact with external systems.

#### Tools, Resources, and Prompts

The server exposes three types of interfaces:

**Tools** are actions the assistant can perform. When you ask the assistant to create a conversation or search for something, it uses a tool. Each tool has a name, description, input parameters, and output format. The assistant reads these definitions and knows how to use each tool.

**Resources** are data the assistant can read. Resources use URI paths, similar to URLs. For example, a resource might be `vcon://uuid/abc123` to access a specific conversation, or `vcon://uuid/abc123/parties` to get just the participant information. Resources are read-only, which keeps them safe.

**Prompts** are guidance templates that help the assistant work effectively. They explain how to structure queries, what information to include, and best practices. The assistant uses prompts to understand how to accomplish tasks correctly.

#### Request Handling

When an assistant sends a request, here is what happens:

1. The request arrives through the MCP protocol, either via standard input/output or HTTP
2. The server parses the JSON-RPC message to understand what the assistant wants
3. The server identifies which tool, resource, or prompt is being requested
4. The server prepares to process the request
5. Plugin hooks can run at this point to modify or intercept the request
6. The request moves to the Business Logic Layer for processing
7. The response comes back from the Business Logic Layer
8. Plugin hooks can run again to modify the response
9. The response is formatted as an MCP protocol message
10. The response is sent back to the assistant

This flow ensures requests are handled consistently and provides opportunities for plugins to add functionality.

### The Business Logic Layer

The Business Logic Layer contains the core functionality of the server. It handles validation, queries, and extensions.

#### The Query Engine

The query engine handles all database operations. It knows how to create, read, update, and delete conversations. It also handles search operations, component management, and tag operations.

The query engine is designed to be efficient. It uses database transactions for operations that involve multiple steps, ensuring data consistency. When creating a conversation, for example, it might need to insert records into multiple tables. Using a transaction means either all the inserts succeed or none of them do, preventing partial data.

The engine also normalizes data when storing it. Normalized data is organized into separate tables with relationships between them. This makes queries efficient and prevents data duplication. When returning data to clients, the engine reconstructs complete conversation objects from the normalized data.

#### The Validation Engine

The validation engine ensures all conversation data follows the IETF vCon standard before it is stored. This prevents invalid data from entering the database and ensures compatibility with other systems.

Validation checks include:

* Version numbers must match the current vCon specification version
* UUIDs must be in the correct format
* At least one participant must exist
* Dialog types must be valid
* Analysis entries must include required fields like vendor information
* References between components must be valid (for example, a dialog entry cannot reference a participant that does not exist)
* Encoding values must be valid
* Dates must be in ISO 8601 format

If validation fails, the server returns a clear error message explaining what is wrong. This helps users fix their data before trying again.

#### The Plugin System

The plugin system allows extending the server without modifying its core code. Plugins can add new tools, resources, and prompts. They can also intercept operations at various points in the request lifecycle.

Plugins use hooks that fire at specific times:

* Before an operation starts, plugins can modify the request
* After an operation completes, plugins can modify the response
* Plugins can add logging, access control, data transformation, or other functionality

For example, a privacy plugin might intercept read operations and remove sensitive information before returning data. An audit plugin might log all operations for compliance tracking. A compliance plugin might check operations against regulatory requirements.

The plugin system loads plugins when the server starts. Plugins register their hooks, tools, resources, and prompts. When requests come in, the server calls the appropriate hooks and tools from all loaded plugins.

### The Database Layer

The Database Layer stores conversation data in Supabase, which provides PostgreSQL with additional features.

#### Normalized Schema Design

The database uses a normalized schema, which means data is organized into separate tables with relationships between them. This is different from storing each conversation as a single JSON document.

The eight core tables are:

* `vcons` — the conversation record itself: `uuid`, `subject`, `created_at`, `updated_at`, `extensions`, `critical` (was `must_support`), `amended` (was `appended`), `vcon_version`
* `parties` — participants, foreign-keyed to `vcons`: `tel`, `sip`, `mailto`, `name`, `role`, `validation`, `jcard`, `timezone`
* `dialog` — conversation content: `type` (`recording`, `text`, `transfer`, `incomplete`), `start`, `duration`, `parties[]`, `body`, `encoding`, `mediatype`, `session_id`, `disposition`
* `analysis` — AI/ML results: `type`, `vendor` (REQUIRED), `product`, `schema` (was `schema_version`), `body`, `encoding`, `dialog_indices[]`
* `attachments` — files and structured documents: `purpose` (spec field), `party`, `dialog`, `body`, `encoding`, `mediatype`, `filename`, `url`, `content_hash`
* `groups` — multi-vCon aggregations
* `party_history` — join / drop / hold / mute events
* `vcon_embeddings` — semantic-search vectors (`embedding vector(384)`, `model`, with a queue table for asynchronous generation)

Two views and one materialized view round out the data layer:

* `vcons_legacy` — backward-compat view that exposes the old field names (`must_support`, `appended`, `schema_version`, `mimetype`) for clients that haven't migrated. See [Field-Name Migration](/mcp-server/field-name-migration).
* `vcon_tags_mv` — materialized view that parses the tags attachment (`purpose: "tags"`, `encoding: "json"`) into rows so tag filtering doesn't pay the JSON-parsing cost on every query.

This design has several benefits:

* Efficient queries. You can search for conversations by participant without loading all conversation data
* Easy to update. You can add a new dialog entry without touching other parts of the conversation
* Scalable. The database can handle millions of conversations efficiently
* Proper constraints. Foreign keys ensure data relationships are valid

When returning data to clients, the query engine joins these tables together to reconstruct complete conversation objects.

#### Search Architecture

The server supports four types of search, each using different database features:

**Metadata search** filters by subject, participant, or dates. It uses B-tree indexes, which are fast for exact matches and range queries. This is the fastest search method, typically returning results in under 100 milliseconds.

**Keyword search** looks for words within conversation content. It uses GIN indexes with trigram matching, which allows it to find words even with minor typos. It searches through dialog text, analysis results, and participant information. This method typically takes a few hundred milliseconds.

**Semantic search** finds conversations by meaning using AI embeddings. Conversations are converted into 384-dimension vectors stored in the `vcon_embeddings` table (`pgvector` extension) and indexed with HNSW for fast similarity search. The default embedding provider is OpenAI; the model is configurable via the `EMBEDDING_MODEL` environment variable, and a queue-based async generator (`embedding_queue` table) handles backfilling new vCons without blocking writes. Semantic queries typically take one to two seconds.

**Hybrid search** combines keyword and semantic search. It runs both searches and merges the results, ranking them based on a weighted combination of both scores. You can control how much weight each method has. This method provides the best of both approaches but takes longer, typically two to three seconds.

Each search method is implemented as a database function that runs on the database server. This keeps search logic close to the data, which improves performance.

#### Tag Storage

Tags are stored as a special type of attachment. This keeps tags within the vCon format while allowing efficient searching.

The server maintains a materialized view that extracts tags from attachments and indexes them. A materialized view is a pre-computed query result that is stored in the database and refreshed periodically. This makes tag searches very fast without requiring schema changes when you add new tags.

When you search by tags, the server uses this materialized view to find conversations quickly. When you add or update tags, the materialized view is updated automatically.

#### Caching with Redis

The server supports optional Redis caching. Redis is an in-memory data store that is much faster than database queries. When enabled, the server checks Redis first before querying the database.

If data is found in Redis, it returns immediately. If not, it queries the database, stores the result in Redis for future requests, and then returns it. This can make frequently accessed conversations load 20 to 50 times faster.

Redis caches have expiration times, so cached data does not become stale. When conversations are updated, the cache is cleared for those conversations, ensuring you always get current data.

### Request Flow Examples

Let us walk through two examples to see how requests flow through the system.

#### Creating a Conversation

You ask the assistant: "Create a vCon for a support call."

1. The assistant calls the `create_vcon` tool with conversation data.
2. The MCP Server Layer receives the request and identifies it as a tool call.
3. Plugin hooks can run at this point. For example, a plugin might add default tags or metadata.
4. The Business Logic Layer receives the request. The validation engine checks that all required fields are present and valid.
5. If validation passes, the query engine starts a database transaction.
6. The query engine inserts records:
   * First, it inserts the main conversation record into the `vcons` table
   * Then it inserts participant records into the `parties` table
   * If dialog content is provided, it inserts dialog records
   * If analysis results are provided, it inserts analysis records
7. The transaction commits, ensuring all inserts succeed together.
8. Plugin hooks run again. For example, an audit plugin might log the creation, or a webhook plugin might notify another system.
9. The response is formatted and sent back to the assistant, including the new conversation's UUID.
10. The assistant receives the response and confirms the conversation was created.

#### Semantic Search

You ask the assistant: "Find frustrated customers from last week."

1. The assistant calls the `search_vcons_semantic` tool with a query and date range.
2. The MCP Server Layer receives the request.
3. Plugin hooks might modify the search criteria. For example, a multi-tenant plugin might add filters to restrict results to your organization.
4. The Business Logic Layer receives the request. It converts your query text into an embedding vector using an AI service like OpenAI.
5. The query engine calls a database function that performs the semantic search. The function:
   * Uses the HNSW index to find conversations with similar embeddings
   * Filters by your date range
   * Applies any tag filters you specified
   * Ranks results by similarity
   * Returns the top matches
6. The query engine reconstructs complete conversation objects by joining the search results with related tables.
7. Plugin hooks might filter the results. For example, a privacy plugin might remove sensitive conversations before returning them.
8. The response is formatted and sent back to the assistant with search results.
9. The assistant receives the results and can analyze or present them to you.

### Performance Considerations

The server is designed for performance at multiple levels.

#### Database Optimization

The database uses indexes strategically. B-tree indexes on UUIDs and dates make lookups by identifier or date range fast. GIN indexes on text fields enable fast full-text search. HNSW indexes on embeddings enable fast semantic search.

The server also uses materialized views for frequently accessed data like tags. These views are pre-computed and refreshed periodically, avoiding expensive computations on every query.

Query patterns are optimized. The server uses prepared statements, which are faster than building queries from strings. It batches operations where possible, reducing the number of database round trips.

#### Memory Management

The server limits result set sizes to prevent memory issues. Large searches return paginated results. The server can stream large responses instead of loading everything into memory at once.

Plugin resources are cleaned up properly. When plugins are unloaded or the server shuts down, resources are released to prevent memory leaks.

#### Scalability

The server design supports scaling in multiple ways:

**Horizontal scaling** means running multiple server instances. Since the server is stateless (it does not store session information), you can run multiple instances behind a load balancer. Requests can be distributed across instances.

**Vertical scaling** means increasing the resources available to a single instance. You can add more memory, faster CPUs, or faster database connections.

**Read replicas** allow distributing read queries across multiple database copies. Write operations go to the main database, while read operations can use replicas, reducing load on the primary database.

### Type Safety and Validation

The server is built with TypeScript, which provides compile-time type checking. This means many errors are caught before the code runs. Type definitions match the IETF vCon specification exactly, ensuring the code implements the standard correctly.

Runtime validation uses Zod, which validates data when it arrives. Even if data comes from an external source that might not follow types correctly, Zod ensures it matches the expected structure before processing.

The combination of TypeScript types and Zod validation provides both compile-time safety and runtime safety, preventing many classes of bugs.

### Extensibility Through Plugins

The plugin system allows the server to be extended without modifying core code. This keeps the core simple and focused, while allowing specific needs to be addressed through plugins.

Plugins can be developed independently and loaded at runtime. They can add functionality like compliance checking, privacy controls, integrations with other systems, or custom analytics.

The plugin interface is well-defined, so plugins work reliably. As long as a plugin implements the interface correctly, it will work with the server regardless of when it was developed.

### Transport

The server runs in one of two transports, controlled by the `MCP_TRANSPORT` environment variable:

* **stdio** (default) — for Claude Desktop, the MCP inspector CLI, and any client that launches the server as a subprocess.
* **HTTP** (Streamable HTTP, spec 2025-03-26) — for remote agents and web clients. Supports stateful (`Mcp-Session-Id` header) or stateless mode, optional Server-Sent Events for notifications, configurable CORS and DNS-rebinding protection.

See [Transport and Deployment](/mcp-server/transport-and-deployment) for the full environment-variable reference and deployment recipes.

### Security Architecture

Security is handled at multiple levels:

**Authentication** ensures only authorized clients can call the server. The MCP server validates a bearer token configured via the `API_KEYS` environment variable (a comma-separated list). The token is read by default from the `Authorization: Bearer <token>` header; override the header name with `API_KEY_HEADER`. If `API_AUTH_REQUIRED=true` (the default) and `API_KEYS` is unset, the server refuses to start — that's intentional, to prevent accidental unauthenticated deployments. See [Transport and Deployment](/mcp-server/transport-and-deployment) for the full configuration surface.

**Multi-tenancy.** When `TENANT_ISOLATION=true`, every request must supply a tenant identifier — either via the `x-tenant-id` header or the `TENANT_ID` env var as a default. The server passes the resolved tenant id into every database query, where Supabase Row Level Security policies enforce isolation between tenants.

**Authorization** beyond authentication is delegated to Supabase RLS policies on the database tables. Plugins can add additional authorization checks at the tool layer.

**Data protection** includes encryption at rest in Supabase and TLS in transit. Plugins can add redaction to hide sensitive information before returning data; the response shape stays the same so clients don't need to know whether redaction was applied.

The server itself does not persist credentials — `API_KEYS` is read at startup, and the Supabase service-role or anon key is read per query. Rotate credentials by restarting the server with new values.

### Conclusion

The vCon MCP Server's architecture balances simplicity, performance, and extensibility. The layered design separates concerns, making the code easier to understand and maintain. The normalized database schema ensures efficient queries and scalability. The plugin system allows extending functionality without modifying core code.

This architecture supports the server's goals of providing reliable conversation data management while remaining flexible enough to meet diverse needs. The next post in this series covers business cases and real-world use cases for the server.


# Tool Reference

Every tool the vCon MCP server exposes, grouped by purpose. Quick lookup for LLM-driven workflows.

The vCon MCP server exposes **37 tools** to LLM clients. They split into seven groups by purpose. For each tool's authoritative response schema, call `describe_response_shape(tool_name)` against the live server — see [Contract Tools](/mcp-server/contract-tools).

The May 2026 contract redesign added a six-tool family (`vcon_fetch`, `vcon_search`, `vcon_capabilities`, `vcon_taxonomy`, `vcon_graph_shape`, `describe_response_shape`) that LLM clients should prefer over the legacy equivalents wherever they overlap.

## vCon CRUD (8 tools)

Create, fetch, update, and delete vCons; append the three sub-resources (dialog, analysis, attachment).

| Tool                        | Purpose                                                                                                                                                                                                  |
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `create_vcon`               | Create a new vCon from a JSON object.                                                                                                                                                                    |
| `create_vcon_from_template` | Create a vCon from one of the server's named templates (`phone_call`, `chat_conversation`, `email_thread`, `video_meeting`, `custom`).                                                                   |
| `get_vcon`                  | Fetch a vCon by UUID. **For LLM workflows, prefer `vcon_fetch`** — it returns a stable envelope and lets you control byte budget.                                                                        |
| `update_vcon`               | Partially update an existing vCon. Accepts both spec-correct (`amended`, `critical`) and legacy (`appended`, `must_support`) field names — see [Field-Name Migration](/mcp-server/field-name-migration). |
| `delete_vcon`               | Delete a vCon by UUID. Cascades to all related rows (parties, dialog, analysis, attachments).                                                                                                            |
| `add_dialog`                | Append a dialog entry to an existing vCon.                                                                                                                                                               |
| `add_analysis`              | Append an analysis entry. `vendor` is REQUIRED.                                                                                                                                                          |
| `add_attachment`            | Append an attachment. Uses `purpose` (or `type` for the lawful\_basis extension exception).                                                                                                              |

## Search — legacy (4 tools)

The pre-2026 search tools. Still supported. For new LLM-driven workflows prefer `vcon_search` from the [contract surface](/mcp-server/contract-tools).

| Tool                    | Purpose                                                                                                                     |
| ----------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `search_vcons`          | Filter by subject, party, dates, and tags. Metadata-only.                                                                   |
| `search_vcons_content`  | Postgres full-text search over dialog and analysis bodies.                                                                  |
| `search_vcons_semantic` | Vector / embedding similarity (pgvector, 384-dim, OpenAI by default). Use when the query is conceptual rather than literal. |
| `search_vcons_hybrid`   | Combines full-text and semantic with a tunable `semantic_weight`.                                                           |

## Contract / discovery (6 tools, new May 2026)

The LLM-facing surface. Stable envelopes, cursor pagination, byte-budget enforcement, dealer filtering. See [Contract Tools](/mcp-server/contract-tools) for the design rationale and envelope formats.

| Tool                      | Purpose                                                                                                                                                  |
| ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `vcon_fetch`              | Fetch a vCon (or a partial projection of one) with explicit `include` selection and `max_response_bytes`. Returns `{ok, item}`.                          |
| `vcon_search`             | Unified search across `metadata`, `keyword`, `semantic`, and `hybrid` modes. Cursor-based, dealer-filterable, LLM-hardened. Returns `{ok, items, page}`. |
| `vcon_capabilities`       | Returns what the server supports — tools, includes, search modes, byte budgets, migration hints. Call once per session and cache.                        |
| `vcon_taxonomy`           | Controlled vocabulary the corpus actually uses (portal taxonomy, common tag keys, attachment types, preferred fields).                                   |
| `vcon_graph_shape`        | Live shape of the corpus — analysis types, attachment purposes, tag keys, and their co-occurrence edges.                                                 |
| `describe_response_shape` | JSON Schema + example payload for any tool. Use before parsing or planning a multi-step query.                                                           |

## Tags (5 tools)

Tags are stored as `attachments[]` entries with `purpose: "tags"` and a JSON body. The MCP server materializes them through `vcon_tags_mv` for fast lookup.

| Tool              | Purpose                                                                  |
| ----------------- | ------------------------------------------------------------------------ |
| `manage_tag`      | Add, update, or remove a single tag.                                     |
| `get_tags`        | List tags on a specific vCon.                                            |
| `get_unique_tags` | List all distinct tag keys/values across the database.                   |
| `search_by_tags`  | Find vCons matching tag filters. Fast — backed by the materialized view. |
| `remove_all_tags` | Bulk-clear all tags on a vCon.                                           |

## Analytics (6 tools)

Aggregations over the corpus. Reports rather than queries.

| Tool                           | Purpose                                                                 |
| ------------------------------ | ----------------------------------------------------------------------- |
| `get_database_analytics`       | High-level summary: size, growth, content distribution, health.         |
| `get_monthly_growth_analytics` | Ingestion trend, with `granularity` of `daily`, `weekly`, or `monthly`. |
| `get_attachment_analytics`     | Distribution of attachment purposes, mediatypes, and sizes.             |
| `get_tag_analytics`            | Tag frequency and value distribution.                                   |
| `get_content_analytics`        | Dialog mediatypes, party patterns, analysis vendors.                    |
| `get_database_health_metrics`  | Query performance, index usage, optimization hints.                     |

## Database inspection (6 tools)

For ops, debugging, and capacity planning. Read-only.

| Tool                      | Purpose                                                                                                                                                                  |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `get_database_shape`      | Tables, columns, indexes, relationships, sizes.                                                                                                                          |
| `get_database_stats`      | Cache hit rates, index usage, slow-query stats.                                                                                                                          |
| `get_database_size_info`  | Row counts and storage totals, with recommendations for large datasets.                                                                                                  |
| `get_smart_search_limits` | The result-size and depth bounds the server will apply. Useful before constructing a large search.                                                                       |
| `analyze_query`           | Returns a Postgres `EXPLAIN` plan for the SQL behind a given tool call.                                                                                                  |
| `vcon_aggregate`          | Server-side rollup grouped by dealer. Returns `filtered_count` and `baseline_count` per group for rate calculation, backed by the `aggregate_vcons_by_dealer_stats` RPC. |

## Schema & examples (2 tools)

| Tool           | Purpose                                                                                                                   |
| -------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `get_schema`   | Return the vCon JSON Schema (or TypeScript types) in the format the client requests.                                      |
| `get_examples` | A handful of example vCons covering common patterns — `minimal`, `phone_call`, `chat`, `email`, `video`, `full_featured`. |

## When to use which search tool

The five search-shaped tools overlap. Quick guide:

| Situation                                                 | Use                                                               |
| --------------------------------------------------------- | ----------------------------------------------------------------- |
| LLM-driven workflow, want stable envelopes and pagination | `vcon_search`                                                     |
| Literal keyword in conversation content                   | `vcon_search` mode=`keyword` (or legacy `search_vcons_content`)   |
| Conceptual / fuzzy query                                  | `vcon_search` mode=`semantic` (or legacy `search_vcons_semantic`) |
| Mixed, want best general result                           | `vcon_search` mode=`hybrid` (or legacy `search_vcons_hybrid`)     |
| Just need metadata or a UUID lookup                       | `vcon_search` mode=`metadata` or `vcon_fetch`                     |
| Tag-only filtering, no full-text                          | `search_by_tags`                                                  |

## See also

* [Contract Tools](/mcp-server/contract-tools) — design and envelope formats for the May 2026 family
* [Field-Name Migration](/mcp-server/field-name-migration) — how the server handles `appended → amended` and `must_support → critical`
* [Transport and Deployment](/mcp-server/transport-and-deployment) — auth, transport modes, Docker / npm
* [What the vCon MCP Server Can Do](/mcp-server/what-the-vcon-mcp-server-can-do) — narrative overview


# Contract Tools

The May 2026 discovery and contract surface — how LLM clients introspect the server before making expensive calls.

In May 2026 the vCon MCP server added a family of six tools designed specifically for LLM clients that don't know what they're talking to in advance. They let a client ask "what do you support?", "what shape will you return?", and "what's your controlled vocabulary?" before making queries that might fail or return junk.

These are the **contract tools**: `vcon_fetch`, `vcon_search`, `vcon_capabilities`, `vcon_taxonomy`, `vcon_graph_shape`, and `describe_response_shape`. Together they give an LLM enough information to write accurate, token-budget-aware queries against any deployment of the server, regardless of its specific extensions, tag vocabulary, or attached domain conventions.

## Why they exist

The pre-2026 MCP surface (and most MCP servers in general) assumes the client knows the server's shape ahead of time. That assumption breaks when an LLM is the client:

* The model doesn't always know which extensions a particular server supports.
* The model can't reliably guess how a server normalizes things like dealer IDs or campaign names.
* Token-budget-aware clients need to know the response shape *before* receiving the response, or they'll overflow context windows.
* LLMs are bad at offset-based pagination — they need cursor-based.

The contract tools fix this by exposing the server's contract explicitly. A well-behaved LLM client calls one or more of them once per session and caches the result.

## Stable response envelopes

All contract tools use one of two envelope shapes — predictable across tools, predictable across versions:

**Single-item (fetch):**

```json
{
  "ok": true,
  "item": { ... }
}
```

**Multi-item (search, list):**

```json
{
  "ok": true,
  "items": [ ... ],
  "page": {
    "next_cursor": "opaque-string-or-null"
  }
}
```

**Error:**

```json
{
  "ok": false,
  "error": {
    "code": "RESPONSE_TOO_LARGE",
    "message": "Response would exceed max_response_bytes",
    "details": { ... }
  }
}
```

If a client encounters `ok: false`, it can recover from a clean structured error. The previous design returned partial / malformed results in the same shape as success, which an LLM had no way to detect.

## The six tools

### `vcon_capabilities`

Returns what this server supports:

* `supported_includes` — the field groups `vcon_fetch` and `vcon_search` understand: `core`, `parties`, `summary`, `tags`, `dealer`, `counts`, `dialog`, `analysis`, `attachments`
* `search_modes` — `metadata`, `keyword`, `semantic`, `hybrid`
* `pagination_semantics` — confirms cursor-based pagination, names the cursor field
* `byte_budgets` — the default and max values of `max_response_bytes`
* `migration_hints` — notes about any active field-name migrations (see [Field-Name Migration](/mcp-server/field-name-migration))

Call this first. Cache the result for the session.

### `vcon_taxonomy`

Returns the controlled vocabulary the database actually uses:

* `portal_taxonomy` — domain-specific enum values surfaced by this deployment (e.g. a portal with categories `complaint`, `inquiry`, `praise`)
* `common_tag_keys` — the tag keys that appear in the corpus, with sample values
* `attachment_types` — recognized attachment purposes, including extension-defined ones like `strolid_dealer`
* `preferred_fields` — hints about which fields the deployment expects to be populated

LLMs use this to write better filters. Without it, the model would guess values; with it, the model knows the real surface area.

### `vcon_graph_shape`

Returns a live picture of how data is shaped in **this** corpus — not what's theoretically possible, but what's actually present:

```json
{
  "ok": true,
  "item": {
    "nodes": [
      { "id": "analysis:transcript", "type": "analysis_type", "count": 12483 },
      { "id": "tag:priority", "type": "tag_key", "count": 821 },
      { "id": "attachment:strolid_dealer", "type": "attachment_purpose", "count": 12200 }
    ],
    "edges": [
      { "source": "analysis:transcript", "target": "tag:priority", "strength": 0.42 }
    ]
  }
}
```

The shape evolves with the deployment. Use it when an LLM needs to ground its assumptions about analysis types and tag keys in what's really there.

### `describe_response_shape`

Given a tool name, returns the JSON Schema (or equivalent) of the response that tool will produce, plus a concrete example payload. Use this when:

* Your client needs to parse the response into typed objects.
* You're building a multi-step plan and need to know which fields will be available downstream.
* You're operating under a token budget and need to limit what to ask for.

Calling without a `tool_name` returns the list of tools with published shapes.

### `vcon_fetch`

The contract-aware fetch. Like `get_vcon` but:

* Accepts an `include` array so you only pay for the parts you need. Valid values are the same `supported_includes` returned by `vcon_capabilities`.
* Accepts a `max_response_bytes` budget (default 250 000). If the response would exceed the budget, the call returns `{ok: false, error: {code: "RESPONSE_TOO_LARGE"}}` rather than truncating silently.
* Returns the response in the `{ok, item}` envelope so the schema matches `describe_response_shape("vcon_fetch")`.

Prefer this over `get_vcon` from any LLM-driven workflow.

### `vcon_search`

The contract-aware search. Designed for LLM use:

* **Modes:** `metadata`, `keyword`, `semantic`, `hybrid`. Hybrid takes a `semantic_weight` (0–1) blending the two scores.
* **Cursor-based.** Returns `page.next_cursor`; the client passes the cursor back to get the next page. No offsets, no page numbers.
* **Dealer filterable.** In multi-tenant deployments where Strolid-style dealer attachments are present, `filters.dealer_id` (exact match) or `filters.dealer_name` (substring) are first-class filters rather than free-form tags. Backed by the `aggregate_vcons_by_dealer_stats` RPC for fast top-of-funnel counts.
* **Tag-first.** Tag filters are the cheapest dimension to filter on; the search pushes them first.
* **LLM-hardened.** Rejects ambiguous queries with a structured error rather than returning bad results — the model can recover from a clean error, but can't recover from a wrong answer it doesn't realize is wrong.

Same `include` and `max_response_bytes` controls as `vcon_fetch`. Same `{ok, items, page}` envelope.

## Recommended flow for an LLM session

```
1. vcon_capabilities()                         -> cache server contract
2. vcon_taxonomy()                             -> cache controlled vocab
3. vcon_graph_shape()                          -> understand what's actually there
4. describe_response_shape("vcon_search")      -> understand search response shape
5. vcon_search(filters, include, max_bytes)    -> paginated list of UUIDs + summaries
6. vcon_fetch(uuid, include, max_bytes)        -> pull the specific data you need
7. repeat 5/6
```

Steps 1–4 happen once per session and are cheap. Steps 5–6 are the working loop.

## Field-name compatibility

In May 2026 the server also shipped database migration `20251120150100_field_renames.sql` that handles the `appended → amended` and `must_support → critical` renames at the storage layer (see [Field-Name Migration](/mcp-server/field-name-migration)). The contract tools always emit and accept the spec-correct names; the legacy names are translated on read via the `vcons_legacy` view.

## See also

* [Tool Reference](/mcp-server/tool-reference) — the full tool list
* [Field-Name Migration](/mcp-server/field-name-migration) — back-compat behavior
* [Transport and Deployment](/mcp-server/transport-and-deployment) — how to actually run a server you can call these tools on
* [What the vCon MCP Server Can Do](/mcp-server/what-the-vcon-mcp-server-can-do) — narrative overview


# Transport and Deployment

How to run the vCon MCP server — transport modes, authentication, environment variables, Docker and npm.

The vCon MCP server runs in one of two transport modes and authenticates clients via a configurable bearer-token scheme. This page is the reference for both.

## Transport modes

The server speaks the [Model Context Protocol](https://modelcontextprotocol.io) over **stdio** by default and **Streamable HTTP** when configured. Pick the transport based on how the client connects:

| Transport                                   | When to use                                                                                | Configured via                   |
| ------------------------------------------- | ------------------------------------------------------------------------------------------ | -------------------------------- |
| **stdio** (default)                         | Claude Desktop, the MCP inspector CLI, any client that launches the server as a subprocess | `MCP_TRANSPORT=stdio` (or unset) |
| **HTTP** (Streamable HTTP, spec 2025-03-26) | Web clients, remote LLM agents, anything that connects over the network                    | `MCP_TRANSPORT=http`             |

### stdio

The default. The server reads JSON-RPC from stdin and writes responses to stdout. The host process (Claude Desktop, an MCP-aware CLI, a test harness) is responsible for spawning the server. Nothing to configure besides credentials and the database connection.

### HTTP (Streamable HTTP)

Streamable HTTP is the 2025-03-26 MCP HTTP transport. Configurable surface:

| Variable                  | Description                                                                   | Default     |
| ------------------------- | ----------------------------------------------------------------------------- | ----------- |
| `MCP_HTTP_HOST`           | Bind address                                                                  | `127.0.0.1` |
| `MCP_HTTP_PORT`           | Bind port                                                                     | `3000`      |
| `MCP_HTTP_STATELESS`      | If `true`, every request is independent — no `Mcp-Session-Id` header tracking | `false`     |
| `MCP_HTTP_JSON_ONLY`      | If `true`, disable Server-Sent Events; responses are plain JSON               | `false`     |
| `MCP_HTTP_CORS`           | Enable CORS                                                                   | `false`     |
| `MCP_HTTP_CORS_ORIGIN`    | Allowed origin when CORS is enabled                                           | (unset)     |
| `MCP_HTTP_DNS_PROTECTION` | Enable DNS rebinding protection                                               | `false`     |

In stateful mode (the default for HTTP), the server returns an `Mcp-Session-Id` header on the first response. Clients include it on every subsequent request. In stateless mode, each request stands alone — useful behind load balancers or for serverless deployments where you can't pin requests to a single instance.

## Authentication

Authentication is **bearer-token based** and **optional**. Configure via environment variables.

| Variable            | Description                                  | Default         |
| ------------------- | -------------------------------------------- | --------------- |
| `API_KEYS`          | Comma-separated list of valid bearer tokens  | (unset)         |
| `API_KEY_HEADER`    | Header name to read                          | `authorization` |
| `API_AUTH_REQUIRED` | If `false`, auth checks are skipped entirely | `true`          |

By default the server reads `Authorization: Bearer <token>`. Override with `API_KEY_HEADER=x-mcp-token` (or similar) if your environment requires a different header.

If `API_KEYS` is unset and `API_AUTH_REQUIRED=true`, the server **refuses to start** — that's intentional, to prevent accidental unauthenticated deployments. If you genuinely want an open server (local development), set `API_AUTH_REQUIRED=false`.

Per-tool scoping is **not currently enforced** — every valid token can call every tool. If you need per-tool ACLs, run a separate server instance with a different token set.

### Multi-tenancy

For multi-tenant deployments, supply a tenant identifier per request:

| Variable               | Description                                                   |
| ---------------------- | ------------------------------------------------------------- |
| `TENANT_ISOLATION`     | When `true`, enables tenant filtering on all queries          |
| `TENANT_ID`            | Default tenant id used when no per-request tenant is supplied |
| `x-tenant-id` (header) | Per-request tenant override                                   |

Tenant filtering is enforced by Postgres Row Level Security on the Supabase backend; the MCP server passes the resolved tenant id into every query. Make sure your RLS policies are configured before you turn this on.

## Database connection

The server expects a Supabase Postgres deployment, configured by:

| Variable                    | Description                                             | Required?    |
| --------------------------- | ------------------------------------------------------- | ------------ |
| `SUPABASE_URL`              | Project URL (e.g. `https://abc123.supabase.co`)         | Yes          |
| `SUPABASE_SERVICE_ROLE_KEY` | Service-role key — bypasses RLS for trusted server work | One of these |
| `SUPABASE_ANON_KEY`         | Anonymous key — relies on RLS for access control        | One of these |
| `DB_TYPE`                   | `supabase` (default) or `mongodb` (experimental)        | No           |

The database schema is managed by the migrations under `supabase/migrations/` in the [vcon-mcp repo](https://github.com/vcon-dev/vcon-mcp); apply them with `supabase db push` or the Supabase migration UI before pointing the server at a fresh project.

For semantic search you'll additionally want to set an embedding provider — currently the server uses OpenAI by default and stores 384-dimension vectors in `vcon_embeddings`. The relevant env vars (`OPENAI_API_KEY`, `EMBEDDING_MODEL`, etc.) are documented in the repo README.

## Caching (optional)

If `REDIS_URL` is set, the server uses Redis as a read-through cache in front of Postgres. Cache hits skip the database call entirely; misses populate the cache after the query. Defaults are sensible for most deployments — see the repo README for the full list of `REDIS_*` env vars and TTL controls.

## Running the server

### Local development

```bash
git clone https://github.com/vcon-dev/vcon-mcp
cd vcon-mcp
npm install
npm run build

# Configure
cp .env.example .env
# edit .env: SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY, API_KEYS

# Run
npm run dev
```

The compiled entry point is `dist/index.js`. The package ships a `vcon-mcp` bin entry, so once installed globally (`npm install -g vcon-mcp`) you can also start it with just `vcon-mcp`.

### Docker

A published image lives on AWS Public ECR:

```bash
docker run --rm -p 3000:3000 \
  -e SUPABASE_URL="https://your-project.supabase.co" \
  -e SUPABASE_SERVICE_ROLE_KEY="$SUPABASE_SERVICE_ROLE_KEY" \
  -e API_KEYS="$MCP_API_KEY_1,$MCP_API_KEY_2" \
  -e MCP_TRANSPORT=http \
  public.ecr.aws/r4g1k2s3/vcon-dev/vcon-mcp:main
```

The `main` tag tracks the trunk branch. For production, pin to an explicit semver tag (the CI tags every release as `vX.Y.Z`).

### Claude Desktop

To use the server with Claude Desktop, run it in stdio mode and reference it from your `claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "vcon": {
      "command": "npx",
      "args": ["-y", "vcon-mcp"],
      "env": {
        "SUPABASE_URL": "https://your-project.supabase.co",
        "SUPABASE_SERVICE_ROLE_KEY": "...",
        "API_AUTH_REQUIRED": "false"
      }
    }
  }
}
```

Claude Desktop launches the server as a subprocess and speaks stdio with it — no HTTP, no exposed ports.

## Observability

The server emits OpenTelemetry spans and metrics for every tool call. The [`vcon-mcp-adapters`](/tools/vcon-mcp-adapters) package ships ready-made OTLP exporters; alternatively configure your own collector via the standard `OTEL_EXPORTER_OTLP_ENDPOINT` env var.

Per-tool span attributes include:

* `tool_name`, `tool_category`
* `status` (`ok` | `error` | `validation_failed`)
* `duration_ms`
* `response_bytes`, `truncated` (for byte-budgeted tools)
* `cache_hit` (when Redis caching is enabled)

## See also

* [Tool Reference](/mcp-server/tool-reference) — every tool, grouped by purpose
* [Contract Tools](/mcp-server/contract-tools) — design and envelopes for the May 2026 LLM-facing surface
* [How the vCon MCP Server is Built](/mcp-server/how-the-vcon-mcp-server-is-built) — internal architecture
* [Field-Name Migration](/mcp-server/field-name-migration) — back-compat behavior


# Field-Name Migration

How the MCP server handles the appended→amended and must\_support→critical field renames.

The vCon working group renamed two fields in `draft-ietf-vcon-vcon-core-02`:

| Old name (pre-spec-02) | New name (spec-02 / current)                       |
| ---------------------- | -------------------------------------------------- |
| `appended`             | `amended`                                          |
| `must_support`         | `critical` (often surfaced as `must_understand[]`) |

The vCon MCP server has shipped a migration that handles both names at the storage layer, so existing data and clients keep working while new code uses the spec-correct names. This page documents how that works in case you're debugging it or migrating a downstream consumer.

## What the migration does

Database migration `20251120150100_field_renames.sql`:

1. Renames the underlying columns: `appended` → `amended`, `must_support` → `critical`.
2. Creates a `vcons_legacy` view that re-exposes the old names for any reader that hasn't migrated.
3. Updates the indexes accordingly.

The TypeScript schema handlers in `src/tools/handlers/schema.ts` (around lines 34–44) translate between the old and new names on the way in and out. The tools themselves always operate on the new names.

## What this means for you

### If you read from the MCP server

Your client receives responses using the **new** field names: `amended`, `critical`. If your downstream code still expects `appended` / `must_support`, either:

* Update your code to use the new names (preferred), or
* Point your reader at the `vcons_legacy` view, which still uses the old names.

### If you write to the MCP server

The server accepts either name on input — for now. Submitting `appended` or `must_support` produces a deprecation warning in the response. New code should write the spec-correct names.

### If you query the database directly

Use the new column names on the canonical tables. Use the `vcons_legacy` view if you have queries you don't want to rewrite yet.

## Other field-name pitfalls

The rename is only the tip of the field-name issue. Some other names commonly get wrong:

* **Analysis field is `schema`**, never `schema_version`. The `vcon` Python library pre-0.9.1 sometimes emitted `schema_version`; the MCP server treats that as `schema` on read.
* **Attachment field is `purpose`**, never `type` — except the `lawful_basis` extension, which is the documented exception. The server flags any other attachment with a `type` field as suspicious.

## See also

* [Tool Reference](/mcp-server/tool-reference) — the full surface
* [Contract Tools](/mcp-server/contract-tools) — the May 2026 design that bakes the new names into the contract
* [vCon Library Quickstart](/vcon-library/quickstart) — the same migration as it applies to Python code


# MCP and Conserver Together

Different Roles, Working Together

The vCon ecosystem includes two main components: the MCP server and the conserver. They serve different purposes but work together to create a complete system for managing conversation data. This post explains what each does, how they differ, and how they combine with Redis and Supabase to optimize performance.

### Two Different Roles

The MCP server and conserver solve different problems in the vCon ecosystem.

**The MCP server** is a storage and query system. It provides an interface for AI assistants to work with conversation data. It focuses on storing, searching, and retrieving vCons through the Model Context Protocol.

**The conserver** is a processing and workflow engine. It focuses on creating and enriching vCons through chains of processing steps. It handles the intake of new conversations and runs them through workflows like transcription, analysis, and enrichment.

Think of it this way: the conserver is like a factory that creates and processes products, while the MCP server is like a warehouse where products are stored and retrieved. They work together but have different jobs.

### The MCP Server: Storage and Query

The MCP server is built in TypeScript and serves as the storage backend for conversation data. Its primary role is providing AI assistants with tools to work with stored conversations.

#### What It Does

The MCP server provides:

* Storage for vCon data in Supabase
* Search capabilities across stored conversations
* Query tools for AI assistants
* Analytics about your conversation database
* Management tools for organizing conversations

#### How It Works

When you use an AI assistant with the MCP server, the assistant can:

* Store conversations you create
* Search through historical conversations
* Analyze patterns in your data
* Answer questions about conversations
* Manage tags and organization

The server exposes 37 tools through the MCP protocol. AI assistants understand these tools and can use them to work with your conversation data. The server handles validation, database operations, and response formatting. See [Tool Reference](/mcp-server/tool-reference) for the full list.

#### Architecture

The MCP server uses a cache-first read strategy when Redis is configured. When you request a conversation:

1. It checks Redis cache first
2. If found in cache, returns immediately
3. If not found, fetches from Supabase
4. Caches the result in Redis for future requests
5. Returns the data

This makes reads very fast for frequently accessed conversations.

### The Conserver: Scaling Creation and Processing

The conserver is built in Python and serves as a workflow engine for processing conversations. Its primary role is taking in raw conversation data and running it through processing chains.

#### What It Does

The conserver provides:

* Processing chains that can transcribe, analyze, and enrich conversations
* Scalable processing through multiple instances
* Integration with external services like transcription APIs
* Storage backends that work with various databases
* Queue management for processing workflows

#### How It Works

The conserver processes conversations through configurable chains. A chain is a sequence of processing steps called links. For example, a chain might:

1. Receive a new conversation
2. Transcribe audio using Deepgram
3. Analyze sentiment using OpenAI
4. Extract key topics
5. Store the enriched conversation

You configure chains in a YAML file, defining what processing should happen and in what order. The conserver reads from Redis queues, processes conversations through the chains, and writes results to storage backends.

#### Scaling

The conserver scales by running multiple instances. You can start multiple conserver processes, and they all read from the same Redis queues. This allows you to process many conversations in parallel.

For example, if you have 100 conversations to process and each takes 30 seconds, one conserver instance would take 50 minutes. With 10 instances, it takes 5 minutes. You scale simply by starting more instances.

### How They Work Together

The MCP server and conserver complement each other:

**Conserver creates and processes** - It takes in raw conversation data, runs it through processing chains, and stores the enriched results.

**MCP server stores and queries** - It provides the storage backend and gives AI assistants tools to work with the stored data.

You can use conserver's Supabase storage backend, which stores data in the same Supabase database that the MCP server uses. This means:

* Conserver writes enriched conversations to Supabase
* MCP server can immediately query those conversations
* Both use the same Redis cache for performance
* Data flows seamlessly between systems

### The Redis and Supabase Combination

Using Redis and Supabase together optimizes performance throughout the conversation lifecycle. Here is how it works at each stage.

#### Stage 1: Creation and Processing

When conserver processes a conversation:

1. **Conserver receives** the conversation from an API or queue
2. **Conserver processes** it through chains (transcription, analysis, etc.)
3. **Conserver writes to Supabase** first (permanent storage)
4. **Conserver caches in Redis** (fast access)
5. **Conserver adds to queues** for further processing if needed

This is called a write-through cache pattern. Data is written to permanent storage first, then cached. This ensures data is never lost, even if Redis fails.

The conserver can run multiple instances in parallel, all writing to the same Supabase database. Redis coordinates the work by managing queues that distribute processing across instances.

#### Stage 2: Storage and Retrieval

When the MCP server retrieves a conversation:

1. **MCP server checks Redis** first (cache-first read)
2. **If found, returns immediately** (typically 1-2 milliseconds)
3. **If not found, fetches from Supabase** (typically 50-100 milliseconds)
4. **Caches result in Redis** for future requests
5. **Returns the data**

This is called a cache-first read pattern. It checks fast storage first, only going to slower storage when needed. This makes repeated reads very fast.

#### Stage 3: Updates and Deletes

When conversations are updated or deleted:

1. **Operation happens in Supabase** (permanent storage)
2. **Cache is invalidated in Redis** (removed or marked stale)
3. **Next read fetches fresh data** from Supabase and caches it

This ensures cached data stays consistent with permanent storage.

### Performance Benefits Throughout the Lifecycle

The Redis and Supabase combination provides speed optimizations at every stage:

**During creation** - Conserver writes to Supabase, then caches in Redis. This makes the conversation immediately available for fast reads while ensuring it is permanently stored.

**During processing** - Redis queues coordinate work across multiple conserver instances. This allows parallel processing without conflicts.

**During reads** - Cache-first reads make frequently accessed conversations load 20 to 50 times faster. A conversation read from cache takes 1-2 milliseconds instead of 50-100 milliseconds.

**During searches** - While search always queries Supabase for complete results, cached individual conversations load instantly when displayed.

**During updates** - Updates write to Supabase first, then invalidate cache. The next read gets fresh data and caches it, keeping everything consistent.

### A Complete Example

Let us trace a conversation through the entire system:

**Hour 1: Creation**

* A phone call comes in to your system
* Conserver receives the call recording
* Conserver processes it: transcribes audio, analyzes sentiment, extracts topics
* Conserver writes the enriched vCon to Supabase
* Conserver caches it in Redis with a 1-hour TTL
* The conversation is now stored and available

**Hour 2: First Access**

* You ask your AI assistant to find the conversation
* MCP server checks Redis cache (miss, it expired)
* MCP server fetches from Supabase (50ms)
* MCP server caches in Redis (1ms)
* MCP server returns to assistant (51ms total)

**Hour 3: Repeated Access**

* You ask about the same conversation again
* MCP server checks Redis cache (hit)
* MCP server returns immediately (1ms total)

**Hour 4: Update**

* You add analysis results to the conversation
* MCP server updates Supabase
* MCP server invalidates Redis cache
* The conversation is now updated permanently

**Hour 5: Search**

* You search for conversations about a topic
* MCP server searches Supabase (query always hits database)
* Results include the conversation you updated
* When you open individual results, they load from cache if recently accessed

### Configuration Overview

To use both systems together, you configure:

**Conserver configuration** (in config.yml):

```yaml
storages:
  supabase:
    module: storage.supabase
    options:
      url: ${SUPABASE_URL}
      anon_key: ${SUPABASE_ANON_KEY}
      redis_url: ${REDIS_URL}
      cache_ttl: 3600
```

**MCP server configuration** (in .env):

```bash
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_ANON_KEY=your-key
REDIS_URL=redis://localhost:6379
VCON_REDIS_EXPIRY=3600
```

Both use the same Supabase database and Redis cache, so data flows seamlessly between them.

### When to Use Each

Use the conserver when:

* You need to process many conversations at scale
* You want to run workflows like transcription and analysis
* You need to integrate with external processing services
* You are receiving conversations from APIs or queues

Use the MCP server when:

* You want AI assistants to work with conversation data
* You need to search and query stored conversations
* You want analytics about your conversation database
* You are building applications that interact with conversations

Most organizations use both:

* Conserver handles the creation and processing pipeline
* MCP server handles storage and AI assistant access
* They share the same Supabase database and Redis cache

### Summary

The MCP server and conserver serve different but complementary roles. The conserver scales the creation and processing of conversations through parallel instances and workflow chains. The MCP server provides storage and query capabilities for AI assistants.

Together with Redis and Supabase, they create an optimized system:

* Conserver writes to Supabase first, then caches in Redis (write-through)
* MCP server reads from Redis first, then Supabase if needed (cache-first)
* This combination makes the entire lifecycle fast while ensuring data durability
* Multiple conserver instances process in parallel while sharing the same storage
* AI assistants get fast access to all conversation data through the MCP server

The result is a system that can handle high-volume creation, complex processing, and fast queries, all while maintaining data consistency and durability.


# Business Cases for MCP Servers and vCon

This post explains why conversation data matters in business, what problems organizations face with it, and how MCP servers using the vCon format can help. It also covers specific use cases and considerations for implementation.

### Why Conversation Data Matters

Businesses have conversations with customers, partners, and employees every day. These conversations contain valuable information that can drive better decisions and outcomes.

**Customer insights** - Conversations reveal what customers care about, what problems they face, and how they feel about your products or services. This information is more direct and actionable than surveys or analytics.

**Operational intelligence** - Conversations show how well your team is performing, what processes work, and where improvements are needed. You can identify training opportunities, process bottlenecks, and quality issues.

**Compliance requirements** - Many industries must maintain records of certain conversations for regulatory compliance. Healthcare, finance, and legal services all have specific requirements.

**Relationship management** - Conversation history helps teams understand relationships over time. When a customer calls, agents can see past interactions and provide better service.

**Analytics and research** - Aggregated conversation data can reveal trends, patterns, and insights that inform strategy, product development, and marketing.

Despite this value, many organizations struggle to capture, organize, and use conversation data effectively.

### Problems with Conversation Data

Organizations face several common problems when working with conversation data:

**Data fragmentation** - Conversations happen across many systems. Phone calls are in one system, emails in another, chat messages in yet another, and video meetings somewhere else. Getting a complete picture requires checking multiple systems.

**Vendor lock-in** - Each system stores data in its own format. Moving data between systems is difficult, and switching vendors often means losing access to historical data or spending significant effort to migrate it.

**Limited search capabilities** - Most systems only support basic search by date, participant, or subject. Finding conversations by meaning, sentiment, or topic is difficult or impossible.

**Integration challenges** - Connecting conversation data to other business systems requires custom development. Each integration is a new project with ongoing maintenance costs.

**Compliance complexity** - Meeting regulatory requirements often means building custom solutions for consent tracking, data retention, redaction, and audit trails.

**AI integration difficulty** - AI assistants and analysis tools cannot easily access conversation data because it is locked in proprietary systems with limited APIs.

These problems make it hard to realize the full value of conversation data.

### How MCP Servers Solve These Problems

MCP servers provide a standard way for AI assistants to access conversation data. This solves several problems:

**Standardized access** - AI assistants understand the MCP protocol, so they can work with any MCP server without custom integration code. You can switch AI assistants or use multiple ones without rebuilding integrations.

**Natural language interaction** - Instead of writing code or learning specific APIs, you can ask AI assistants to work with your conversation data using plain language.

**Real-time capabilities** - MCP servers provide access to live data, not just historical snapshots. AI assistants can work with current information.

**Extensibility** - MCP servers can expose multiple tools, making them more capable over time without requiring changes to how assistants interact with them.

### The Value of the vCon Format

The vCon format adds additional value on top of MCP:

**Portability** - vCon is a standard format that works across systems. You can move conversation data between vendors or tools without losing information. You own your data in a format you can use anywhere.

**Completeness** - vCon captures all aspects of a conversation in one place. Participants, content, analysis results, attachments, and metadata are all included. You get a complete picture, not fragments scattered across systems.

**Interoperability** - Because vCon is a standard, different tools can work with the same data. A transcription service, an analysis tool, and a compliance system can all use the same vCon files.

**Future-proofing** - As new tools and services emerge, they can work with your vCon data because it follows a standard. You are not locked into today's technology choices.

**Compliance-ready** - The vCon format includes fields for consent tracking, privacy markers, and redaction. You can implement compliance features on top of standard data.

### Detailed Use Cases

Here are specific examples of how organizations use MCP servers with vCon for business value:

#### Contact Centers

Contact centers handle large volumes of customer interactions. They need to track issues, analyze performance, and maintain compliance.

**How they use it:** The center stores all customer calls in vCon format through an MCP server. Agents and managers can ask AI assistants questions like "What are the top three issues customers called about this month?" or "Show me calls where customers were frustrated."

**Value delivered:**

* Faster issue identification and resolution
* Better training based on actual customer interactions
* Automated compliance reporting
* Performance analytics without manual data collection

**Specific capabilities:**

* Search for calls by topic, sentiment, or issue type
* Generate summaries of common problems
* Track resolution rates and customer satisfaction
* Identify training opportunities for agents

#### Sales Teams

Sales teams have conversations with prospects and customers throughout the sales process. These conversations contain information about needs, objections, timelines, and decision criteria.

**How they use it:** Sales teams record calls and meetings in vCon format. They tag conversations with deal stages, product names, and customer segments. They can ask AI assistants to extract action items, identify decision makers, or find similar past deals.

**Value delivered:**

* Better deal tracking and forecasting
* Faster onboarding for new sales team members
* Insights into what messaging works
* Automatic extraction of next steps and commitments

**Specific capabilities:**

* Search for conversations about specific products or features
* Find similar past deals to inform strategy
* Extract action items and follow-up tasks automatically
* Analyze which approaches lead to closed deals

#### Compliance and Legal Teams

Legal and compliance teams must maintain records of regulated communications and ensure they meet retention, privacy, and audit requirements.

**How they use it:** Teams store all regulated communications in vCon format. Plugins add compliance features like automatic retention policy enforcement, consent tracking, and audit logging.

**Value delivered:**

* Automated compliance with regulations like GDPR, CCPA, and HIPAA
* Complete audit trails for regulatory reviews
* Efficient response to privacy requests
* Reduced risk of compliance violations

**Specific capabilities:**

* Automatic redaction of sensitive information
* Consent tracking and management
* Retention policy enforcement
* Audit log generation for regulators

#### Research and Analytics Teams

Research teams collect conversation datasets to study communication patterns, build training data for machine learning, or analyze language use.

**How they use it:** Teams store research conversations in vCon format, which provides a standard structure for analysis. They can export data in a format that works with analysis tools.

**Value delivered:**

* Standardized data format across research projects
* Easy integration with analysis tools
* Reproducible research with consistent data structures
* Sharing datasets with other researchers

**Specific capabilities:**

* Export conversations in standard formats
* Search and filter conversations by research criteria
* Anonymize data for sharing
* Track metadata about research participants

#### Healthcare Organizations

Healthcare organizations must document patient interactions while maintaining strict privacy and compliance standards.

**How they use it:** Organizations store patient consultation records in vCon format. Plugins add HIPAA compliance features, access controls, and integration with electronic health record systems.

**Value delivered:**

* Better documentation of patient interactions
* Compliance with healthcare regulations
* Integration with existing health record systems
* Improved care coordination through accessible records

**Specific capabilities:**

* Store consultation transcripts and notes
* Link conversations to patient records
* Automatic privacy controls and access restrictions
* Generate clinical summaries automatically

### ROI Considerations

Implementing an MCP server with vCon format can provide returns in several areas:

**Time savings** - Teams spend less time searching for information, exporting data, or switching between systems. AI assistants can answer questions that previously required manual work.

**Better decisions** - Access to conversation insights leads to better decisions about products, services, processes, and strategy.

**Compliance cost reduction** - Automated compliance features reduce the manual work and risk associated with meeting regulatory requirements.

**Integration cost reduction** - Standard formats reduce the cost of integrating conversation data with other systems. Instead of custom integrations for each system, you use standard formats.

**Vendor flexibility** - Not being locked into a single vendor allows you to choose tools based on what they do best, not just what integrates with your existing system.

**Future capability** - As new AI tools and services emerge, you can take advantage of them because your data is in a standard format.

### When to Use vCon MCP Server vs Alternatives

The vCon MCP Server is a good choice when:

* You need to work with conversation data across multiple systems or tools
* You want AI assistants to access your conversation data
* You value data portability and avoiding vendor lock-in
* You need to meet compliance requirements with conversation data
* You want to integrate conversation data with other business systems
* You are building applications that work with conversation data

Alternatives might be better when:

* You only use a single conversation system and do not need integration
* Your conversation volume is very small and does not justify the setup
* You have existing systems that meet all your needs and you do not plan to change
* You need features that are not yet available in the open source version

### Implementation Considerations

If you decide to implement the vCon MCP Server, here are things to consider:

**Database setup** - You will need a Supabase account or self-hosted PostgreSQL database. The free tier works for development and small deployments, but production may require a paid tier.

**AI assistant selection** - You need an AI assistant that supports MCP. Currently, Claude Desktop supports MCP, with more assistants adding support over time.

**Data migration** - If you have existing conversation data, you will need to migrate it to vCon format. The server includes tools and examples for this.

**Training** - Teams need to learn how to work with AI assistants and the vCon format. The learning curve is relatively gentle because you interact in natural language, but some training helps.

**Compliance plugins** - If you need compliance features, you may need proprietary plugins in addition to the open source server.

**Scaling** - For large deployments, consider caching with Redis and potentially running multiple server instances. The architecture supports scaling, but you need to plan for it.

**Integration** - Think about how the server fits into your existing systems. The server works well as part of a larger ecosystem, but you need to plan the integrations.

### Getting Started

If you want to try the vCon MCP Server:

1. Set up a Supabase account (free tier works for testing)
2. Install the server following the documentation
3. Connect Claude Desktop or another MCP-compatible assistant
4. Start with a small set of test conversations
5. Experiment with asking the assistant to work with your data
6. Gradually expand as you see value

The server is open source, so you can evaluate it without commitment. Many organizations start with a pilot project to understand the value before broader deployment.

### Conclusion

Conversation data is valuable, but realizing that value requires solving problems of fragmentation, vendor lock-in, and limited access. MCP servers provide a standard way for AI assistants to access conversation data, and the vCon format ensures data portability and completeness.

Organizations use this combination to improve customer service, sales effectiveness, compliance, research, and healthcare documentation. The value comes from better access to insights, automated compliance, reduced integration costs, and future flexibility.

If you want to learn more about implementing the vCon MCP Server, the earlier posts in this series cover the overview, MCP and AI integration, server capabilities, and architecture in detail.


# Tools

Standalone tools and adapters that produce, consume, or operate on vCons.

The vCon ecosystem is more than the spec and the libraries — it's a set of practical tools you can run today. This section indexes them.

## Generators

Tools that produce vCons.

* [vCon Faker](/tools/vcon-faker) — synthetic vCons from LLM-generated dialog + TTS audio
* [vCon Anthropic Chats](/tools/vcon-anthropic-chats) — converts Claude AI conversation exports into vCons
* [vCon SIPREC Adapter](/tools/vcon-siprec-adapter) — ingests SIPREC-formatted call recordings

## Administration & operations

Tools for managing vCons at scale.

* [vCon Admin](/tools/vcon-admin) — admin UI for browsing, editing, and exporting vCons
* [Mongo ↔ Redis Sync](/tools/mongo-redis-sync) — keep a Mongo vCon store and a Redis cache in sync
* [vCon MCP Adapters](/tools/vcon-mcp-adapters) — observability adapters (OpenTelemetry) for the MCP server

## Apps and stores

* [vCon Apps and Stores](/tools/vcon-apps-and-stores) — community-built apps and the vCon App Template

## Adding a tool

If you've built something that produces, consumes, or operates on vCons and you'd like it indexed here, open a pull request against [vcon-dev/vcon-docs](https://github.com/vcon-dev/vcon-docs) adding a new page under `tools/`. Keep entries to: what it does in one sentence, when you'd use it, install / link, and one minimal usage example.


# vCon Faker

Generate synthetic vCons with LLM-written dialog, TTS audio, and S3 upload — the standard way to create test data.

**Repo:** [vcon-dev/vcon\_faker](https://github.com/vcon-dev/vcon_faker) · **Streamlit app:** [vcon-faker.streamlit.app](https://vcon-faker.streamlit.app/)

`vcon_faker` generates realistic synthetic vCons. It pairs an LLM (OpenAI by default) to write the dialog with a TTS service to render audio, then assembles a vCon with the audio referenced as external media and uploaded to S3.

## When to use it

* Filling a fresh database with believable test vCons during development.
* Building a benchmark corpus for ASR providers.
* Demoing vCon-based workflows without exposing real customer data.
* Stress-testing pipelines.

## Use the synthetic-data consent pattern

Synthetic vCons should be auditably synthetic. The pattern:

1. **Mark every party as synthetic.** Set `validation: "synthetic"` on each party.
2. **Attach a `purpose: "synthetic_data_consent"` attachment** so downstream consumers can identify the vCon as synthetic in one tag-or-purpose lookup. The attachment body can be minimal — the existence of the purpose is the signal.

Example:

```json
{
  "parties": [
    { "name": "Alice", "role": "customer", "validation": "synthetic" },
    { "name": "Bob",   "role": "agent",    "validation": "synthetic" }
  ],
  "attachments": [
    {
      "purpose": "synthetic_data_consent",
      "party": 0,
      "dialog": 0,
      "encoding": "json",
      "body": "{\"origin\":\"vcon_faker\",\"prompt_version\":\"2026-05-18\"}"
    }
  ]
}
```

For full GDPR-style audit, *also* attach a [Lawful Basis](/extensions/lawful-basis) entry using `legitimate_interests` and `expiration: null`, documenting the synthetic origin in a `proof_mechanism` of type `external_system`. Both attachments together give you the strongest possible "this is not real personal data" signal.

## Running locally

The Streamlit app is the easiest entry point. Clone the repo, set `OPENAI_API_KEY` and S3 credentials in `.env`, and `streamlit run app.py`.

For programmatic generation, the underlying module is callable from Python — useful for batch generation in a notebook or pipeline.

## See also

* [Lawful Basis extension](/extensions/lawful-basis) — the spec-correct way to declare synthetic origin
* [Speech Recognition Test Set use case](/use-cases-studies/speech-recognition-test-set) — one of vcon\_faker's most common consumers


# vCon Anthropic Chats

Convert Claude AI conversation exports into vCons — the canonical adapter for LLM chat sessions.

**Repo:** [vcon-dev/vcon-anthropic-chats](https://github.com/vcon-dev/vcon-anthropic-chats) · **First release:** May 2026

A standalone adapter that takes Claude AI conversation exports — the JSON files you get from the Claude web app's export feature, or from the API — and converts them into spec-compliant vCons.

## When to use it

* You want to put your team's Claude conversations into the same store as your call recordings, emails, and chats.
* You're building a corpus of LLM interactions for audit, training, or compliance.
* You need to apply [Lawful Basis](/extensions/lawful-basis) consent to AI conversations the same way you do to human ones.

## What the output looks like

The adapter uses the [Agent Session extension](/extensions/agent-session), so an exported Claude conversation produces:

* A `parties[]` array with the human user and one or more agent parties (each agent gets `role: "agent"` and a `meta.agent_session` block identifying the model and provider).
* A `dialog[]` array with each message as a text dialog entry.
* An `analysis[]` entry of type `agent_trace` containing the full session trace (tool calls, tool results, reasoning) as a JSON-encoded VAC document.
* Optional `attachments[]` for files generated or modified during the session (purpose: `agent_file_change`, `agent_artifact`, etc.).
* An `extensions: ["agent_session"]` declaration.

## Install and usage

See the repo README for the current CLI. The typical invocation:

```bash
vcon-anthropic-chats < claude-export.json > conversation.vcon.json
```

For batch processing, the same module is usable as a Python import.

## See also

* [Agent Session extension](/extensions/agent-session) — the spec the adapter produces against
* [vCon Adapter Development Guide](/vcon-adapters/vcon-adapter-development-guide) — patterns for building your own adapters


# vCon SIPREC Adapter

Ingest SIPREC-formatted SIP recording streams and produce vCons with full signaling metadata.

**Repo:** [vcon-dev/vcon-siprec-adapter](https://github.com/vcon-dev/vcon-siprec-adapter)

SIPREC (RFC 7245 / RFC 7866) is the IETF standard for SIP-based call recording. This adapter consumes a SIPREC stream — both the recorded media and the signaling metadata — and produces a vCon with the [SIP Signaling extension](/extensions/sip-signaling) populated.

## When to use it

* You're running SIPREC in a contact center or carrier environment and want vCons as the durable output.
* You need STIR/SHAKEN attestation data, SIP Call-IDs, and SDP preserved alongside the recording for fraud investigation or TRACED Act compliance.
* You're correlating vCons with carrier-side CDRs.

## What you get

For each SIPREC session, the adapter produces a vCon with:

* A `dialog[]` entry of type `recording`, with the media as external (URL + SHA-512 hash) and the SIPREC-specific dialog fields populated (`sip_call_id`, `sip_from_tag`, `sip_to_tag`, `sip_cseq`).
* A `parties[]` array with `sip`, `sip_contact`, `sip_user_agent`, and `sip_display_name` for each participant.
* `attachments[]` containing the raw INVITE, the SDP, any STIR PASSporTs, and STIR verification reports — each tagged with the appropriate `purpose:` (`sip-invite`, `sip-sdp`, `stir-passport-extended`, etc.).
* `extensions: ["sip-signaling"]`.

## Spec status

The adapter is part of the May 2026 post-speckit-re-audit batch (commit history shows the field-name compliance pass on 2026-05-10). Output matches the current [SIP Signaling extension](/extensions/sip-signaling) draft.

## See also

* [SIP Signaling extension](/extensions/sip-signaling) — the spec the adapter produces against
* [Authenticating and Certifying Conversations](/use-cases-studies/authenticating-and-certifying-conversations) — the STIR/SHAKEN integration story


# vCon Admin

Admin UI for browsing, editing, and exporting vCons in a conserver-backed deployment.

**Repo:** [vcon-dev/vcon-admin](https://github.com/vcon-dev/vcon-admin)

`vcon-admin` is a web UI for the conserver and its storage backends. Use it when you need to:

* Browse the vCons in a database without writing SQL or a custom UI.
* Inspect a vCon's full JSON.
* Edit metadata on a vCon (tags, subject, custom fields).
* Export selected vCons to JSON.
* Run ad-hoc searches.

It's a complementary tool to the [conserver](/conserver) — the conserver runs the data pipeline, vcon-admin gives humans a window into the result.

## Install

See the repo for current install instructions. The typical deployment is a Docker container alongside a conserver instance, pointing at the same storage backend.

## When NOT to use vcon-admin

* For LLM-driven querying, the [vCon MCP Server](/mcp-server) is the right surface.
* For pipeline orchestration (transcribe → analyze → store), the [conserver](/conserver) is what you want; vcon-admin is read/edit, not workflow.

## See also

* [Conserver](/conserver)
* [vCon MCP Server](/mcp-server) — programmatic / LLM-facing alternative


# Mongo Redis Sync

Keep a MongoDB vCon store and a Redis cache in sync — useful for hybrid deployments.

**Repo:** [vcon-dev/mongo-redis-sync](https://github.com/vcon-dev/mongo-redis-sync)

A small utility that mirrors vCons between a MongoDB long-term store and a Redis cache. Useful when:

* You're using Mongo as durable storage but the conserver pipeline expects vCons in Redis.
* You're migrating a deployment between the two and want bidirectional sync during the transition.
* You want hot vCons in Redis for low-latency reads while keeping the canonical copy in Mongo.

This tool predates much of the current conserver storage layer and is most useful for deployments that pre-date the unified Supabase backend used by `vcon-mcp`. For new deployments, prefer the conserver's native storage configuration — see [Storage](/conserver/storage).

## See also

* [Conserver Storage](/conserver/storage)
* [Production Deployment](/conserver/production-deployment)


# vCon MCP Adapters

Observability adapters for the vCon MCP server — OpenTelemetry tracing for tool calls.

**Repo:** [vcon-dev/vcon-mcp-adapters](https://github.com/vcon-dev/vcon-mcp-adapters) · **v0.2 released:** May 2026

A collection of observability adapters for the [vCon MCP server](/mcp-server). The flagship is an OpenTelemetry tracing integration that emits spans for every tool call, capturing:

* Tool name, arguments (with optional redaction)
* Latency, cache hits/misses
* Errors and validation failures
* The client identity (model, agent name) when the MCP transport surfaces it

## When to use it

* You're running the MCP server in production and want trace data flowing into your existing observability stack (Datadog, Honeycomb, Grafana Tempo, etc.).
* You're debugging an LLM client that's making a lot of MCP calls and want to see which ones, in what order, with what arguments.
* You're tracking model-by-model usage of vCon tools for capacity planning or cost attribution.

## Install

See the repo README. The typical deployment is a small wrapper around the MCP server's tool dispatch path that emits OTLP spans to whatever collector you're already running.

## What's new in v0.2 (May 2026)

* Spans now include the May 2026 contract-tool family (`vcon_fetch`, `vcon_search`, `vcon_capabilities`, `vcon_taxonomy`, `describe_response_shape`).
* Argument redaction is now configurable per-tool, so you can keep argument payloads out of trace storage for tools that touch personal data.
* Cache attributes (hit, miss, bypass) are now emitted for tools that have caching.

## See also

* [vCon MCP Server overview](/mcp-server)
* [Tool Reference](/mcp-server/tool-reference) — what the spans are tracing


# vCon Apps and Stores

## The vCon Store: Building an Open Ecosystem for Conversational Applications

### Introduction: From Infrastructure to Innovation

The history of telecommunications reveals a profound truth: what we perceive as infrastructure today often began as revolutionary applications. Alexander Graham Bell's telephone wasn't conceived as the backbone of global communication—it was commissioned by Samuel Gridley Howe, president of the Perkins School for the Blind, as an assistive device for the blind, built atop existing telegraph infrastructure. This pattern of application-driven innovation has repeated throughout telecommunications history, from Interactive Voice Response (IVR) systems that businesses once had to be convinced to adopt, to prepaid calling cards that drove massive traffic in the telecommunications field during the 1970s and 80s, each representing an attempt to unlock new value from voice communications.

Even the early SIP phone initiatives, despite sophisticated technical foundations, struggled to find compelling applications that resonated with users. The iPhone represented what many thought would finally deliver true phone applications, but the reality proved different—the iPhone succeeded not as a phone with applications, but as a handheld computer where the phone function became just one capability among many. Notably, until very recently, most applications on smartphones weren't truly "phone applications" that leveraged voice communication as their core functionality.

Today, we stand at a similar inflection point with vCons (virtual Conversation records) and the emerging ecosystem of conversational applications. What we're seeing from the vCon application community is genuinely stunning—sophisticated dashboards, business intelligence tools, and AI-powered insights that were previously impossible. The question isn't whether voice applications will evolve—it's how we can architect systems that enable innovation rather than constrain it.

### Understanding vCons: A PDF for Conversations

Before exploring the store architecture, it's essential to understand what makes vCons uniquely powerful. A vCon functions as "a PDF for conversations"—a standardized, tamper-proof, signed, and encryptable JSON document that captures the complete context of any communication interaction. Each vCon contains four key components:

**Parties** - The identities of all participants in the conversation, providing clear attribution and context for every interaction.

**Dialogs** - The actual content of what was communicated, whether through voice, video, or text, preserving the full conversational record.

**Analysis** - Automated insights from AI and machine learning systems that track sentiment, extract key topics, identify action items, and provide business intelligence.

**Attachments** - Supporting and associated data including documents, images, or any files relevant to the conversation.

This structure creates a complete, portable record of human communication that can be processed, analyzed, and acted upon by applications while maintaining data integrity and authenticity.

### The Application Evolution: Why Documents Trump APIs

The telecommunications industry has consistently struggled with the tension between innovation and integration complexity. While early attempts at voice applications often failed due to architectural limitations, today's vCon-based applications are demonstrating remarkable success. The key insight driving this success comes from years of sponsoring vCon mashup competitions with the TadHack community: **vCon mashups are becoming dominant not just because vCons are cool (though they are), but because a document is easier than an API.**

This principle explains why vCon applications are gaining traction where previous voice application attempts failed. When developers can base applications on standardized document formats rather than navigating complex API integrations, the barrier to entry drops dramatically. More people can participate, tools can more easily process the data, and innovation accelerates. The fact that vCons exist as files rather than API endpoints represents what might be called "accidental magic"—a design decision that unlocks unprecedented accessibility for application developers.

The file-based nature of vCons means integration becomes as simple as "file URL to file"—a pattern that's easy to test, easy to describe, and works seamlessly across different systems and platforms.

### Architectural Foundation: The Model-View-Controller for Conversations

Modern application development relies heavily on the Model-View-Controller (MVC) architecture, which separates concerns into three distinct layers: the data model, the business logic controller, and the presentation view. This separation allows developers to create applications that work consistently across mobile, web, and messaging interfaces while maintaining a single source of truth for data and logic.

<figure><img src="/files/scRMVYwkyu6z54BMl0ii" alt=""><figcaption></figcaption></figure>

The vCon ecosystem maps naturally onto this proven architecture. Instead of traditional database models, conversations become the foundational data layer. The same controller patterns that manage business logic and access controls apply seamlessly to conversational data. Views can range from dashboards and reports to AI-powered interfaces, all drawing from the same conversational foundation.

<figure><img src="/files/TlMosjE5nNKSDjhHzAsq" alt=""><figcaption></figcaption></figure>

This architectural alignment isn't coincidental—it represents a maturation of conversational technology that makes it compatible with standard development practices and tools.

### Advanced Architecture: The Conserver System

The technical implementation of vCon hosting involves sophisticated architecture that balances performance, security, and accessibility. The Conserver system represents the middleware layer that makes the vCon store ecosystem possible, handling everything from real-time processing to long-term storage and consent management.

The Conserver architecture employs a processing pipeline with multiple specialized components. Incoming vCons flow through analysis, transcription, and large language model processing before being distributed to various storage systems. High-speed access through Redis enables real-time applications, while long-term storage in systems like S3 provides cost-effective archival. The system also supports webhook notifications for real-time application updates and integrates with various AI services through standardized interfaces.

<figure><img src="/files/naoHINuG9ZWyCiq0GgBE" alt=""><figcaption></figcaption></figure>

**Model Control Protocol (MCP)** integration represents a particularly innovative aspect of the architecture. As MCP emerges as a standard for AI system integration, vCon hosters can provide MCP interfaces as a standard connection point for their customers' existing AI ecosystems. Whether integrating with OpenAI, Claude, Watson X, or other AI platforms, the MCP interface provides a consistent integration pattern that eliminates the need for custom API development.

The system also includes **SCITT (Supply Chain Integrity, Transparency and Trust) compatible ledger** capabilities for enhanced security and auditability. This blockchain-inspired approach ensures that vCon records maintain integrity and provide verifiable audit trails—critical for regulatory compliance and trust verification in sensitive business communications.

For consent management, the architecture implements comprehensive privacy controls with real-time enforcement. When a data subject revokes consent, the system can immediately propagate deletion requests through all storage layers, from high-speed caches to long-term archives, ensuring compliance with privacy regulations like GDPR.

### The vCon Store: A Four-Component Ecosystem

<figure><img src="/files/AcuAtEKX25Lgq2mlphlH" alt=""><figcaption></figcaption></figure>

The proposed vCon store architecture consists of four essential components, each serving a distinct role while maintaining clear boundaries and responsibilities:

**vCon Creators** form the foundation, encompassing all systems that generate conversational records—phone systems, email platforms, chat applications, voice automation systems, and emerging AI agents. Legacy equipment can participate through vCon adapters, while native voice suppliers and modern communication systems can generate vCons directly. The diversity of creator types ensures that virtually any communication platform can participate in the ecosystem.

**vCon Hosters** serve as the custodians of conversational data, operating as data controllers responsible for storage, protection, and access management. These entities bear the crucial responsibility of data rights protection, consent management, and compliance with privacy regulations. By centralizing these concerns with specialized providers, the architecture allows other ecosystem participants to focus on their core competencies without becoming privacy law experts.

The hosting function includes sophisticated data management capabilities: enterprise-grade databases, cloud services integration, real-time processing capabilities, and comprehensive audit trails. Hosters can integrate with existing business systems through webhooks, APIs, and emerging standards like MCP (Model Control Protocol).

**Data Subjects** retain ultimate control over their conversational data through managed consent mechanisms. The architecture ensures clear accountability—data subjects know exactly who has access to their conversations and why, with straightforward mechanisms for consent withdrawal that cascade through all system components.

**vCon-Enabled Applications** represent the innovation layer, where developers create value-added services without needing to worry about data storage, privacy compliance, or integration complexity. These applications connect to data stores populated by Conservers using familiar database interfaces and development patterns. Application categories include voice analytics dashboards, compliance monitoring systems, customer intelligence platforms, fraud detection tools, and custom enterprise applications tailored to specific business needs.

### Breaking Down Walled Gardens

The current communications landscape suffers from what Mark Twain might recognize as history rhyming with itself. Just as AOL once controlled users' access to news, weather, and sports by being the single gateway to information, today's communication platforms often capture conversations, analyze them, and control application access within closed ecosystems.

<figure><img src="/files/PBldgv7wm0mXZGCNa3IV" alt=""><figcaption></figcaption></figure>

The vCon store architecture offers an alternative path—one that separates concerns and prevents any single entity from controlling the entire value chain. In this open ecosystem, hosters can choose their level of openness, service providers can focus on core capabilities without managing hundreds of API integrations, and application developers can reach users through standardized interfaces rather than platform-specific implementations.

This separation creates a truly competitive marketplace where each component can excel at its specific function while participating in a larger, interoperable ecosystem.

### Practical Implementation: From Theory to Reality

Real-world implementations demonstrate the practical viability of this architecture, with applications being developed in remarkably short timeframes. Enterprise applications using vCons stored in Snowflake and accessed through Python Jupyter notebooks can be developed in minutes rather than months. The "vCon Quality Report" dashboard—complete with quality metrics, conversation analytics, and even a patron saint of quality data (Saint Vincenzo)—was actually created as a joke during a team standup meeting, yet provides genuine business value by tracking vCon creation rates, summarization progress, and system performance.

<figure><img src="/files/SylFf1WHUrNso6bzsj4D" alt=""><figcaption></figcaption></figure>

The quality report shows practical metrics like daily vCon generation (6,752 vCons on the day measured, down 20% because it was Sunday), summarization rates approaching 100%, and detailed analytics on conversation duration and patterns. This level of business intelligence, traditionally requiring extensive custom development, becomes straightforward when working with vCons as standardized data files.

Similarly, small business applications like conversational diaries can be built using standard tools: MongoDB for storage, OpenAI for processing insights, and Streamlit for user interfaces. These applications provide immediate value by summarizing daily conversations, extracting action items, and identifying business opportunities. For example, a simple diary application can show "what happened today," list actionable next steps, and highlight potential opportunities—all derived automatically from the day's conversational data.

The BMW dealership example showcases how conversational intelligence can transform business operations, providing detailed summaries of customer interactions, agent performance metrics, and actionable insights for improving service delivery. The system tracked 168 calls on a single day, breaking down agent performance and identifying specific customer needs and opportunities.

### The Strategic Imperative

The vision for vCon stores extends beyond technical architecture to market strategy. By focusing on file-based standards rather than proprietary APIs, the ecosystem can support much wider participation and generate better results for all participants. Hosters can focus on their core competency of secure, compliant data management. Application developers can create innovative solutions without becoming integration specialists. End users benefit from choice, interoperability, and innovation.

<figure><img src="/files/jTGvivIdEr4MPwx115DM" alt=""><figcaption></figcaption></figure>

This approach promises to unlock the same kind of explosive growth that occurred when the internet broke down AOL's walled garden, giving users access to unlimited sources of information and services rather than a single provider's curated selection.

### Conclusion: Files as the Foundation of Innovation

The telecommunications industry has repeatedly demonstrated that breakthrough applications drive infrastructure evolution, not the reverse. Today's vCon ecosystem represents the latest iteration of this pattern, with document-based architectures enabling a new generation of conversational applications.

The proposed vCon store architecture offers a path forward that balances innovation with responsibility, openness with security, and simplicity with capability. By treating conversations as files and building standard architectures around them, we can create an ecosystem where innovation flourishes while protecting the rights and interests of all participants.

The future of conversational applications lies not in more complex APIs or tighter platform integration, but in simpler, more open architectures that let developers focus on creating value rather than managing complexity. The vCon store represents a concrete step toward that future—one file at a time.


# TADHack vCon

## Conversation Set

For this year's TADHack vCon Hackathon, we've generated a set of synthetic vCons for your use:

* You can download the set at <https://github.com/vcon-dev/tadhack-2025>
* An S3 Bucket is here: arn:aws:s3:::tadhack-vcons

## Overview

This dataset contains customer service conversation data from Aquidneck Yacht Brokers in VCON (Virtual Call Object Notation) format. The conversations span from May 18-24, 2025, and represent typical customer interactions for a yacht brokerage company. The dataset includes 42 customer service calls between Aquidneck Yacht Brokers agents and customers, covering various marine industry-specific support scenarios.

### Conversation Types

#### 1. Returns & Refunds

* Customers requesting returns for yacht equipment
* Processing refund requests
* Emotional customers (often expressing sadness about returns)

#### 2. Shipping & Logistics

* Yacht transportation inquiries (e.g., Fort Lauderdale to Newport)
* Delivery status updates
* Shipping cost questions

#### 3. Order Issues

* Wrong items received (e.g., yacht anchor instead of navigation system)
* Missing order investigations
* Order verification and corrections

#### 4. Equipment Support

* GPS malfunction troubleshooting
* Navigation system issues
* Equipment compatibility questions

#### 5. Business Services

* Yacht listing inquiries
* Brokerage service questions
* Pricing and commission discussions

#### 6. Account Management

* Membership cancellations
* Billing inquiries
* Privacy and data concerns
* Contact information updates

#### 7. Appointments & Scheduling

* Yacht viewing appointments
* Service scheduling
* Consultation bookings

### Call Characteristics

* **Average Duration**: 50-60 seconds
* **Call Disposition**: All marked as "ANSWERED" with "VM Left" status
* **Language**: English
* **Transcription Confidence**: 99%
* **Professional Tone**: Agents maintain consistent, helpful demeanor
* **Resolution Rate**: Most issues resolved or appropriately escalated

### Data Format

Each conversation includes:

* Audio recording (MP3 format)
* Full transcript with speaker diarization
* AI-generated summary
* Participant metadata (names, roles, contact info)
* Call metadata (duration, timestamp, disposition)

### Typical Interaction Flow

1. Agent greeting with company name and agent introduction
2. Customer name verification
3. Issue description by customer
4. Information gathering (order numbers, email verification)
5. Resolution or escalation
6. Professional closing

### Notable Patterns

* Customers frequently express emotions related to their issues
* Agents consistently follow verification protocols
* Marine industry-specific terminology used throughout
* Focus on high-value transactions typical of yacht brokerage

This dataset provides realistic examples of customer service interactions in the luxury marine industry, useful for training, analysis, or demonstration purposes.


# Deep Dives


# vCon Lifecycle Management using SCITT

A Privacy-First Approach to Conversation Data Management

> **Spec note:** This whitepaper describes the rationale and operating model for SCITT-anchored vCon lifecycle management. The corresponding spec is [`draft-howe-vcon-lifecycle`](https://datatracker.ietf.org/doc/draft-howe-vcon-lifecycle/); for the spec-surface and event vocabulary, see [Lifecycle extension](/extensions/lifecycle).

## Executive Summary

Organizations across industries are grappling with an unprecedented challenge: how to extract value from conversational data while maintaining strict privacy compliance and earning consumer trust. Every day, billions of conversations occur through phone calls, video conferences, chat systems, and email exchanges, generating valuable insights for customer service, artificial intelligence training, and business intelligence. However, this same data contains sensitive personal information subject to increasingly stringent privacy regulations worldwide.

Traditional approaches to conversation data management create fragmented systems where data, consent records, and compliance information exist in isolated silos. When individuals exercise their privacy rights—requesting access to their data, corrections to inaccurate information, or complete deletion—organizations often struggle to provide comprehensive responses or coordinate actions across multiple systems.

This whitepaper introduces a revolutionary approach that combines Virtualized Conversations (vCons) with Supply Chain Integrity, Transparency, and Trust (SCITT) protocols to create a comprehensive framework for privacy-first conversation data management. This approach embeds consent management directly into conversation containers, creates immutable audit trails, and enables automated compliance across distributed systems.

The implications extend far beyond technical implementation. Organizations adopting this framework can transform privacy from a compliance burden into a competitive advantage, building customer trust through unprecedented transparency while enabling responsible innovation in artificial intelligence and data analytics.

### The Privacy Imperative in Conversation Data

The landscape of privacy regulation has fundamentally shifted over the past decade. The European Union's General Data Protection Regulation (GDPR), California's Consumer Privacy Act (CCPA), and similar laws worldwide have established individual rights that organizations must respect and protect. These regulations are not merely compliance checkboxes; they reflect a societal expectation that individuals should maintain meaningful control over their personal information.

Conversational data presents unique challenges within this regulatory framework. Unlike static customer records or transaction data, conversations are dynamic, multi-party interactions that may be processed by various systems over time. A single customer service call might be recorded by a telephony system, transcribed by an AI service, analyzed for sentiment by another platform, and used to train machine learning models by a fourth system. Each step in this process requires explicit consent and creates obligations for data protection.

The complexity multiplies when considering the temporal aspects of consent. Privacy regulations establish that consent is not a one-time agreement but an ongoing relationship that individuals can modify or revoke at any time. When a customer withdraws consent for AI training, every system that has processed their conversation data must respond appropriately. Currently, most organizations lack the infrastructure to identify where data exists, verify the authority of deletion requests, or coordinate responses across multiple systems.

Furthermore, the global nature of modern business means that conversation data often crosses jurisdictional boundaries, each with distinct privacy requirements. An international company might have customer data processed in European data centers, analyzed by AI systems in North America, and stored in cloud infrastructure spanning multiple continents. Managing consent and compliance across this distributed landscape requires standardized approaches that can operate across different legal frameworks.

The stakes of privacy compliance extend beyond regulatory fines, though these can be substantial. Organizations face reputational risks, competitive disadvantages, and operational disruptions when privacy incidents occur. More fundamentally, privacy protection has become a consumer expectation and market differentiator. Companies that can demonstrate transparent, responsible data practices gain customer trust and loyalty in an increasingly privacy-conscious marketplace.

### Understanding Virtualized Conversations (vCons)

Virtualized Conversations, or vCons, represent a paradigm shift in how conversational data is structured, stored, and shared. Rather than treating conversation elements as disparate pieces of information scattered across multiple systems, vCons create standardized containers that keep all related data together in a cohesive, portable format.

The vCon specification defines a comprehensive framework for conversation representation that accommodates the full spectrum of modern communication modalities. Whether the interaction occurs through traditional telephony, video conferencing platforms, instant messaging, email threads, or emerging communication channels, vCons provide a consistent structure for capturing and organizing the associated data.

At its core, a vCon contains four primary components, each serving distinct purposes in conversation data management. The parties section identifies all participants in the conversation, including detailed contact information, roles, and relationships. This goes beyond simple name and phone number records to include structured data about participant authority, organizational affiliations, and communication preferences. The dialog section captures the actual conversation content in its original form, whether audio recordings, video files, text transcripts, or multimedia messages. This raw conversational data maintains its integrity and authenticity while being packaged in a standardized format that any compatible system can process.

The analysis section houses derived insights and transformations of the original conversation data. This might include automatically generated transcripts, sentiment analysis results, topic extraction, speaker identification, or any other algorithmic processing of the conversation content. Importantly, the analysis section maintains clear linkages to the original dialog data, enabling audit trails and verification of analytical accuracy. The attachments section provides space for supplementary materials related to the conversation, such as documents shared during the interaction, consent forms, digital signatures, or other contextual information that enhances understanding of the conversational context.

Beyond these core components, vCons include comprehensive metadata that enables sophisticated conversation lifecycle management. Unique identifiers ensure that conversations can be tracked and referenced across systems and time periods. Timestamps capture not only when conversations occurred but also when various processing steps were completed. Version control mechanisms support conversation evolution, allowing organizations to update or append information while maintaining historical integrity.

The standardization aspect of vCons cannot be overstated in its importance. Currently, every communication platform, customer relationship management system, and analytics tool uses proprietary formats for storing conversational data. This fragmentation creates vendor lock-in, complicates data migration, and makes comprehensive privacy management nearly impossible. vCons establish a lingua franca for conversational data that enables interoperability between systems, facilitates vendor independence, and supports comprehensive lifecycle management.

Moreover, vCons are designed to be both human-readable and machine-processable. The underlying JSON format makes vCon contents accessible to developers and auditors while maintaining compatibility with modern software architectures. This dual accessibility supports both automated processing workflows and manual review processes required for compliance verification.

### Supply Chain Integrity, Transparency, and Trust (SCITT)

Supply Chain Integrity, Transparency, and Trust (SCITT) represents a breakthrough in creating verifiable, tamper-evident records of digital events and transactions. While originally conceived for supply chain transparency, SCITT's principles and architecture prove remarkably well-suited to privacy and consent management challenges.

Traditional database systems, even those with strong security controls, operate on a paradigm where records can be modified, updated, or deleted by authorized users. While this flexibility supports operational needs, it creates challenges for compliance and auditing. How can an organization prove to a regulator that consent records haven't been modified after the fact? How can individuals trust that their data deletion requests were actually honored rather than simply marked as deleted in a database that could be reversed?

SCITT addresses these challenges through an append-only ledger architecture that creates permanent, verifiable records of events. Once an entry is added to a SCITT transparency service, it becomes mathematically impossible to alter or remove that entry without detection. This immutability creates a foundation of trust that benefits all parties involved in privacy and consent management.

The transparency service operates through a distributed network of nodes that collectively maintain the integrity of the ledger. When an organization wants to record a consent decision, data processing event, or compliance action, they submit a signed statement to the transparency service. The service validates the statement, adds it to the permanent ledger, and returns a cryptographic receipt that proves the statement was recorded at a specific time.

These receipts serve multiple critical functions in privacy management. For organizations, receipts provide irrefutable proof that they have documented consent decisions, recorded data processing activities, and responded to individual rights requests. For individuals, receipts offer verification that their privacy choices have been officially recorded and cannot be disputed later. For regulators, receipts enable efficient audit processes where compliance can be verified through cryptographic proof rather than extensive document review.

The cryptographic foundations of SCITT ensure that statements and receipts cannot be forged or manipulated. Digital signatures verify the identity of entities making statements, while hash functions and Merkle tree structures ensure that any attempt to modify historical records would be immediately detectable. This mathematical certainty removes the need for trust relationships between parties, as the protocol itself provides verification mechanisms.

Importantly, SCITT's transparency doesn't mean that all information becomes publicly visible. The system can operate with privacy-preserving mechanisms where the existence and timing of events are transparent while the content remains confidential to authorized parties. This balance enables public verifiability of compliance processes while protecting sensitive personal information.

The distributed nature of SCITT also provides resilience against single points of failure or malicious actors. Unlike centralized systems where a single breach or system failure could compromise all records, SCITT's distributed ledger ensures that transparency and verification capabilities remain available even if individual nodes are compromised or become unavailable.

### Integrating vCons with SCITT for Comprehensive Lifecycle Management

The integration of vCons with SCITT creates a powerful framework that addresses the complete lifecycle of conversational data while maintaining privacy protection and regulatory compliance. This integration operates on multiple levels, from initial consent collection through final data deletion, creating an end-to-end system for responsible conversation data management.

The lifecycle begins with conversation initiation and consent collection. Modern communication systems can automatically detect when conversations involve personal data and trigger appropriate consent collection mechanisms. Rather than treating consent as a separate, disconnected process, the vCon framework embeds consent information directly into the conversation container through standardized consent attachments. These attachments capture not only the consent decisions but also the context in which consent was given, the specific purposes for which consent was granted, and any limitations or conditions on that consent.

When consent is collected, the decision is immediately recorded in a SCITT transparency service, creating an immutable timestamp and cryptographic proof of the consent event. This recording includes essential metadata such as the identity of the consenting party, the specific purposes for which consent was granted, any expiration dates or renewal requirements, and references to applicable terms of service or privacy policies. The SCITT receipt provides immediate verification that the consent was properly recorded and cannot be disputed later.

As conversations are processed and enhanced through various systems, each step in the data lifecycle is documented through additional SCITT entries. When a conversation is transcribed by an AI service, analyzed for sentiment, or used to train machine learning models, these processing events are recorded with references to the original consent authorizations. This creates a comprehensive audit trail that demonstrates compliance with consent limitations and provides visibility into how personal data has been used.

The embedded nature of consent information in vCons ensures that privacy constraints travel with the data itself. When a vCon is shared between organizations or processing systems, the receiving party can immediately verify the consent status and any limitations on data use. This eliminates the common scenario where data is shared without proper consent verification, as the consent information is integral to the data container itself.

Consent management through this integrated system supports the full spectrum of individual privacy rights. When someone requests access to their conversation data, the SCITT transparency service provides a complete audit trail of all processing activities, enabling organizations to provide comprehensive responses. When consent is modified or revoked, the change is recorded in SCITT and propagated to all systems holding the relevant vCons, ensuring coordinated compliance across distributed architectures.

The temporal aspects of consent are particularly well-served by this integration. Privacy regulations often require that consent be renewed periodically, especially for sensitive data processing activities. The vCon consent framework supports configurable verification intervals based on data sensitivity and regulatory requirements. High-sensitivity medical or financial conversations might require daily consent verification, while routine customer service interactions might require weekly or monthly checks. The SCITT transparency service ensures that these verification activities are documented and that any changes in consent status are immediately visible to all relevant systems.

Perhaps most importantly, this integration provides mechanisms for proving compliance to regulators and auditors. Rather than requiring organizations to produce extensive documentation during privacy audits, regulators can verify compliance through cryptographic proof provided by SCITT receipts. This transforms regulatory compliance from a document-intensive process to an automated verification system that benefits both organizations and oversight bodies.

### Consent Attachments: Embedding Privacy Protection in Data

The concept of consent attachments represents a fundamental innovation in how privacy protections are implemented in practice. Rather than treating consent as metadata stored separately from personal data, consent attachments embed privacy controls directly into conversation containers, ensuring that consent information travels with data throughout its entire lifecycle.

Traditional consent management systems create significant operational challenges because consent records are typically stored in centralized databases or identity management systems, separated from the data they govern. This separation creates multiple points of failure in privacy protection. Data might be processed without proper consent verification, shared between systems without privacy constraints, or retained beyond consent expiration dates because the processing systems lack visibility into consent status.

Consent attachments solve these problems by making privacy protection an integral property of the data itself. Each vCon can contain one or more consent attachments that specify exactly what processing activities are authorized, which parties have granted consent, and what limitations or conditions apply to data use. These attachments use structured metadata formats that both humans and automated systems can understand and enforce.

The structure of consent attachments accommodates the complexity of modern privacy requirements. Rather than simple binary consent decisions, the attachments support granular permissions that can vary by purpose, time period, and processing activity. For example, a customer might consent to conversation recording for quality assurance purposes but decline consent for AI training applications. The consent attachment captures these nuanced decisions and enables automated systems to respect the specified limitations.

Temporal management within consent attachments addresses the dynamic nature of privacy consent. The attachments include expiration timestamps that specify when consent expires and requires renewal. They also support indefinite consent with periodic revalidation requirements, accommodating different regulatory frameworks and organizational policies. When consent expires or is revoked, systems processing the vCon can immediately detect the status change and respond appropriately.

The cryptographic foundations of consent attachments ensure their authenticity and integrity. Digital signatures verify that consent decisions came from authorized parties and haven't been tampered with during data processing or transmission. Hash functions and integrity checks prevent unauthorized modifications to consent decisions, ensuring that privacy protections cannot be circumvented through technical manipulation.

Integration with emerging privacy standards further enhances the utility of consent attachments. The framework supports the AI Preferences vocabulary developed by the Internet Engineering Task Force, enabling standardized expression of consent for artificial intelligence and machine learning applications. This standardization is particularly important as AI governance regulations emerge worldwide, requiring organizations to demonstrate explicit consent for AI training and automated decision-making systems.

Consent attachments also support multiple proof mechanisms that accommodate different organizational and regulatory requirements. Cryptographic proofs provide mathematical certainty for high-security environments, while documented consent processes support traditional compliance frameworks. The attachments can reference external consent forms, embed digital signatures, or document verbal consent given during conversations, providing flexibility while maintaining verification capabilities.

The practical implementation of consent attachments transforms how organizations approach privacy compliance. Instead of requiring separate consent verification processes before each data use, systems can automatically check consent status by examining the embedded attachments. This automation reduces compliance burden while improving privacy protection, as consent verification becomes a natural part of data processing workflows rather than an additional overhead.

### Privacy Rights and Automated Compliance

The automation of privacy rights fulfillment represents one of the most significant practical benefits of integrating vCons with SCITT transparency services. Traditional approaches to privacy rights management rely heavily on manual processes that are slow, error-prone, and difficult to scale. The integrated framework enables automated responses to privacy requests while maintaining the accuracy and verifiability required for regulatory compliance.

When individuals exercise their right to access personal data, traditional systems require manual searches across multiple databases, applications, and archive systems. This process is time-consuming, expensive, and often incomplete, as organizations struggle to identify all systems that might contain relevant data. The vCon framework fundamentally changes this dynamic by creating comprehensive conversation records that include complete audit trails of data processing activities.

The SCITT transparency service acts as a central index of all conversation-related activities, enabling automated identification of relevant data in response to access requests. When someone requests information about how their conversation data was processed, the system can query the transparency service to identify all vCons containing their information, trace all processing activities performed on that data, and generate comprehensive reports without manual intervention.

Data portability rights, which require organizations to provide personal data in machine-readable formats, are naturally supported by the vCon framework. Since vCons use standardized JSON formats with well-defined schemas, individuals can receive their conversation data in formats that are both human-readable and compatible with other systems. This eliminates the common practice of providing data exports in proprietary formats that are difficult to use or transfer to other services.

The right to rectification, which allows individuals to correct inaccurate personal data, benefits from the immutable audit trails provided by SCITT. When corrections are made to conversation data, the changes are documented in the transparency service, creating a verifiable record of what information was changed, when the change occurred, and who authorized the modification. This transparency builds trust while ensuring that corrections cannot be disputed later.

Perhaps most complex is the automation of data deletion rights, commonly known as the "right to be forgotten." Traditional deletion processes struggle with distributed data architectures where copies of personal information might exist across multiple systems, backup archives, and third-party processors. The vCon framework addresses this challenge through comprehensive tracking of data distribution combined with automated deletion coordination.

When consent is revoked or deletion is requested, the SCITT transparency service identifies all systems that have received copies of the relevant vCons. Automated deletion requests are then sent to these systems, with responses tracked and verified through additional SCITT entries. This process ensures that deletion requests are honored comprehensively rather than only in the primary system where the request was received.

The framework also supports partial deletion scenarios where individuals might revoke consent for specific purposes while maintaining consent for others. For example, someone might withdraw consent for AI training while maintaining consent for conversation recording for quality assurance. The system can automatically redact or modify vCons to reflect these granular consent changes while maintaining data integrity for authorized uses.

Compliance reporting becomes significantly more efficient through automated privacy rights management. Organizations can generate real-time reports on privacy request volumes, response times, and completion rates using data automatically collected through the SCITT transparency service. These reports provide the metrics and documentation required for regulatory reporting while reducing the manual effort traditionally required for compliance reporting.

The verification capabilities provided by SCITT receipts transform the relationship between organizations and privacy regulators. Instead of requiring extensive document production during privacy audits, organizations can provide cryptographic proof of their privacy rights fulfillment processes. Regulators can verify compliance through mathematical certainty rather than document review, enabling more efficient oversight while providing stronger privacy protections.

### Business Benefits and Competitive Advantages

Organizations implementing vCon lifecycle management with SCITT transparency services realize significant business benefits that extend far beyond regulatory compliance. These advantages touch multiple aspects of business operations, from operational efficiency and risk management to customer trust and competitive positioning.

Operational efficiency improvements emerge from the standardization and automation enabled by the vCon framework. Currently, most organizations manage conversation data through fragmented systems with incompatible formats, manual processes, and disconnected privacy controls. The integration of vCons with SCITT creates unified workflows that reduce manual effort, eliminate data format conversion overhead, and enable automated compliance processes.

Customer service organizations particularly benefit from standardized conversation management. Representatives can access complete conversation histories regardless of the original communication channel, enabling more effective customer interactions. The embedded consent information ensures that representatives understand exactly what data uses are authorized, preventing privacy violations while enabling personalized service.

Risk management benefits accrue from the comprehensive audit trails and tamper-evident records provided by SCITT transparency services. Organizations can demonstrate compliance with privacy regulations through cryptographic proof rather than documentary evidence, reducing regulatory risk and potential penalties. The immutable nature of SCITT records provides legal protections in disputes about consent decisions or data processing activities.

The transparency provided by this framework enables new forms of customer engagement based on trust and data ownership. Organizations can provide customers with real-time visibility into how their conversation data is being used, what insights are being generated, and what value is being created. This transparency can transform privacy from a compliance burden into a competitive differentiator.

Cost reduction occurs across multiple dimensions of privacy management. Automated privacy rights fulfillment reduces the labor costs associated with manual data searches and report generation. Standardized data formats eliminate the need for custom integration work when implementing new conversation analysis tools or communication platforms. The prevention of privacy violations through embedded consent controls reduces potential regulatory penalties and reputation damage.

Innovation enablement represents a particularly significant benefit for organizations developing artificial intelligence and machine learning capabilities. The granular consent management provided by consent attachments enables organizations to use conversation data for AI training while respecting individual privacy preferences. This capability becomes increasingly important as AI governance regulations emerge worldwide, requiring explicit consent for AI applications.

Market differentiation through privacy leadership provides competitive advantages in markets where consumers are increasingly privacy-conscious. Organizations that can demonstrate transparent, respectful data practices through verifiable audit trails gain customer trust and loyalty. This differentiation is particularly valuable in industries where data processing is central to service delivery, such as healthcare, financial services, and telecommunications.

The global nature of the framework provides advantages for multinational organizations that must comply with diverse privacy regulations across different jurisdictions. Rather than implementing separate compliance systems for each regulatory framework, organizations can use the vCon framework to meet the requirements of multiple regulations through a single, standardized approach.

Vendor independence emerges from the standardized nature of vCons, reducing reliance on proprietary data formats and enabling organizations to choose best-of-breed solutions for different aspects of conversation management. This flexibility supports innovation and cost optimization while reducing vendor lock-in risks.

Partnership and collaboration opportunities expand when organizations can share conversation data with verified consent and audit trails. Research collaborations, industry benchmarking, and supply chain partnerships become more feasible when all parties can verify that data sharing complies with privacy requirements and consent limitations.

### Implementation Considerations and Best Practices

Successful implementation of vCon lifecycle management with SCITT transparency services requires careful planning, staged deployment, and attention to both technical and organizational change management factors. Organizations embarking on this transformation should consider several key dimensions of implementation to maximize benefits while minimizing risks and disruption.

Technical architecture planning forms the foundation of successful implementation. Organizations must assess their current conversation data landscape, identifying all systems that capture, process, store, or analyze conversational information. This assessment typically reveals significant complexity, with conversation data scattered across customer relationship management systems, telephony platforms, email servers, chat applications, video conferencing tools, and various analytics platforms.

The migration strategy should prioritize high-value, high-risk conversation types for initial implementation. Customer service interactions, sales calls, and other conversations involving sensitive personal information typically provide the greatest immediate benefits from enhanced privacy protection and compliance automation. These conversations also face the highest regulatory scrutiny, making the compliance benefits particularly valuable.

Consent collection process redesign represents a critical implementation consideration. Organizations must evaluate their current consent mechanisms and design new processes that integrate seamlessly with vCon creation workflows. This often involves updating communication platform configurations, training customer service representatives, and implementing automated consent detection for different conversation types.

Staff training and change management requirements extend beyond technical implementation to encompass new operational procedures, privacy awareness, and compliance workflows. Customer service representatives need training on consent verification procedures and privacy limitation awareness. IT staff require education on vCon processing workflows and SCITT verification procedures. Legal and compliance teams need familiarity with new audit trail capabilities and automated reporting functions.

Integration with existing systems requires careful API design and data migration planning. Organizations typically cannot replace all conversation management systems simultaneously, necessitating hybrid architectures where vCon-enabled systems interoperate with legacy platforms. This requires robust transformation and synchronization mechanisms that maintain data integrity and consent consistency across system boundaries.

Security considerations encompass both data protection and access control requirements. vCons containing sensitive conversation data require encryption in transit and at rest, with careful key management and access control implementation. SCITT transparency services require secure authentication and authorization mechanisms that prevent unauthorized statement submission while maintaining the transparency benefits of the framework.

Performance and scalability planning must account for the potentially massive volumes of conversation data in enterprise environments. Large organizations might process millions of conversations daily, requiring SCITT transparency services and vCon processing systems that can handle high transaction volumes with acceptable latency and reliability.

Vendor selection and partnership decisions significantly impact implementation success. Organizations must evaluate SCITT transparency service providers, vCon processing platforms, and consent management solutions based on technical capabilities, compliance certifications, and long-term viability. The standardized nature of vCons provides flexibility in vendor selection while requiring due diligence on implementation quality and interoperability.

Monitoring and measurement frameworks should be established to track implementation progress and business benefits realization. Key metrics might include privacy rights request processing times, consent verification automation rates, audit trail completeness, and customer satisfaction with transparency features. These measurements enable continuous improvement and demonstrate return on investment for privacy technology initiatives.

Regulatory engagement and compliance validation represent ongoing requirements rather than one-time implementation tasks. Organizations should engage with relevant privacy regulators to understand how vCon-based compliance demonstrations will be evaluated and what documentation or verification procedures are expected. This proactive engagement can inform implementation decisions and reduce regulatory uncertainty.

### Future Implications and Industry Transformation

The widespread adoption of vCon lifecycle management with SCITT transparency services has the potential to transform entire industries and reshape how society approaches privacy protection and data governance. These changes extend far beyond individual organizations to encompass market dynamics, regulatory frameworks, and consumer expectations.

Industry standardization around vCons could eliminate many of the interoperability challenges that currently plague conversation management systems. When all communication platforms, analytics tools, and customer relationship management systems support standardized vCon formats, organizations gain unprecedented flexibility in system selection and vendor management. This standardization could accelerate innovation by reducing integration overhead and enabling specialized solutions to focus on their core value propositions.

The transparency provided by SCITT-based audit trails could fundamentally change the relationship between organizations and privacy regulators. Rather than periodic audits based on document review, regulators could implement continuous monitoring systems that verify compliance through real-time cryptographic proof. This shift could reduce regulatory burden for compliant organizations while enabling more effective oversight of privacy violations.

Consumer expectations around data transparency and control will likely evolve as these capabilities become more widely available. Individuals may begin to expect real-time visibility into how their conversation data is being used, similar to how financial services now provide real-time transaction notifications. Organizations that can provide this transparency will gain competitive advantages, while those that cannot may face customer attrition.

Artificial intelligence governance could be revolutionized through granular consent management capabilities. As governments worldwide develop AI regulations requiring explicit consent for training data use, the ability to track and verify consent for AI applications becomes a critical competitive capability. Organizations with robust consent management frameworks will be better positioned to develop AI capabilities within regulatory constraints.

Cross-border data governance could be simplified through standardized privacy frameworks that work across jurisdictions. Rather than implementing separate compliance systems for each country's privacy regulations, multinational organizations could use vCon-based frameworks that meet the requirements of multiple regulatory systems simultaneously. This could reduce compliance costs while improving privacy protection consistency.

New business models could emerge around privacy transparency and data governance services. Organizations might offer premium services that provide enhanced privacy transparency, specialized consent management for sensitive industries, or privacy audit services based on SCITT verification capabilities. These new service categories could create economic incentives for privacy protection beyond regulatory compliance.

The democratization of privacy technology could extend advanced privacy capabilities to smaller organizations that currently lack the resources for sophisticated privacy management systems. Cloud-based vCon processing and SCITT transparency services could make enterprise-grade privacy capabilities accessible to organizations of all sizes, raising the overall level of privacy protection across the economy.

Academic research and policy development could benefit from standardized conversation data formats that enable privacy-preserving analysis of communication patterns and privacy behavior. Researchers could study the effectiveness of different consent mechanisms, analyze the impact of privacy regulations, and develop improved privacy protection technologies using anonymized vCon datasets.

International cooperation on privacy protection could be enhanced through standardized frameworks that enable cross-border verification of privacy compliance. Trade agreements, mutual recognition arrangements, and international privacy frameworks could incorporate vCon-based verification mechanisms that reduce friction while maintaining protection standards.

The evolution toward privacy-first data architectures represents a fundamental shift in how technology systems are designed and operated. Rather than treating privacy as an add-on feature, future systems could embed privacy protection as a core architectural principle, with consent management, audit trails, and transparency features built into the foundation of data processing systems.

### Conclusion

The integration of Virtualized Conversations with Supply Chain Integrity, Transparency, and Trust protocols represents more than a technological advancement; it embodies a fundamental reimagining of how organizations can balance the tremendous value of conversational data with the imperative of privacy protection and regulatory compliance. This framework transforms privacy from a constraint on business operations into an enabler of trust, innovation, and competitive advantage.

The challenges that this framework addresses are not merely technical but reflect deeper societal questions about data ownership, consent, and the balance between individual privacy rights and collective benefits from data processing. By embedding privacy protection directly into data containers and creating immutable audit trails of data processing activities, the vCon-SCITT integration provides a foundation for rebuilding trust between organizations and the individuals whose data they process.

The business case for adoption extends far beyond regulatory compliance to encompass operational efficiency, risk management, customer trust, and innovation enablement. Organizations that embrace this framework position themselves to thrive in an increasingly privacy-conscious marketplace while gaining the flexibility to develop new services and capabilities within a foundation of verifiable privacy protection.

Perhaps most significantly, this approach provides a pathway for responsible innovation in artificial intelligence and machine learning applications. As societies worldwide grapple with the governance of AI systems, the ability to demonstrate explicit, granular consent for training data use becomes essential for maintaining public trust and regulatory approval for AI development.

The transformation potential extends beyond individual organizations to encompass entire industries and regulatory frameworks. Standardized approaches to conversation data management and privacy protection could reduce compliance costs, enable new forms of collaboration, and provide regulators with more effective oversight mechanisms based on cryptographic verification rather than document review.

The future envisioned by this framework is one where privacy protection enhances rather than constrains business value creation. Organizations that can demonstrate transparent, respectful data practices through verifiable audit trails will earn customer trust and loyalty while gaining access to rich conversational data for legitimate business purposes. This alignment of privacy protection with business success creates sustainable incentives for responsible data management that extend far beyond regulatory compliance requirements.

As this framework matures and gains adoption, it has the potential to serve as a model for privacy protection in other domains beyond conversational data. The principles of embedded consent management, immutable audit trails, and automated compliance could be applied to healthcare data, financial information, location data, and other categories of personal information that require sophisticated privacy protection.

The path forward requires collaboration between technology vendors, privacy advocates, regulators, and organizations across industries to refine standards, develop best practices, and create the ecosystem of tools and services needed for widespread adoption. This collaboration must balance innovation enablement with robust privacy protection, ensuring that technological advancement serves human values and societal benefit.

Ultimately, the vCon lifecycle management framework with SCITT transparency services represents a vision of a future where privacy protection and business innovation are not opposing forces but complementary capabilities that together create more trustworthy, more valuable, and more sustainable approaches to data processing in our increasingly connected world.


# Privacy-First Conversation Management

A Technical Whitepaper on Standardized Lawful Basis in Virtualized Conversations

> **Spec note:** This whitepaper was originally written against an early `draft-vcon-consent` document. That work has been superseded by [`draft-howe-vcon-lawful-basis`](https://datatracker.ietf.org/doc/draft-howe-vcon-lawful-basis/), which formalizes the model described here. For the current spec surface and JSON shapes, see [Lawful Basis](/extensions/lawful-basis) and [Lifecycle](/extensions/lifecycle). The rationale and patterns in this whitepaper remain valid.

## Executive Summary

Voice and chat conversations are some of the richest personal data a business handles, and also some of the hardest to govern. The real problem is not collecting consent. It is keeping that consent meaningful as the data moves between systems, gets re-used for new purposes, and outlives the conversation it came from.

The vCon [Lawful Basis extension](/extensions/lawful-basis) addresses this by embedding the legal grounds for processing directly inside the conversation container. The lawful basis, the purposes it covers, an expiration, and a cryptographic proof all travel with the data. The result is a machine-checkable record of why processing is allowed, available wherever the conversation goes.

## The Problem: Consent in the AI Era

Traditional consent management has four recurring failure modes:

1. Consent records sit in a separate database from the data they govern, so compliance checks require reconstruction across systems.
2. Permissions are binary, which makes nuanced cases (yes to transcription, no to model training) impossible to express.
3. Audit trails are scattered, so proving compliance is an investigation rather than a query.
4. AI-era use cases such as model training, inference, and text-and-data-mining were never modeled in older consent schemas.

Consider a healthcare contact center. A single patient call may pass through call recording, transcription, sentiment analysis, and eventual use as training data for a future assistant. Each step lives under a different rule (HIPAA for healthcare data, GDPR for EU residents, CCPA for Californians, sectoral rules for AI training). Tracking which permissions apply at which step, when they expire, and how to prove they were given is the operational problem that the Lawful Basis extension exists to solve.

## The vCon Solution

A vCon (Virtualized Conversation) is a standardized JSON container that packages everything related to a single conversation: the parties, the dialog content (audio, text, video), any analysis (transcripts, sentiment, summaries), and a list of typed attachments.

The Lawful Basis extension adds a structured attachment to that list. The attachment declares the legal grounds for processing the vCon, the specific purposes those grounds cover, when the basis expires, and how the basis was established. Because the attachment lives inside the vCon, consent travels with the data and cannot be separated from it.

A minimal attachment looks like this:

```json
{
  "type": "lawful_basis",
  "encoding": "json",
  "party": 0,
  "dialog": 0,
  "body": {
    "lawful_basis": "consent",
    "expiration": "2026-01-02T12:00:00Z",
    "purpose_grants": [
      { "purpose": "recording",     "granted": true, "granted_at": "2025-01-02T12:15:30Z" },
      { "purpose": "transcription", "granted": true, "granted_at": "2025-01-02T12:15:30Z" },
      { "purpose": "analysis",      "granted": true, "granted_at": "2025-01-02T12:15:30Z" }
    ],
    "proof_mechanisms": [
      {
        "mechanism_type": "audio_recording",
        "dialog_index": 0,
        "description": "Verbal consent captured at start of recording"
      }
    ]
  }
}
```

The top-level vCon must also declare the extension:

```json
{
  "vcon": "0.4.0",
  "extensions": ["lawful_basis"],
  "must_understand": ["lawful_basis"]
}
```

Placing `lawful_basis` in `must_understand` is the safer default. A consumer that does not understand the lawful-basis model should refuse the vCon rather than silently lose the consent record.

## Key Features

### Granular, purpose-based permissions

Each entry in `purpose_grants[]` names a specific processing purpose and records whether it was granted. Common purposes include `recording`, `transcription`, `analysis`, `storage`, `redistribution`, and AI-oriented purposes such as model training and inference. Granting `transcription` while denying `ai_training` is a single attachment, not a separate document.

### Six GDPR-aligned bases

The `lawful_basis` field takes one of the six GDPR bases: `consent`, `contract`, `legal_obligation`, `vital_interests`, `public_task`, or `legitimate_interests`. The first five require an expiration. `legitimate_interests` may set `expiration: null` for ongoing grounds. Aligning to GDPR's six bases makes the extension legible to regulators while remaining usable under CCPA, HIPAA, and similar regimes.

### Cryptographic verification

Each attachment carries at least one entry in `proof_mechanisms[]` describing how the basis was established. The mechanism can be an audio segment in the vCon itself, a reference to an external system of record, or a signed document. When paired with the [Lifecycle extension](/extensions/lifecycle), consent acceptance and revocation events are also recorded on a SCITT transparency ledger, producing a tamper-evident, third-party-verifiable audit trail.

### Temporal management

Expiration timestamps invalidate consent automatically. Revalidation intervals can require periodic refresh for sensitive purposes. Clock-skew handling and indefinite-but-revalidated grants are first-class concerns in the spec rather than ad-hoc patches in application code.

### Regulatory compliance

The extension is designed to satisfy the operational requirements of the major privacy regimes without forcing a separate compliance stack:

* **GDPR.** Right of access (read the attachment), right of rectification (update or replace it), right to be forgotten (revoke via Lifecycle), right to portability (export the vCon).
* **CCPA.** Right to know, right to delete, opt-out of sale, non-discrimination, all expressible in `purpose_grants` and Lifecycle events.
* **HIPAA.** Per-purpose authorization, audit trail, breach support, all attached to the conversation itself.

## Technical Implementation

### Real-time verification at processing time

Processing pipelines read the attachment and gate behavior on the relevant purpose:

```python
from vcon import Vcon

def process_conversation(v: Vcon) -> Vcon:
    basis = next(
        (a for a in v.vcon_dict.get("attachments", [])
         if a.get("type") == "lawful_basis"),
        None,
    )
    if basis is None:
        return v  # no basis declared; do not process

    body = basis["body"]
    grants = {g["purpose"]: g["granted"] for g in body["purpose_grants"]}

    if grants.get("transcription"):
        v.add_analysis(transcribe_audio(v.dialog[0]))

    if not grants.get("ai_training", False):
        v.add_processing_restriction("no_ai_training")

    return v
```

### Building the attachment

The Python `vcon` library provides `add_lawful_basis_attachment()`, but appending the dict directly is often simpler:

```python
v.vcon_dict["attachments"].append({
    "type": "lawful_basis",
    "encoding": "json",
    "party": 0,
    "dialog": 0,
    "body": {
        "lawful_basis": "consent",
        "expiration": "2026-01-02T12:00:00Z",
        "purpose_grants": [
            {"purpose": "recording",     "granted": True, "granted_at": "2025-01-02T12:15:30Z"},
            {"purpose": "transcription", "granted": True, "granted_at": "2025-01-02T12:15:30Z"},
        ],
        "proof_mechanisms": [
            {"mechanism_type": "audio_recording", "dialog_index": 0,
             "description": "Verbal consent captured at start of recording"},
        ],
    },
})
v.add_extension("lawful_basis")
```

### Pairing with Lifecycle

Lawful Basis declares the legal grounds. The [Lifecycle extension](/extensions/lifecycle) records the events: when consent was accepted, modified, or revoked, each anchored on a SCITT transparency service. Used together, the two answer both "what was allowed" and "what happened, and when, provably." Most production deployments use both.

### Security considerations

Signatures use COSE (CBOR Object Signing and Encryption) with certificate-chain validation for signing authority. External proof references include content-integrity hashes. Consent ledger traffic uses TLS 1.2 or later with certificate pinning for critical services. Access to consent data follows least privilege and is audit-logged.

## Use Cases

### Multi-channel customer service

A telco handles inquiries across voice, chat, and email. Each interaction is a vCon with its own Lawful Basis attachment. When a chat escalates to a voice call, the basis travels with the case file. AI analysis and reporting gate themselves on `purpose_grants`, so the same data pipeline behaves correctly across channels with no parallel compliance system.

### Healthcare telemedicine

A telemedicine platform records video consultations with patients in multiple jurisdictions. Consent is captured before recording starts, with separate grants for clinical record, quality review, and de-identified research. Expiration drives automatic deletion. The same vCon satisfies HIPAA for US patients and GDPR for EU patients without forking the data pipeline.

### AI training data

A vendor builds conversational AI from historical customer-service recordings. Lawful Basis attachments mark which conversations carry training permission. A filter in the training pipeline reads `purpose_grants` and includes only conversations where `ai_training` is granted and the basis is unexpired. The resulting model has an auditable provenance trail: a regulator can trace any training example back to the consenting interaction.

## Conclusion

Lawful Basis turns privacy compliance from a paperwork exercise into a property of the data itself. The legal grounds, the purposes they cover, the expiration, and the proof all live inside the vCon, where any downstream system can read them, gate on them, and verify them. Combined with Lifecycle's SCITT-anchored event log, the result is a privacy posture that scales with AI-era data use rather than collapsing under it.

***

#### References and Further Reading

* IETF vCon Working Group: <https://datatracker.ietf.org/wg/vcon/>
* `draft-howe-vcon-lawful-basis`: <https://datatracker.ietf.org/doc/draft-howe-vcon-lawful-basis/>
* SCITT Working Group: <https://datatracker.ietf.org/wg/scitt/>
* Lawful Basis extension (this docs site): [extensions/lawful-basis.md](/extensions/lawful-basis)
* Lifecycle extension (this docs site): [extensions/lifecycle.md](/extensions/lifecycle)
* GDPR Compliance Guide: <https://gdpr.eu/>
* CCPA Resource Center: <https://oag.ca.gov/privacy/ccpa>

*For questions about implementation or to contribute to the specification, contact the vCon working group at <vcon@ietf.org>*


# vCons and Increasing End User Agency

How vCons restore meaningful user control over personal conversational data

> **Spec note:** This post was originally written when the consent model was described as a standalone "consent extension." That work has since been formalized as [`draft-howe-vcon-lawful-basis`](https://datatracker.ietf.org/doc/draft-howe-vcon-lawful-basis/). For the current spec-surface and JSON shapes, see [Lawful Basis](/extensions/lawful-basis). The rationale and patterns described here remain valid.

In our data-driven world, most of us have experienced the frustration of feeling powerless over our own information. We click "Accept All Cookies," agree to lengthy terms of service without reading them, and wonder what happens to our conversations, location data, and digital footprints once they disappear into corporate servers. This powerlessness represents a fundamental lack of what privacy researchers call "end user agency" – and it's a problem that new technical standards like vCons (Virtualized Conversations) are beginning to address.

### What is End User Agency and Why Does it Matter?

End user agency refers to an individual's meaningful control over their personal data – not just the theoretical right to privacy, but the practical ability to exercise that right. As privacy researcher Mike Smith explains, true agency means answering critical questions: "Are end users allowed to permanently delete their data from the company's servers? Or restrict which outside services can gain access? Can they stop the collection of their data altogether, either for a set amount of time or indefinitely, without forfeiting their ability to use the product or service?"

The absence of such agency creates what Smith identifies as problematic "power asymmetries" between individuals and technology organizations, particularly "near monopolies, like Google and Amazon," which "present a more lopsided power dynamic in the struggles over privacy than smaller competitors."

This power imbalance is especially concerning because, as Smith notes, "the larger the footprint a technology has in your life and the more types and amounts of data it collects on you, the less control you have over your privacy." Without genuine agency, users become passive subjects of data collection rather than active participants in determining how their information is used.

### How vCons Technically Address User Agency

The vCon (Virtualized Conversation) standards, particularly the [Lawful Basis extension](/extensions/lawful-basis), represent a significant technical advancement in restoring user agency over conversational data. These standards draw inspiration from several key insights in privacy theory and build technical solutions around them.

**Contextual Control Beyond Binary Choices:** Privacy theorist Helen Nissenbaum's concept of "contextual integrity" argues that privacy is not about secrecy but about "appropriate flow" of information according to contextual norms. vCons embody this principle by enabling users to grant specific permissions for distinct purposes rather than blanket "yes or no" consent. A user might consent to having their customer service call recorded but deny permission for sentiment analysis or marketing use of that same conversation. This granular approach directly addresses Smith's insight that privacy issues "exist on spectrums" and can be thought of as "knobs on a company's dashboard that can be turned up or down."

**Addressing Systemic Control Problems:** Legal scholar Julie Cohen argues that "privacy's most enduring institutional failure modes flow from its insistence on placing the individual and individualized control at the center." vCons address this by creating systematic, auditable consent mechanisms that don't rely solely on individual burden but build accountability into the technical infrastructure itself.

**Reclaiming the "Right to Future Tense":** Shoshana Zuboff warns that surveillance capitalism threatens what she calls our "right to the future tense, which is the essence of free will." vCons counter this through temporal boundaries – expiration controls and revalidation intervals that prevent indefinite data usage. Users can grant time-limited permissions that require explicit renewal, preserving their ability to change their minds about future data use.

**Cryptographic Transparency:** Through integration with [SCITT](/deep-dives/scitt-supply-chain-integrity-transparency-and-trust) (Supply Chain Integrity, Transparency, and Trust) transparency services, vCons create verifiable audit trails of all consent decisions. This addresses Smith's concern about data aggregation that "works mostly in the dark, without our explicit consent or opt-in." Instead of trusting that companies honor privacy preferences, users can demand cryptographic proof.

**Technical Infrastructure for Rights:** The vCon specification explicitly requires support for deletion, rectification, and consent withdrawal – providing the technical infrastructure for the user agency Smith advocates. This moves beyond theoretical rights to enforceable technical capabilities.

**Proof Mechanisms and Verification:** vCons support multiple ways to verify consent authenticity, from verbal confirmation within recorded conversations to cryptographic signatures. This addresses what law and technology scholar Ryan Calo identifies as the challenge of "privacy vulnerability" – situations where users are particularly susceptible to privacy harms due to power imbalances or information asymmetries.

### Challenges That Still Exist

Despite their technical sophistication, vCons face several significant challenges in actually increasing user agency:

**Adoption and Market Power:** vCons only work if widely adopted. As Smith notes, organizations with larger networks and more dominance have greater power asymmetries. A small startup implementing vCons may increase user agency, but a dominant platform that refuses to implement them maintains the status quo.

**Technical Literacy Requirements:** Granular consent requires users to understand concepts like "sentiment analysis," "biometric processing," or "data aggregation." Many users lack the technical knowledge to make informed decisions about these processing types.

**Economic Pressure:** Users may still face "take it or leave it" scenarios where essential services require broad permissions. True agency requires not just the technical ability to say "no," but real alternatives when users exercise that choice.

**Regulatory Enforcement:** Technical standards alone cannot address power imbalances. Without strong regulatory requirements and enforcement, organizations may simply choose not to implement user-friendly consent mechanisms.

**Complexity vs. Usability:** While granular control increases agency in theory, it may overwhelm users with decisions. The challenge is providing meaningful control without creating consent fatigue.

### The Strategic Value for User Agency

Despite these challenges, vCons represent a fundamental shift in how we approach user agency over personal data. The current system, as Smith observes, makes "it almost impossible to know how data are being aggregated together and by whom," creating accountability problems for services "which we may not even know exist."

Shoshana Zuboff's analysis of surveillance capitalism provides crucial context here. She describes how tech companies have created "behavioral futures markets," where "predictions about our behavior are bought and sold, and the production of goods and services is subordinated to a new 'means of behavioral modification.'" vCons directly counter this by making behavioral data processing conditional on explicit, granular, and time-bounded consent.

vCons address this by creating a standardized, technically enforceable framework for consent that shifts the burden of proof from users to organizations. Instead of trusting that companies honor privacy preferences, users can demand cryptographic evidence of proper consent handling. This represents what Julie Cohen calls "turning privacy inside out" – focusing on "the conditions that are needed to produce sufficiently private and privacy-valuing subjects" rather than placing all responsibility on individual choice.

Most importantly, vCons recognize that meaningful agency requires more than binary choices. Helen Nissenbaum's contextual integrity theory emphasizes that privacy violations occur when "information flows outside appropriate social contexts." vCons provide infrastructure for nuanced consent decisions that acknowledge this contextual nature of privacy. Users can engage with digital services while maintaining precise control over how their data flows between different contexts and purposes.

The strategic value lies not just in the technical capabilities vCons provide, but in their potential to rebalance what Zuboff calls the fundamental power dynamic where "surveillance capitalists now develop 'economies of action,' as they learn to tune, herd, and condition our behavior with subtle and subliminal cues." By creating auditable, granular, time-bounded consent mechanisms, vCons transform privacy from a trust-based system to a verify-based system. This represents the kind of structural change needed to address the fundamental asymmetries Smith identifies between individuals and technology organizations.

While vCons alone cannot solve the privacy crisis, they provide essential technical infrastructure for a world where user agency is not just a regulatory requirement, but a practical reality built into our digital systems. As Ryan Calo observes about emerging technologies and law, we need frameworks that can adapt to "the promiscuity of data" while maintaining human control. The question now is whether market forces, regulatory pressure, and user demand will drive their adoption widely enough to fulfill their promise.

***

*I recently completed a course at HBS Online on Privacy, and I want to thank the instructors for a thoughtful program and for introducing me to the privacy researchers whose work shapes the arguments in this post.*

*This post was inspired by the work of several leading privacy researchers: Mike Smith's analysis of privacy as intersecting power dynamics and user agency; Helen Nissenbaum's theory of contextual integrity and appropriate information flows; Julie Cohen's critique of individual-centered privacy approaches and her concept of "turning privacy inside out"; Shoshana Zuboff's groundbreaking analysis of surveillance capitalism and behavioral futures markets; and Ryan Calo's work on privacy vulnerability in emerging technologies. Their collective insights inform our understanding of how technical standards like vCons might restore meaningful user control in our data-driven world.*


# SCITT: Supply Chain Integrity, Transparency and Trust

A Framework for Securing Modern Software Supply Chains

#### Abstract

The increasing complexity of software supply chains has created unprecedented security challenges, as demonstrated by high-profile attacks like SolarWinds and Log4Shell. Supply Chain Integrity, Transparency and Trust (SCITT) emerges as a comprehensive framework designed to create an immutable, transparent ledger for software supply chain artifacts and attestations. This whitepaper examines SCITT's architecture, security model, and interoperability features, demonstrating how it addresses critical gaps in current supply chain security approaches while maintaining compatibility with existing tools and workflows.

#### 1. Introduction

Modern software development relies on complex supply chains involving multiple parties, from open-source contributors to commercial vendors. Each component may pass through numerous hands before reaching production systems, creating opportunities for compromise at every stage. Traditional approaches to supply chain security rely on point-in-time verification and trust relationships that can be exploited by sophisticated attackers.

SCITT provides a standardized framework for creating tamper-evident, publicly verifiable records of claims about software artifacts throughout their lifecycle. By establishing a cryptographically secured, append-only registry of attestations, SCITT enables organizations to make verifiable statements about their software while allowing consumers to independently verify these claims and trace complete component histories.

#### 2. Technical Architecture

**2.1 Core Components**

SCITT's architecture consists of four primary components:

**Transparency Service**: The central registry that maintains an append-only log of all claims. This service provides cryptographic receipts proving that claims have been registered at specific points in time.

**Claims**: Signed statements about software artifacts, including but not limited to Software Bills of Materials (SBOMs), vulnerability reports, build provenance, and security assessments. Claims are format-agnostic, allowing integration with existing standards.

**Receipts**: Cryptographic proofs issued by the Transparency Service confirming that a claim has been registered. These receipts enable offline verification without accessing the service.

**Verifiers**: Entities that validate claims and their associated receipts to establish trust in software components before deployment or use.

**2.2 Cryptographic Foundation**

SCITT builds upon established cryptographic standards, primarily CBOR Object Signing and Encryption (COSE) for signature formats. This ensures compatibility with existing public key infrastructure while providing flexibility for future cryptographic algorithms. The framework uses hash chains similar to certificate transparency logs, creating an immutable record that makes tampering immediately detectable.

#### 3. Relationship to Blockchain Technology

While SCITT shares fundamental concepts with blockchain technology, it represents a specialized application optimized for software supply chain metadata rather than general-purpose transactions.

**3.1 Shared Principles**

Both SCITT and blockchain systems implement:

* **Immutable append-only logs** where historical records cannot be altered
* **Cryptographic linking** between entries to ensure temporal ordering
* **Transparent verification** allowing any party to audit the complete history
* **Decentralization potential** through federated architectures

**3.2 Key Differentiators**

Unlike traditional blockchains, SCITT:

* **Eliminates consensus overhead**: No proof-of-work or proof-of-stake mechanisms required
* **Enables efficient scaling**: Designated transparency services operated by trusted organizations
* **Separates storage and verification**: Federated registries can reference each other without full replication
* **Optimizes for specific use cases**: Designed specifically for software attestations rather than financial transactions

This specialized approach makes SCITT more energy-efficient and performant while maintaining the security guarantees essential for supply chain integrity.

#### 4. Security Benefits

**4.1 Non-Repudiation and Accountability**

Once an organization registers a claim about their software, the cryptographic receipt creates an undeniable record. This accountability mechanism ensures that malicious actors cannot quietly inject compromised components or retroactively alter their attestations after a breach is discovered.

**4.2 Tamper-Evidence and Forensic Capabilities**

SCITT's cryptographic receipts provide temporal proof of when specific claims existed, enabling precise forensic analysis. Security teams can definitively determine what was known at any point in time, identifying exactly when and where compromises occurred in the supply chain.

**4.3 Attack Prevention and Detection**

SCITT addresses several critical attack vectors:

**Supply Chain Injection**: Unauthorized modifications become immediately visible due to missing or invalid SCITT registrations from legitimate sources.

**Dependency Confusion**: Internal packages can be cryptographically distinguished from public packages through issuer verification.

**Time-of-Check/Time-of-Use**: Cryptographic receipts ensure the verified version matches the deployed version.

**Retroactive Tampering**: The append-only nature prevents attackers from covering their tracks by modifying historical records.

**4.4 Policy Enforcement**

Organizations can implement automated security policies that verify multiple conditions before allowing software deployment:

* Presence of required attestations (SBOMs, vulnerability scans)
* Signatures from authorized entities
* Build provenance from approved CI/CD systems
* Compliance with regulatory requirements

These policies can be enforced programmatically across organizational boundaries, creating a transparent trust framework.

#### 5. Interoperability and Integration

**5.1 Format-Agnostic Design**

SCITT's architecture accepts any serializable content type, enabling seamless integration with existing tools and standards:

* SPDX and CycloneDX for software composition
* In-toto and SLSA for build provenance
* Custom formats for proprietary security assessments
* Industry-specific compliance attestations

This flexibility allows organizations to adopt SCITT without abandoning their current toolchains or workflows.

**5.2 Federation Capabilities**

Multiple SCITT instances can interoperate through claim references, enabling:

* Cross-organizational trust without centralization
* Industry-specific registries that maintain autonomy
* Geographic distribution for performance and compliance
* Gradual adoption across supply chain participants

**5.3 Standardized APIs**

SCITT employs standard HTTP REST APIs with COSE signatures, ensuring:

* Language-agnostic integration
* Minimal modification to existing tools
* Consistent verification regardless of claim format
* Simplified adoption across diverse ecosystems

#### 6. Implementation Considerations

**6.1 Deployment Models**

Organizations can choose from several deployment approaches:

**Public Registries**: Industry-wide transparency services operated by trusted entities **Private Registries**: Internal services for proprietary software and sensitive attestations **Hybrid Models**: Selective publication based on confidentiality requirements **Federated Networks**: Interconnected registries sharing trust relationships

**6.2 Performance and Scalability**

SCITT's design prioritizes efficiency:

* Lightweight claim registration process
* Offline verification capabilities
* Distributed caching of receipts
* Selective synchronization between registries

These characteristics enable SCITT to scale to global software supply chains without creating performance bottlenecks.

**6.3 Migration Strategies**

Organizations can adopt SCITT incrementally:

1. Begin with high-value or high-risk components
2. Integrate with existing CI/CD pipelines
3. Gradually expand coverage across the software portfolio
4. Establish federation with supply chain partners

#### 7. Future Directions

The SCITT framework continues to evolve through the IETF standardization process. Key areas of development include:

* Enhanced privacy features for sensitive attestations
* Improved federation protocols for cross-registry trust
* Integration with emerging software identity standards
* Automated policy languages for complex trust requirements

#### 8. Conclusion

SCITT represents a fundamental advancement in software supply chain security, providing the transparency and accountability necessary for modern software ecosystems. By combining the immutability of blockchain-inspired architectures with the efficiency required for practical deployment, SCITT offers a path toward comprehensive supply chain integrity.

The framework's format-agnostic design and standardized APIs ensure that organizations can adopt SCITT without disrupting existing workflows, while its cryptographic foundation provides the security guarantees necessary to detect and prevent sophisticated supply chain attacks. As software supply chains continue to grow in complexity, SCITT's transparent trust model becomes increasingly critical for maintaining security at scale.

#### References

* IETF SCITT Working Group. "Supply Chain Integrity, Transparency and Trust." Internet Engineering Task Force. <https://datatracker.ietf.org/wg/scitt/about/>
* SCITT Architecture Internet-Draft. "An Architecture for Supply Chain Integrity, Transparency, and Trust." <https://datatracker.ietf.org/doc/draft-ietf-scitt-architecture/>
* Microsoft Corporation. "SCITT Confidential Consortium Framework Ledger Implementation." <https://github.com/microsoft/scitt-ccf-ledger>
* SCITT Community. "SCITT API Emulator Reference Implementation." <https://github.com/scitt-community/scitt-api-emulator>
* Internet Engineering Task Force. "CBOR Object Signing and Encryption (COSE)." RFC 8152. <https://datatracker.ietf.org/doc/html/rfc8152>
* Cybersecurity and Infrastructure Security Agency. "Software Supply Chain Security Guidance." <https://www.cisa.gov/software-supply-chain-security>
* SCITT Receipts Format Specification. "SCITT Receipts." <https://datatracker.ietf.org/doc/draft-ietf-scitt-receipts/>
* Package URL Specification. "A minimal specification for purl." <https://github.com/package-url/purl-spec>
* Supply-chain Levels for Software Artifacts. "SLSA Framework." <https://slsa.dev/>
* OpenSSF Sigstore Project. "A new standard for signing, verifying and protecting software." <https://www.sigstore.dev/>


# HELPS and HACKS


# Coding Prompt Cheat Sheet

For when you need to tell cursor or replit what a vCon is in a prompt...

> **Spec target:** [`draft-ietf-vcon-vcon-core-02`](https://datatracker.ietf.org/doc/draft-ietf-vcon-vcon-core/) · syntax parameter `"vcon": "0.4.0"`.
>
> **Field-name migration (read this first if you have pre-0.9.1 code):**
>
> * `appended` → `amended`
> * `must_support` → `critical`
> * Attachment field is `purpose` (REQUIRED), never `type` (the one documented exception is the `lawful_basis` extension).
> * Analysis field is `schema` (never `schema_version`); `vendor` is REQUIRED on every analysis entry.

## vCon (Virtual Conversation) Standard - LLM Context

### What is a vCon?

A vCon is a standardized JSON container for storing and exchanging real-time human conversation data. It supports multiple communication types: phone calls, video conferences, SMS, MMS, emails, web chat, and more. vCons enable consistent storage, analysis, and interchange of conversational data across different platforms and services.

### Core Structure

Every vCon has exactly 5 main sections:

1. **metadata** - conversation context and identifiers
2. **parties** - participant information
3. **dialog** - actual conversation content
4. **analysis** - derived insights (transcripts, sentiment, etc.)
5. **attachments** - supplemental files

### vCon States

* **unsigned** - initial/intermediate state during collection
* **signed** - verified with JWS digital signature for immutability
* **encrypted** - secured with JWE for sensitive data

### Complete JSON Schema

#### Top-Level vCon Object Properties

```json
{
  "vcon": "0.4.0",                    // REQUIRED: syntax version
  "uuid": "string",                   // REQUIRED: globally unique identifier
  "created_at": "Date",               // REQUIRED: creation timestamp (RFC3339)
  "updated_at": "Date",               // OPTIONAL: last modification timestamp
  "subject": "string",                // OPTIONAL: conversation topic/subject
  "extensions": [],                   // OPTIONAL: array of extension names this vCon uses (e.g. "lawful_basis", "wtf")
  "must_understand": [],              // OPTIONAL: subset of extensions[] that consumers MUST support to safely process this vCon
  "parties": [],                      // REQUIRED: array of Party objects
  "dialog": [],                       // OPTIONAL: array of Dialog objects
  "analysis": [],                     // OPTIONAL: array of Analysis objects
  "attachments": [],                  // OPTIONAL: array of Attachment objects
  "redacted": {},                     // OPTIONAL: Redacted object (mutually exclusive with amended/group)
  "amended": {}                       // OPTIONAL: Amended object (mutually exclusive with redacted/group)
  // NOTE: `group` is reserved in draft-ietf-vcon-vcon-core-02; do not emit an empty `group: []`.
}
```

#### Party Object Properties

```json
{
  "tel": "string",                    // OPTIONAL: telephone number (E.164 format preferred)
  "stir": "string",                   // OPTIONAL: STIR PASSporT in JWS Compact form
  "mailto": "string",                 // OPTIONAL: email address
  "name": "string",                   // OPTIONAL: participant name
  "validation": "string",             // OPTIONAL: identity validation method used
  "jcard": "object",                  // OPTIONAL: jCard object for contact info
  "gmlpos": "string",                 // OPTIONAL: GML position (lat/long)
  "civicaddress": {                   // OPTIONAL: civic address object
    "country": "string",              // OPTIONAL: country code
    "a1": "string",                   // OPTIONAL: national subdivision (state/province)
    "a2": "string",                   // OPTIONAL: county/parish/district
    "a3": "string",                   // OPTIONAL: city/township
    "a4": "string",                   // OPTIONAL: city division/borough
    "a5": "string",                   // OPTIONAL: neighborhood/block
    "a6": "string",                   // OPTIONAL: street/group of streets
    "prd": "string",                  // OPTIONAL: leading street direction
    "pod": "string",                  // OPTIONAL: trailing street suffix
    "sts": "string",                  // OPTIONAL: street suffix
    "hno": "string",                  // OPTIONAL: house number
    "hns": "string",                  // OPTIONAL: house number suffix
    "lmk": "string",                  // OPTIONAL: landmark
    "loc": "string",                  // OPTIONAL: additional location info
    "flr": "string",                  // OPTIONAL: floor
    "nam": "string",                  // OPTIONAL: name/description
    "pc": "string"                    // OPTIONAL: postal code
  },
  "timezone": "string",               // OPTIONAL: timezone identifier
  "uuid": "string",                   // OPTIONAL: unique participant identifier
  "role": "string",                   // OPTIONAL: participant role (agent, customer, supervisor, etc.)
  "contact_list": "string"            // OPTIONAL: reference to contact list
}
```

#### Dialog Object Properties

```json
{
  "type": "string",                   // REQUIRED: "recording", "text", "transfer", or "incomplete"
  "start": "Date",                    // REQUIRED: start time (RFC3339)
  "duration": "number",               // OPTIONAL: duration in seconds (UnsignedInt or UnsignedFloat)
  "parties": [],                      // REQUIRED: array of party indices or arrays for multi-channel
  "originator": "number",             // OPTIONAL: index of originating party (if not first in parties)
  "mediatype": "string",              // OPTIONAL: MIME type (required for inline, optional if in HTTP header)
  "filename": "string",               // OPTIONAL: original filename
  
  // Content (for types other than "incomplete" and "transfer")
  "body": "string",                   // OPTIONAL: inline content (mutually exclusive with url)
  "encoding": "string",               // REQUIRED with body: "base64url", "json", or "none"
  "url": "string",                    // OPTIONAL: external reference (mutually exclusive with body)
  "content_hash": "string|string[]",  // REQUIRED with url: SHA-512 hash for integrity
  
  // Additional properties
  "disposition": "string",            // REQUIRED for "incomplete" type: reason for failure
  "party_history": [],               // OPTIONAL: array of party join/leave events
  "campaign": "string",               // OPTIONAL: campaign identifier
  "interaction_type": "string",       // OPTIONAL: type of interaction
  "interaction_id": "string",         // OPTIONAL: interaction identifier
  "skill": "string",                  // OPTIONAL: required skill for handling
  "application": "string",            // OPTIONAL: application/platform used
  "message_id": "string",             // OPTIONAL: unique message identifier
  
  // Transfer-specific properties (only for "transfer" type)
  "transferee": "number",             // Party index for transferee role
  "transferor": "number",             // Party index for transferor role  
  "transfer_target": "number",        // Party index for transfer target role
  "original": "number",               // Dialog index for original conversation
  "consultation": "number",           // Dialog index for consultation (optional)
  "target_dialog": "number"           // Dialog index for target conversation
}
```

#### Analysis Object Properties

```json
{
  "type": "string",                   // REQUIRED: "summary", "transcript", "translation", "sentiment", "tts"
  "dialog": "number|number[]",        // OPTIONAL: index(es) of related dialog objects
  "mediatype": "string",              // OPTIONAL: MIME type of analysis data
  "filename": "string",               // OPTIONAL: filename for analysis data
  "vendor": "string",                 // OPTIONAL: vendor/product name that generated analysis
  "product": "string",                // OPTIONAL: specific product name
  "schema": "string",                 // OPTIONAL: data format/schema identifier
  
  // Content
  "body": "string",                   // OPTIONAL: inline analysis data (mutually exclusive with url)
  "encoding": "string",               // REQUIRED with body: encoding method
  "url": "string",                    // OPTIONAL: external reference (mutually exclusive with body)
  "content_hash": "string|string[]"   // REQUIRED with url: integrity hash
}
```

#### Attachment Object Properties

```json
{
  "purpose": "string",                // REQUIRED: purpose of the attachment (e.g. "contract", "screenshot", "synthetic_data_consent")
  "start": "Date",                    // REQUIRED: timestamp when attachment was exchanged
  "party": "number",                  // REQUIRED: index of party who contributed attachment (use 0 for vCon-level attachments)
  "dialog": "number",                 // REQUIRED: index of related dialog (use 0 for vCon-level attachments)
  "mediatype": "string",              // OPTIONAL: MIME type
  "filename": "string",               // OPTIONAL: original filename
  
  // Content
  "body": "string",                   // OPTIONAL: inline attachment data (mutually exclusive with url)
  "encoding": "string",               // REQUIRED with body: encoding method
  "url": "string",                    // OPTIONAL: external reference (mutually exclusive with body)
  "content_hash": "string|string[]"   // REQUIRED with url: integrity hash
}
```

#### Redacted Object Properties

```json
{
  "uuid": "string",                   // REQUIRED: UUID of unredacted version
  "type": "string",                   // REQUIRED: type of redaction performed
  "body": "string",                   // OPTIONAL: inline unredacted vCon (encrypted)
  "encoding": "string",               // REQUIRED with body: encoding method
  "url": "string",                    // OPTIONAL: external reference to unredacted vCon
  "content_hash": "string|string[]"   // REQUIRED with url: integrity hash
}
```

#### Amended Object Properties

> **Spec note:** This object was renamed from `appended` to `amended` in `draft-ietf-vcon-vcon-core-02`. If you are reading legacy code or vCons that use `appended`, treat the name as a synonym and migrate.

```json
{
  "uuid": "string",                   // OPTIONAL: UUID of original vCon version
  "body": "string",                   // OPTIONAL: inline original vCon
  "encoding": "string",               // REQUIRED with body: encoding method
  "url": "string",                    // OPTIONAL: external reference to original vCon
  "content_hash": "string|string[]"   // OPTIONAL with url: integrity hash
}
```

#### Group Object Properties

```json
{
  "uuid": "string",                   // REQUIRED: UUID of vCon to aggregate
  "body": "string",                   // OPTIONAL: inline vCon (JSON form)
  "encoding": "string",               // REQUIRED with body: must be "json"
  "url": "string",                    // OPTIONAL: external reference to vCon
  "content_hash": "string|string[]"   // REQUIRED with url: integrity hash
}
```

#### Party History Object Properties (for Dialog.party\_history)

```json
{
  "party": "number",                  // REQUIRED: index of party
  "event": "string",                  // REQUIRED: "join", "drop", "hold", "unhold", "mute", "unmute"
  "time": "Date"                      // REQUIRED: timestamp of event
}
```

### Security Features

* **JWS Signing** - ensures integrity and authenticity using RS256 (recommended)
* **JWE Encryption** - protects sensitive content using RSA-OAEP + A256CBC-HS512 (recommended)
* **Content Hashing** - SHA-512 hashes for external file integrity (mandatory for external refs)
* **Versioning** - maintains history of changes and redactions via uuid references

### Signed vCon Structure (JWS)

```json
{
  "payload": "string",                // Base64url encoded unsigned vCon
  "signatures": [{                    // Array of signature objects
    "header": {                       // Unprotected header
      "alg": "RS256",                 // SHOULD be RS256
      "x5c": ["string"],              // REQUIRED: certificate chain OR x5u
      "x5u": "string",                // REQUIRED: cert chain URL OR x5c
      "uuid": "string"                // SHOULD be provided: vCon UUID for convenience
    },
    "protected": "string",            // Base64url encoded protected header
    "signature": "string"             // Base64url encoded signature
  }]
}
```

### Encrypted vCon Structure (JWE)

```json
{
  "unprotected": {                    // Unprotected header
    "cty": "application/vcon+json",   // SHOULD be application/vcon+json
    "enc": "A256CBC-HS512",           // SHOULD be A256CBC-HS512
    "uuid": "string"                  // SHOULD be provided: vCon UUID
  },
  "recipients": [{                    // Array of recipient objects
    "header": {                       // Per-recipient header
      "alg": "RSA-OAEP"               // SHOULD be RSA-OAEP
    },
    "encrypted_key": "string"         // Base64url encoded encrypted key
  }],
  "iv": "string",                     // Base64url encoded initialization vector
  "ciphertext": "string",             // Base64url encoded encrypted signed vCon
  "tag": "string"                     // Base64url encoded authentication tag
}
```

### Common Media Types

#### Dialog

* `text/plain` - plain text messages
* `audio/x-wav` - WAV audio files
* `audio/x-mp3` - MP3 audio files
* `audio/x-mp4` - MP4 audio files
* `audio/ogg` - OGG audio files
* `video/x-mp4` - MP4 video files
* `video/ogg` - OGG video files
* `multipart/mixed` - multipart content (emails)

#### Content Encoding Options

* `base64url` - Base64url encoded binary data
* `json` - Valid JSON object
* `none` - Valid JSON string, no encoding needed

### Implementation Guidelines

* Follow JSON schema strictly for compliance
* Use proper timestamps (RFC3339/ISO 8601 format)
* Ensure UUIDs are globally unique (prefer version 8 with domain-based generation)
* Implement proper signing/encryption for production use
* Maintain referential integrity between sections
* Always use HTTPS for external file references
* Validate content hashes for external files using SHA-512

### Python Development Notes

* Use `uuid` library for generating UUIDs (version 8 recommended)
* `datetime.isoformat()` for RFC3339 timestamps
* `json` module for serialization/deserialization
* `cryptography` library for JWS/JWE operations
* `hashlib` for SHA-512 content hash generation
* Validate against vCon JSON schema before processing
* Use `requests` with SSL verification for external file retrieval

### Key Considerations

* vCons can reference previous versions (redaction/amendment history via uuid)
* Media content can be embedded (body + encoding) or referenced externally (url + content\_hash)
* Privacy and compliance requirements vary by jurisdiction
* Large media files should typically be stored as external references
* Ensure proper escaping of JSON content in dialog sections
* Support both single-channel and multi-channel audio recordings
* Handle participant join/leave events in party\_history for complex conversations
* Maintain chain of custody through signing and encryption across security domains

### Validation Rules

* At most one of: redacted, amended, or group parameters in top-level object
* Dialog objects of type "incomplete" or "transfer" MUST NOT have body/url content
* Dialog objects of other types SHOULD have body+encoding OR url+content\_hash
* External references (url) MUST include content\_hash for integrity
* Signed vCons MUST include x5c OR x5u in header for certificate chain
* Party indices in dialog.parties must reference valid parties array elements
* Dialog indices in analysis.dialog must reference valid dialog array elements

### UUID Generation (Version 8 Recommended)

```python
# Recommended UUID generation approach
import hashlib
import uuid
from datetime import datetime

def generate_vcon_uuid(domain="example.com"):
    """Generate version 8 UUID for vCon with domain-based uniqueness"""
    timestamp = int(datetime.utcnow().timestamp() * 1000)  # milliseconds
    domain_hash = hashlib.sha1(domain.encode()).digest()[:8]  # 62 bits
    
    # Construct version 8 UUID (implementation details vary)
    # Use standard uuid library with custom generation
    return str(uuid.uuid4())  # Fallback to uuid4 if version 8 not available
```


# VCONIC TADHack 2026: Hackathon Review

A review of all 16 submissions from the VCONIC TADHack 2026 hackathon, held March 7-8, 2026 — with winners, individual project reviews, and themes.

> **Event:** [VCONIC TADHack](https://blog.tadhack.com/2025/12/19/vconic-tadhack/) | March 7-8, 2026 | Virtual
>
> **Core technology:** [vCon](https://datatracker.ietf.org/doc/draft-ietf-vcon-vcon-container/) (IETF standard) | [vCon MCP Server](https://github.com/vcon-dev/vcon-mcp) ([docs](https://www.conserver.io/mcp-server/what-is-the-vcon-mcp-server) | [live](https://mcp.conserver.io/))

## Overview

The VCONIC TADHack 2026 (March 7-8) drew 16 submissions from developers across four continents — Nigeria, Kenya, the United States, and Brazil — tackling problems in emergency response, financial services, education, compliance, supply chains, animal rescue, community safety, and personal memory. Every team built on the [vCon standard](https://datatracker.ietf.org/group/vcon/about/). Most connected to the [vCon MCP Server](https://github.com/vcon-dev/vcon-mcp). Several pushed the boundaries of what vCon can represent, demonstrating that a conversation container originally designed for telephony is becoming a general-purpose intelligence format.

***

## Winners

> **Judge:** Thomas McCarthy-Howe | **Host:** Alan Quayle ([TADHack](https://tadhack.com))

Winners were selected across two tracks: a **Senior/Professional Track** recognizing innovation and community leadership, and a **Student Track** with cash prizes for emerging developers.

### Senior / Professional Track

*These awards recognize innovation, leadership, and community impact from experienced practitioners.*

#### Senior Class Winner — Apparitions

**Team:** David Sikes & Jared Ashcraft | [Video](https://www.youtube.com/watch?v=kYvfCVWyz1M)

A location-based augmented reality framework where each point of interest is a vCon. Users walk through physical spaces — museums, historical sites, mystery stories — and audio/media triggers as they approach GPS coordinates stored in vCon attachments.

> *"When I first saw that hack, I was on the way over to China and I put the laptop down. I shut the laptop because my head was spinning... I hadn't thought of this. To be able to take a vCon as a dialogue, as a piece of data — to power museum experiences, product experiences, real-time experiences — and such a small piece of integration. No APIs. Wow."*

#### Matriculation Award — Ollie

**Team:** Anna Correa | [Video](https://www.youtube.com/watch?v=guBpk1E9yZA)

An AI-powered platform to help find and rescue lost animals faster through coordinated conversation tracking.

> *"Anna is not acting like a student. The creativity, the execution, the understanding of what vCons do and why they do it, and just the technical sense — she's no longer allowed to get the student discount."*

#### Senior Respect Mention — vCon Example App & vCon Intelligence Platform

**Team:** Muntaser Syed | [Video (Example App)](https://www.youtube.com/watch?v=msvvAcZFEng) | [Video (Intelligence Platform)](https://www.youtube.com/watch?v=h4ehOvFSqLw)

A reference implementation demonstrating vCon JSON-LD extensions, cryptographic signing, and MongoDB vector search — plus a comprehensive multi-backend platform for conversation intelligence.

> *"What an incredible intellect. What great work. Muntaser did the MCP extension into vCon, and that's a PR we're testing right now to pull in — that's going to be part of the MCP server forever."*

### Student Track

*Cash prizes for emerging developers.*

#### Grand Prize — vCohort ($3,000)

**Team:** Ziyad Shuaibu, Abdulalim Ladan, Mubarak Ibrahim | [Video](https://www.youtube.com/watch?v=j6f88p8DIZU)

An educational platform supporting bootcamps and cohort-based learning in Nigeria, using vCon to capture and structure educational conversations.

> *"It really showed complete understanding of vCon. It's a piece of data, we're getting it from different places — and here's a real strong business case."*

#### Honorable Mention — Community Watch ($1,000)

**Team:** Victor Abdul | [Video](https://www.youtube.com/watch?v=7RWY4BdJNb8)

Turns fragmented neighborhood safety reports into a unified intelligence stream using vCon and Groq AI.

> *"I love that — in the same way as Apparitions or Ollie — I like that we're treating it like data. I like understanding this is just another piece of data, how do we use this data in interesting ways?"*

#### Honorable Mention — Life Canvas ($1,000)

**Team:** Sabrina (Valencia College) | [Video](https://www.youtube.com/watch?v=C0UhGJR72pk)

A personal life intelligence system that captures everyday moments and turns them into searchable, structured insights using vCon.

> *"Just neck and neck with Anna... amazing personal development work you can do with this, at a very high level of presentation. She plucks at the heartstrings."*

***

## Individual Reviews

### 1. 911 First Response — Shouvik Sharma & Ankita Bhanushali

> [Video](https://www.youtube.com/watch?v=HRKcU5U-gzI)

**The idea:** Turn a live 911 call into a structured dispatch action in real time. The moment a call comes in, it becomes a vCon. AI extracts location, emergency type, severity, and incident classification (medical, fire, burglary, accident), then triggers the appropriate dispatch workflow — choosing the right service, finding the nearest unit, and dispatching immediately.

**What they built:** Two working demos. First, a programmatic MCP consumption script that ingests vCon files from a `911_calls` folder, performs health checks, lists stored vCons, fetches by UUID, and prints the structured dispatch action (e.g., "Dispatch the nearest police unit to 5520 Cedar Lane. Reason: burglary in progress"). Second, a Streamlit-based Conversation Viewer with a table of all vCons, per-conversation detail views, audio playback, transcript display, and raw JSON inspection.

**vCon capabilities demonstrated:**

* vCon as the canonical record for an emergency call, from raw audio through AI-derived dispatch instructions
* The analysis array carrying transcript, summary, speaker diarization, and a structured action — all attached to the same vCon
* MCP server as the query layer for both scripts and AI assistants

**What makes it unique:** This is the most operationally immediate submission. It's not a dashboard or analytics tool — it's a real-time pipeline where seconds matter. The structured action output (service type, nearest unit, dispatch reason) shows vCon moving beyond record-keeping into decision-triggering.

***

### 2. Apparitions — David Sikes & Jared Ashcraft

> [Video](https://www.youtube.com/watch?v=kYvfCVWyz1M) | **Senior Class Winner**

**The idea:** A location-based augmented reality framework where each point of interest is a vCon. Users walk through physical spaces — museums, historical sites, mystery stories — and audio/media triggers as they approach GPS coordinates stored in vCon attachments.

**What they built:** A working mobile prototype demonstrating a mystery story scenario. As the user physically approaches real-world objects (a garage, a street), audio plays with volume that increases with proximity. The entire scenario is a list of vCons — swapping vCons changes events. Text scrolls from the dialog body; latitude, longitude, and audio file paths are stored as typed attachments.

**vCon capabilities demonstrated:**

* vCon as a unit of location-based experience, not a conversation record
* Attachments with type identifiers for coordinates, media paths, and metadata
* The list-of-vCons pattern for sequencing events
* Shareability — because each scenario is just a list of vCon JSON files, scenarios transfer trivially between devices

**What makes it unique:** This is the most creative reinterpretation of what a vCon can be. The team treated vCon not as a conversation format but as a general-purpose container for location-tagged, sequenceable media events. Their future plans (browsing scenarios by location and category, MCP-powered voice synthesis for user-generated content) point toward a vCon-native content distribution platform. No other submission reimagined the standard this boldly.

***

### 3. Budget Yangu — Elvis Ogunga

> [Video](https://www.youtube.com/watch?v=fxBPIaZPSTk)

**The idea:** "My Budget" in Swahili — an AI-powered personal finance assistant where every conversation between the user and the AI becomes a vCon record that can be analyzed for financial insights.

**What they built:** A Laravel-backed application where users manually input financial records and then use an AI agent to analyze their data. The agent retrieves user data via authenticated sessions, analyzes it, and every interaction is captured as a vCon with parties, dialog, and analysis. The app sends vCons to the MCP server running on a local node. A dashboard shows total vCon count, dialog types, conversation subjects, and data health metrics.

**vCon capabilities demonstrated:**

* vCon as the persistence layer for human-AI financial conversations
* The full vCon creation pipeline in Laravel: initializing the vCon service, installing dialog, handling party names and roles
* MCP server integration for storage and retrieval
* Analysis array for AI-derived financial insights

**What makes it unique:** This is a solo developer building a complete financial product with vCon at the center. The insight that every financial AI conversation is worth preserving as a structured record — not just for the user, but for improving the AI and the business — is commercially astute.

***

### 4. Community Watch — Victor Abdul

> [Video](https://www.youtube.com/watch?v=7RWY4BdJNb8) | **Student Honorable Mention ($1,000)**

**The idea:** Turn fragmented neighborhood safety reports into a unified, interoperable intelligence stream. Community sightings — suspicious activity, incidents, hazards — are structured as vCon records, aggregated in real time, and analyzed by Groq AI to give residents, NGOs, and first responders a shared operational picture of what's happening on the ground.

**What they built:** A platform that ingests neighborhood safety reports from multiple sources, converts them to vCon records, and runs Groq AI analysis for pattern detection and real-time alerts. The unified dashboard gives different stakeholders (residents, NGOs, first responders) visibility into community safety events with structured data they can act on. One standard, one platform, faster answers.

**vCon capabilities demonstrated:**

* vCon as the standard for community incident data — one format regardless of report source
* Real-time aggregation of disparate safety reports into structured, queryable records
* Groq AI analysis stored in the vCon analysis array for pattern detection and alert generation
* Multi-stakeholder access: residents, NGOs, and emergency services sharing the same data structure

**What makes it unique:** This submission applies vCon's "treat it like data" principle to community safety — a domain where fragmented, informal reports are the norm. The emphasis on interoperability (one standard for all stakeholders) mirrors ConvoSense's anti-lock-in positioning but in a civic context. As the judge noted: "vCons have two sides — they protect people. And protecting people is more than just the protocol to us."

***

### 5. ConsentMate — Abdurrahman Umar & Berlu (Team Skyline Coders)

> [Video](https://www.youtube.com/watch?v=WUdpfmbMAAQ)

**The idea:** An AI-powered GDPR compliance dashboard that tracks customer consent across all recorded conversations, provides compliance scoring, and delivers daily briefings.

**What they built:** A Next.js dashboard with four core views: (1) a Daily Briefing that delivers a personalized compliance update each morning with a consent compliance score, (2) a Transcript tab previewing all vCon calls, (3) an Analysis tab breaking down each conversation for sentiment, compliance score, and key highlights, and (4) a Consent tab tracking active, expired, and expiring consent in one place. Backend AI generates real-time compliance messages and analyzes transcripts.

**vCon capabilities demonstrated:**

* vCon as the source of truth for consent state — every call is a vCon, and consent status is derived from the vCon record
* AI analysis of vCon transcripts for compliance scoring
* The analysis array carrying sentiment, compliance scores, and highlights

**What makes it unique:** This is the submission most directly aligned with the [vCon Lawful Basis extension](https://datatracker.ietf.org/doc/draft-howe-vcon-lawful-basis/). The daily compliance briefing with a numerical score is a compelling UX pattern — it turns a legal obligation into something a business owner can glance at over coffee.

***

### 6. ConvoLens — Josphat Mwangi

> [Video](https://www.youtube.com/watch?v=lX8pXsz-47c)

**The idea:** A customer conversation intelligence platform for banking and financial services that ingests conversations from any channel, converts them to vCon, analyzes them with AI, and provides a live dashboard.

**What they built:** The most feature-rich analytics platform in the hackathon. ConvoLens does three things: (1) ingests conversations from WhatsApp exports, call transcripts, tweet threads, or any text source and converts them to IETF-standard vCon records; (2) analyzes every conversation using Claude for sentiment, issue classification, complaint flagging, and action recommendations — all stored in the vCon analysis array; (3) provides a live dashboard with compliance risk alerts, customer journey timelines, and an "Ask Claude" chat where teams can type natural language questions ("What are the top complaints this week?") and get instant answers. Also runs the official vCon MCP server alongside the platform, letting Claude Desktop query vCon records through MCP natively.

**vCon capabilities demonstrated:**

* Multi-channel ingestion normalized to vCon: WhatsApp, Twitter/X, Facebook, Instagram, call center, email, CRM
* The vCon analysis array as a rich structured output: sentiment, issue classification, compliance flags, action recommendations
* MCP server running alongside the app for native AI assistant access
* Pure vCon JSON API — any external tool that understands vCon can consume ConvoLens directly
* Open source (GitHub), built on Next.js, Supabase, and Anthropic Claude

**What makes it unique:** The banking/financial services focus is commercially sharp — this is a heavily regulated industry where conversation compliance is mandatory, not optional. The "Ask Claude" feature querying vCon data through MCP is a clean demonstration of the AI-native future of conversation intelligence.

***

### 7. ConvoSense — Collins Omondi

> [Video](https://www.youtube.com/watch?v=XNn8HUwuzec)

**The idea:** A platform that solves the three core problems of SMB customer support: data scattered across platforms, manual effort to derive insights, and vendor lock-in.

**What they built:** A dashboard that connects to VAPI (voice AI) and Intercom via internal plugins using API keys. Once connected, it pulls all conversations, then connects to the vCon MCP server. On connection, conversations are automatically synchronized into vCon format. An AI assistant with built-in visualization tools and all vCon MCP server tools can answer questions a support team would ask, providing analysis in seconds rather than hours.

**vCon capabilities demonstrated:**

* Plugin-based ingestion from VAPI and Intercom, converting proprietary formats to vCon
* The MCP server's `create_vcon` tool used programmatically for format conversion
* vCon as the anti-vendor-lock-in layer — businesses connect their existing tools without migrating
* AI assistant querying both platforms simultaneously through vCon tools

**What makes it unique:** The vendor lock-in angle is the most strategically important idea in the hackathon. By positioning vCon as the interoperability layer between VAPI, Intercom, and future platforms, ConvoSense demonstrates that vCon can be the "PDF of conversations" — a format that liberates data from proprietary silos.

***

### 8. Life Canvas — Sabrina (Valencia College)

> [Video](https://www.youtube.com/watch?v=C0UhGJR72pk) | **Student Honorable Mention ($1,000)**

**The idea:** A personal life intelligence system that turns everyday moments — journal entries, photos, voice notes, moods — into structured vCon records that become searchable, analyzable life data.

**What they built:** A full web application with: a Dashboard (journal entries, streaks, highlights, recent memories), a Journal (capture reflections via writing, photos, voice notes, or mood selection), Buckets (life categories: family, career, health, personal growth), a Calendar (revisit memories by date), Analytics (emotional trends, common topics, most-mentioned people), Memory Search, a Timeline (visual stories of personal growth), and Year in Review (annual milestones and insights). Behind the scenes, every entry becomes a structured vCon record, and AI detects meaningful patterns.

**vCon capabilities demonstrated:**

* vCon applied to personal life data, not business conversations
* Multi-modal capture (text, photo, voice, mood) all structured into the vCon format
* The analysis array used for emotional pattern detection and life insights
* Privacy-first design with user-controlled access through the MCP server

**What makes it unique:** This is the most emotionally resonant submission. The opening pitch — "How much of your life do you actually remember?" — reframes vCon from a business tool to a personal one. The idea that your life is a series of conversations worth structuring is philosophically aligned with vCon's vision, and the execution (buckets, analytics, year-in-review) shows a thoughtful product designer at work.

***

### 9. Ollie — Anna Correa

> [Video](https://www.youtube.com/watch?v=guBpk1E9yZA) | **Matriculation Award**

**The idea:** An AI platform that crawls Reddit for lost pet reports, converts them to vCon records, and provides actionable tools for animal rescue coordination.

**What they built:** A Next.js application deployed on Vercel that: (1) curls Reddit daily, finding posts about lost or stray animals; (2) converts each conversation into a vCon and stores it in MongoDB; (3) runs AI enrichment (summary, sentiment, location extraction); (4) provides a Live Feed, Map view (with animal locations plotted on a map), and Help Now page (filter by support/foster requests); (5) includes a chat assistant on every page ("Are there any dogs near Austin?") that queries the vCon data; (6) includes a custom MCP server built from scratch, designed to layer on top of the existing vCon MCP server.

**vCon capabilities demonstrated:**

* Automated web scraping → vCon conversion pipeline
* vCon as the normalization layer for unstructured Reddit data
* Custom MCP server tools: `search_animal_conversations`, `find_lost_pets`, `find_ways_to_help`
* MongoDB as the vCon store with AI enrichment
* Chat assistant querying vCon data across all pages

**What makes it unique:** This is the most heartwarming submission and demonstrates the most creative data source. Nobody else scraped external web content into vCons. The custom MCP server built from scratch, designed to compose with the existing vCon MCP server, shows strong architectural thinking.

***

### 10. OnePrice Sales Memory — Joan Ovalles Rosario (Valencia College)

> [Video](https://www.youtube.com/watch?v=XCIGV91PZn4)

**The idea:** End information decay in auto sales. Consolidate multi-channel customer interactions into a structured vCon-based memory system so salespeople never lose context on a deal.

**What they built:** A working sales workflow demo: create a lead (Juan Soto, interested in Ford F-150, budget $1,000/month), run AI tagging analysis (warm lead, payment-sensitive, shopping competitors, price shock, needs manager follow-up), search across all vCons for hot leads matching criteria ("search my vCons for any hot leads who are highly sensitive to monthly payments"), and generate personalized follow-up scripts built directly from the vCon profile. The system uses Docker, the MCP server, OpenAI for analysis, and Supabase for storage.

**vCon capabilities demonstrated:**

* vCon as CRM memory — each customer interaction is a persistent, searchable record
* AI-powered tagging stored in the vCon analysis array
* MCP-based search across the vCon corpus ("find payment-sensitive leads")
* Follow-up script generation grounded in vCon data, not generic templates
* Multi-location dealership support

**What makes it unique:** The "information decay" framing is brilliant. Every salesperson knows the pain of losing context on a deal. The demo flow — create lead, tag, search, generate personalized follow-up — is a complete sales workflow, not just a proof of concept.

***

### 11. Patanisha — Charles Wachira

> [Video](https://www.youtube.com/watch?v=bl_YNeu3MCM)

**The idea:** "Patanisha" is Swahili for "unifying." The first support platform built on the vCon standard — every customer interaction (phone, SMS, email, chat) automatically becomes a vCon, unified into a single timeline.

**What they built:** A live demo using Africa's Talking APIs for voice and SMS in the Kenyan market. The dashboard shows cases on the left, loaded from TADHack 2025 data as proof of scale. A simulated customer interaction demonstrates a voice call, SMS, and email all unified into one case timeline. Agents can claim cases, send SMS responses via Africa's Talking, and mark cases as resolved. All data flows to a Supabase backend. The team used Conserver.io for vCon storage and AI processing.

**vCon capabilities demonstrated:**

* "Phone call, that's a vCon. SMS, that's a vCon. Email, that's a vCon." — the clearest articulation of vCon's unification promise
* [Africa's Talking](https://africastalking.com/) API integration for real-world telephony in the Kenyan market
* [Conserver.io](https://www.conserver.io/) as the vCon backend
* Supabase integration for the dashboard

**What makes it unique:** Patanisha is the submission closest to production. It uses real telephony APIs (Africa's Talking) in a real market (Kenya), handles real communication channels (voice + SMS + email), and is deployed at a real URL (patanisha.ruita.co.ke).

***

### 12. TraceConnect — Jevans Otieno

> [Video](https://www.youtube.com/watch?v=POUeuloABtU)

**The idea:** Enterprise-grade global visibility for distributed teams. HQ gets real-time intelligence on how branches in different countries handle issues and complaints, powered by vCon and Claude Desktop via MCP.

**What they built:** A Radar Dashboard showing activity across branches (Nairobi, Lagos, New York), with metrics like open issues, resolved issues, and average resolution time per branch. An "Ask TraceConnect" sidebar lets HQ query across all branches via natural language ("Walk me through the enterprise outage escalation in Lagos — show me the full conversation trail"). The system connects vCon to Claude Desktop via MCP for direct querying.

**vCon capabilities demonstrated:**

* vCon as the standardized conversation format across global branches
* Cross-region intelligence — querying vCons from Nairobi, Lagos, and New York through a single interface
* Claude Desktop connected to vCon via MCP for natural language queries
* Conversation trail reconstruction from vCon records
* Branch-level performance metrics derived from vCon data

**What makes it unique:** This is the only submission targeting enterprise HQ-to-branch visibility. The use case — "something might be happening in real-time but HQ only finds out at the weekly meeting" — is immediately recognizable to anyone who's managed distributed operations.

***

### 13. vChat — Ahmadu Suleiman

> [Video](https://www.youtube.com/watch?v=7_PImyiISn8)

**The idea:** Bring vCon to everyone's phone. A general-purpose messaging app where you chat normally and vCon structures everything in the background — who said what, in what role, when, and what was agreed.

**What they built:** A mobile-optimized prototype with two modes: (1) start a new conversation in-app with role assignment, structured in real time; (2) import existing WhatsApp, SMS, or email threads, assign roles, and convert them into the same structured vCon format. Three demo vCons show use cases in land disputes, healthcare, and education. MCP server attachment enables AI capabilities directly on conversations — insights, semantic search, and more. Supports vCon export/download for full data portability and optional redaction for privacy.

**vCon capabilities demonstrated:**

* Real-time vCon creation during live chat
* Import and conversion of WhatsApp/SMS/email threads to vCon
* Role-based party attribution (critical for mediation, legal, and dispute contexts)
* MCP integration for AI-powered conversation analysis
* vCon export for institutional sharing (banks, lawyers, government offices)
* Selective redaction before sharing

**What makes it unique:** vChat's positioning as "vCon for everyone" is the most ambitious consumer play. The import-from-WhatsApp feature is strategically important: it means existing conversations, not just future ones, can enter the vCon ecosystem. The emphasis on verifiability ("signed and independently verifiable") positions vCon as a trust layer for informal agreements.

***

### 14. vCohort — Ziyad Shuaibu, Abdulalim Ladan & Mubarak Ibrahim

> [Video](https://www.youtube.com/watch?v=j6f88p8DIZU) | **Student Grand Prize ($3,000)**

**The idea:** A conversation intelligence layer for Nigeria's booming cohort-based learning market ($1.5B in 2025). Learning sessions on Zoom and Google Meet become vCon records, generating engagement analytics, confusion signals, and intervention recommendations.

**What they built:** A full platform with: organization management, cohort creation, session upload/live recording ingestion. Per-session analytics include duration, speaking time vs. silence, engagement scores, questions asked, key moments, action items, and per-participant speaking time breakdowns. A transcript preview is available for each session, and vCon files can be downloaded in JSON format. The team built integrations for Zoom, Google Meet, and planned Trello/Slack/Google Calendar integration.

**vCon capabilities demonstrated:**

* vCon as the intelligence layer for educational conversations
* Bot-joins-meeting pattern for live vCon capture
* Session upload for asynchronous recording ingestion
* Rich analysis array: engagement scores, confusion signals, speaking time distribution, action items
* vCon download/export for portability
* Organization → Cohort → Session hierarchy modeled with vCon

**What makes it unique:** The market insight is sharp — Nigeria's education sector is growing fast, and the gap between "we had a Zoom call" and "we understand what happened in that call" is enormous. The "at-risk participants" detection (learners who aren't engaging) demonstrates vCon enabling proactive intervention, not just passive recording.

***

### 15. vCon Example App — Muntaser Syed (Submission 1)

> [Video](https://www.youtube.com/watch?v=msvvAcZFEng) | **Senior Respect Mention** | [Pull request on vcon-mcp](https://github.com/vcon-dev/vcon-mcp)

**The idea:** A reference implementation demonstrating vCon enhancements: JSON-LD extensions for richer metadata, cryptographic signing for integrity verification, and MongoDB integration for analytics and vector search.

**What they built:** An interactive web app where users can: load sample vCons, add JSON-LD context and extensions (external types, confidence labels), enrich analysis with extended metadata, cryptographically sign a vCon, verify integrity (tampering detection demonstrated by modifying a field and showing the signature check fail), and perform MongoDB-backed vector search across vCon embeddings.

**vCon capabilities demonstrated:**

* JSON-LD and JSON-LD-EX extensions for semantic enrichment
* Cryptographic signing and integrity verification ([JWS](https://datatracker.ietf.org/doc/draft-ietf-vcon-vcon-container/))
* Tamper detection — changing a single field breaks the signature
* MongoDB vector search over vCon embeddings
* Confidence labels for agentic AI trust decisions

**What makes it unique:** This is the most technically deep submission. The cryptographic signing demo — sign, tamper, verify failure, restore — is the clearest demonstration of vCon's integrity guarantees in the entire hackathon. Submitted as an actual pull request to the vCon MCP repository.

***

### 16. vCon Intelligence Platform — Muntaser Syed (Submission 2)

> [Video](https://www.youtube.com/watch?v=h4ehOvFSqLw) | **Senior Respect Mention**

**The idea:** A comprehensive multi-backend intelligence platform that ingests conversations from every source, enriches them, stores them in multiple database backends, and provides graph-based visualization and AI-powered querying.

**What they built:** A full platform supporting: direct input, SIP REC recordings, Microsoft Teams transcripts, WhatsApp chat exports, and direct audio upload with automatic Whisper transcription. Multiple storage backends: MongoDB, Neo4j (graph database), ChromaDB (vector store), and Supabase. MQTT-based event system for real-time ingestion notifications. A Neo4j-powered graph visualization showing agents, customers, conversations, and topic nodes with their relationships. A JSON-LD-EX inspector for enhanced vCon viewing. AI chat interface for querying across all ingested vCons. Sentiment analysis timeline across all conversations.

**vCon capabilities demonstrated:**

* The widest ingestion surface: direct input, SIP REC, Teams, WhatsApp, audio upload
* MQTT integration for real-time event-driven architecture
* Neo4j graph visualization of conversation networks
* Multi-backend storage: MongoDB, Neo4j, ChromaDB, Supabase
* JSON-LD-EX enhanced vCon format with extended analytics fields
* Whisper-based automatic transcription on audio upload
* AI-powered cross-corpus querying

**What makes it unique:** This is the most architecturally ambitious submission. The multi-backend approach (relational + graph + vector + document) acknowledges that different query patterns need different storage engines. The MQTT integration connects vCon to industrial event-driven architectures, bridging the gap between conversation data and IoT/manufacturing systems.

***

## Themes and Patterns

### Geographic Reach

The hackathon drew developers from at least four countries across three continents:

* **Kenya:** Patanisha (Charles Wachira), ConvoLens (Josphat Mwangi), ConvoSense (Collins Omondi), TraceConnect (Jevans Otieno), Budget Yangu (Elvis Ogunga)
* **Nigeria:** vCohort (Ziyad Shuaibu et al.), vChat (Ahmadu Suleiman), ConsentMate (Abdurrahman Umar & Berlu)
* **United States:** 911 First Response (Shouvik Sharma & Ankita Bhanushali), Apparitions (David Sikes & Jared Ashcraft), Life Canvas (Sabrina), OnePrice Sales Memory (Joan Ovalles Rosario), vCon Example App & Intelligence Platform (Muntaser Syed)
* **Brazil:** Ollie (Anna Correa)

The strong East African and West African representation is notable. Five of the six Kenyan/Nigerian submissions address local market needs (financial services in Kenya, education in Nigeria, Swahili-named products). This suggests vCon is finding natural traction in markets where conversation data is plentiful but infrastructure for structuring it is scarce.

### vCon as More Than Telephony

The original vCon use case was capturing phone calls. This hackathon proved the format's generality:

| What vCon represented                      | Submission                 |
| ------------------------------------------ | -------------------------- |
| Emergency 911 calls                        | 911 First Response         |
| Location-based AR experiences              | Apparitions                |
| Human-AI financial conversations           | Budget Yangu               |
| Community safety incident reports          | Community Watch            |
| GDPR consent records                       | ConsentMate                |
| Multi-channel banking complaints           | ConvoLens                  |
| Voice AI + Intercom chats                  | ConvoSense                 |
| Personal journal entries                   | Life Canvas                |
| Reddit posts about lost pets               | Ollie                      |
| Auto dealership sales interactions         | OnePrice Sales Memory      |
| Phone + SMS + email support cases          | Patanisha                  |
| Global enterprise branch conversations     | TraceConnect               |
| Community mediation agreements             | vChat                      |
| Educational bootcamp sessions              | vCohort                    |
| Semantically enriched conversation records | vCon Example App           |
| SIP REC + Teams + WhatsApp + audio         | vCon Intelligence Platform |

The most surprising entries — Apparitions (vCon as AR event container), Life Canvas (vCon as life journal record), and Ollie (vCon as Reddit post container) — show that developers intuitively extend the standard beyond its original scope when the format is flexible enough.

### The MCP Server as Enabler

Nearly every submission connected to the vCon MCP Server, but they used it differently:

* **Query layer for AI assistants:** 911 First Response, ConvoLens, TraceConnect, OnePrice Sales Memory
* **Format conversion gateway:** ConvoSense (VAPI/Intercom → vCon), vCon Intelligence Platform (Teams/WhatsApp/SIP REC → vCon)
* **Storage and retrieval backend:** Budget Yangu, Patanisha, vCohort
* **Custom MCP server built on top:** Ollie (animal-specific tools layered on vCon MCP)

The pattern of "connect the MCP server, then query with natural language" appeared repeatedly, validating the MCP approach as the AI-native interface for conversation data.

### Notable Innovations

1. **Apparitions' vCon-as-content-unit pattern** — Each location event is a vCon; a scenario is a list of vCons. The most novel structural use of the format.
2. **ConvoSense's anti-vendor-lock-in positioning** — vCon as the interoperability layer between proprietary platforms. Strategically important for adoption.
3. **Ollie's web-scraping-to-vCon pipeline** — Proving that vCon can normalize unstructured web content, not just structured API data.
4. **vChat's import-existing-conversations feature** — Existing WhatsApp/SMS threads can be retroactively structured into vCon.
5. **vCon Example App's tamper-detection demo** — The most visceral demonstration of vCon integrity: change one character, signature breaks.
6. **vCohort's "at-risk learner" detection** — Using conversation analysis for proactive intervention, not just reporting.
7. **Community Watch's civic data model** — One vCon standard shared across residents, NGOs, and first responders, eliminating fragmentation in community safety data.
8. **vCon Intelligence Platform's MQTT integration** — Connecting vCon to industrial event-driven architectures.

### What the Hackathon Proved About vCon

1. **The format is genuinely general-purpose.** Developers applied it to AR experiences, personal journals, Reddit posts, community safety reports, and emergency dispatch — none of which are traditional "conversations" — and the format held up.
2. **The MCP Server is the right abstraction.** Teams spent their time building applications, not fighting with data ingestion.
3. **The analysis array is vCon's killer feature for AI.** Nearly every submission used it to store AI-derived insights (sentiment, compliance scores, dispatch actions, engagement metrics) alongside the raw conversation. This "raw data + derived intelligence in one container" pattern is what makes vCon more than just a recording format.
4. **vCon attracts developers who think in systems.** These aren't toy demos. Multiple submissions (ConvoLens, vCon Intelligence Platform, Patanisha, vCohort) are architecturally complete enough to evolve into products.
5. **The East African developer community is a natural fit.** Five Kenyan and three Nigerian submissions, many addressing local market needs with local telephony APIs ([Africa's Talking](https://africastalking.com/)), suggest that vCon has found an enthusiastic early-adopter community in a region where conversation-heavy business processes are growing rapidly.

***

## Resources

### Specifications and Standards

* [vCon Container Format](https://datatracker.ietf.org/doc/draft-ietf-vcon-vcon-container/) — The core IETF standard
* [vCon Working Group](https://datatracker.ietf.org/group/vcon/about/) — IETF working group developing the standard
* [vCon Lawful Basis Extension](https://datatracker.ietf.org/doc/draft-howe-vcon-lawful-basis/) — Consent and legal basis tracking
* [vCon Lifecycle Management](https://datatracker.ietf.org/doc/draft-howe-vcon-lifecycle/) — SCITT-based transparency services
* [SIP Extension for MCP](https://datatracker.ietf.org/doc/draft-howe-sipcore-mcp-extension/) — SIP protocol extension for MCP discovery

### Tools and Platforms

* [vCon MCP Server](https://github.com/vcon-dev/vcon-mcp) — The core MCP server used by most submissions ([docs](https://www.conserver.io/mcp-server/what-is-the-vcon-mcp-server) | [live server](https://mcp.conserver.io/))
* [vCon GitHub Organization](https://github.com/vcon-dev) — All vCon open source projects
* [Conserver.io](https://www.conserver.io/) — VCONIC's vCon platform
* [vCon UNS Starter Kit](https://github.com/fieldcloud/vcons-uns-starter-kit) — vCon + Unified Namespace for manufacturing
* [Africa's Talking](https://africastalking.com/) — Voice and SMS APIs (used by Patanisha)
* [Hackathon vCon files](https://github.com/vcon-dev/vcon-the-hacks) — All 16 submission vCons with transcripts and AI summaries

### Training Sessions

| Session                                 | Presenter               | Video                                                |
| --------------------------------------- | ----------------------- | ---------------------------------------------------- |
| VCONIC TADHack Background and Resources | Thomas McCarthy-Howe    | [Watch](https://www.youtube.com/watch?v=Tm6EewfOa4M) |
| AI Coding for Beginners                 | Rob Pickering           | [Watch](https://www.youtube.com/watch?v=byP02fQe7sI) |
| Consent and Lifecycle                   | Thomas McCarthy-Howe    | [Watch](https://www.youtube.com/watch?v=jzOw94GKcbs) |
| Spec-Driven Development                 | Jason Goecke            | [Watch](https://www.youtube.com/watch?v=FnuzyBi2ntw) |
| The VCON App Store                      | Audrey Hayn, MindMaking | [Watch](https://www.youtube.com/watch?v=cwgY3D7DVdY) |
| VCON + UNS for Manufacturing            | Matthew Smith           | [Watch](https://www.youtube.com/watch?v=QNLUXTr3JI4) |

### Event Links

* [VCONIC TADHack Announcement](https://blog.tadhack.com/2025/12/19/vconic-tadhack/) — Original event page with challenge details
* [VCONIC TADHack — The Hacks](https://blog.tadhack.com/2026/03/08/vconic-tadhack-the-hacks/) — Blog post covering all submissions
* [TADHack](https://tadhack.com) — Telecom Application Developer Hackathon


# Thinking in vCon

Thinking in vCon is a short-form video series arguing that conversations should be treated as governed, portable artifacts. This section collects the full episodes, source material, and clips.

**Thinking in vCon** is a video series about one idea: a conversation is a thing you can hold. Not a recording locked in a vendor's platform, not a row in a CRM, but a signed, portable object with its parties, its dialog, its consent, and its analysis all in one place.

Each episode takes a single question — privacy, provenance, the shape of a conversation, identity — and works it from first principles up to something you can act on. The standard is vCon. The point is what it lets you build.

{% hint style="info" %}
New here? Start with [A vCon Primer](/vcons/a-vcon-primer) for the written version of the argument, then come back and watch.
{% endhint %}

## Start with the keynote

{% embed url="<https://www.youtube.com/watch?v=YDh0phRx0bM>" %}
Thinking in vCon — the through-line of the series, in one sitting. 17:55.
{% endembed %}

Why conversations have never had a standard file format the way documents, images, and spreadsheets do, and why that gap matters now. Introduces vCon as a single trustworthy object carrying a conversation's recording, transcript, metadata, provenance, and rights.

## Episodes

{% content-ref url="/pages/OGzjYEOSm0yXr8hKLn6V" %}
[The Keynote](/thinking-in-vcon/the-keynote)
{% endcontent-ref %}

{% content-ref url="/pages/22LtvBLurByEY1JtUSJ0" %}
[Privacy](/thinking-in-vcon/thinking-in-vcon-privacy)
{% endcontent-ref %}

{% content-ref url="/pages/3W1a136YQJwLfLNsDalz" %}
[The Conversational Graph](/thinking-in-vcon/the-conversational-graph)
{% endcontent-ref %}

## Clips

Short cuts live on the [conserver channel](https://www.youtube.com/@conserverio) and are cross-posted to LinkedIn and Facebook. They stand alone: one idea, a couple of minutes.

* [Privacy (short)](https://www.youtube.com/watch?v=XAAaSRiyBe4) — 4:52 — consent that travels with the recording

Everything in one place, in series order: the [Thinking in vCon playlist](https://www.youtube.com/playlist?list=PLaxNQ-GrpFLc) on YouTube.

## Related

* [Talks, Articles & Press](/talks-articles-press) — conference keynotes, IETF sessions, and podcasts
* [Why Conversations Need a File](/vcons/why-vcons) — the core written argument


# The Keynote

The through-line of the series in one sitting: why conversations have never had a standard file format the way documents, images, and spreadsheets do, and what becomes possible once they do.

{% embed url="<https://www.youtube.com/watch?v=YDh0phRx0bM>" %}

Thinking in vCon — the keynote, in one sitting. 17:55.

### The argument

Documents have files. Images have files. Spreadsheets have files. Conversations never got one. A call lives as a recording in one vendor's storage, a transcript in a second system, a summary in a third, and a few notes typed into a CRM by whoever happened to be on the line. Nothing binds those pieces together and nothing certifies that they belong to each other.

The talk argues that this is not a small gap in tooling. It is the reason conversations are hard to govern, hard to audit, and hard to move between systems. Every integration re-derives what the conversation was, and every re-derivation is another chance for the record to drift from what actually happened.

vCon closes that gap by giving a conversation what every other kind of content already has: a defined container. Parties, dialog, recordings, transcripts, analysis, and rights sit together in one signed object that can be handed from system to system without losing its meaning or its provenance.

### Why the gap matters now

AI raises the stakes. Models are trained on conversations, agents act on them, and analytics pipelines make decisions from them. When the underlying record has no fixed shape and carries no permissions of its own, basic questions have no reliable answer: who agreed to what, which transcript a summary came from, whether a recording has been altered since it was captured.

A conversation with a standard format is a conversation you can reason about. Consent becomes checkable rather than assumed. Provenance becomes verifiable rather than asserted. And the conversation becomes portable — something the parties to it can actually hold, instead of a byproduct locked inside whatever platform happened to capture it.

### What the talk covers

* Why documents, images, and spreadsheets all got file formats while conversations never did
* What a conversation actually contains once you write it down honestly: parties, dialog, recordings, transcripts, analysis, and rights
* How signing and provenance turn a scattered pile of artifacts into a record a recipient can check
* What changes for governance, compliance, portability, and AI once the container is standard

### Watch and share

* [Thinking in vCon — full talk on YouTube](https://www.youtube.com/watch?v=YDh0phRx0bM)
* [Thinking in vCon playlist on YouTube](https://www.youtube.com/playlist?list=PLaxNQ-GrpFLc)
* [conserver channel on YouTube](https://www.youtube.com/@conserverio)

### Go deeper

* [Privacy](/thinking-in-vcon/thinking-in-vcon-privacy)
* [The Conversational Graph](/thinking-in-vcon/the-conversational-graph)
* [Why Conversations Need a File](/vcons/why-vcons)
* [A vCon Primer](/vcons/a-vcon-primer)


# Privacy

Existing privacy law already applies to conversations. This episode shows how a vCon carries consent, purpose limits, minimization, and redaction inside the conversation itself.

{% embed url="<https://www.youtube.com/watch?v=dw4mMcp7jD0>" %}
Thinking in vCon: Privacy — 15:16, a companion to the main talk.
{% endembed %}

## The argument

One example carries the whole talk: a recorded customer support call.

GDPR, CCPA, and comparable rights elsewhere in the world already apply to that call. The person on the other end of it can ask what was collected, object to a new use, or ask to be forgotten. None of that is new law and none of it is seriously disputed. What is missing is machinery. In a typical stack the recording lands in one system, the transcript in another, the summary in a third, and the consent, if it was captured at all, sits in a checkbox somewhere in a fourth. Rights that exist on paper become expensive or impossible to honor in practice.

The claim this episode makes is that privacy here is a container problem before it is a policy problem. When consent, purpose limitation, data minimization, and redaction travel inside the conversation object, permissions can be verified by whoever receives it rather than assumed on the strength of a contract. A downstream tool does not have to trust that the right thing happened upstream. It can check.

## What that looks like in a vCon

Consent travels with the dialog, recorded per purpose in the object itself rather than in a separate system that the recording will eventually be separated from. Purpose limits are explicit, so "recorded for quality assurance" and "usable as model training data" are distinguishable permissions, and an object can carry one without the other. Minimization and redaction become visible, verifiable states of the record instead of undocumented side effects of some pipeline. And because the object is signed and carries a tamper-evident history, a recipient can tell whether the consent they are reading is the consent that was actually given.

The practical consequence is that answering a right-to-know or right-to-erasure request stops being a project and becomes a query against objects that already carry the answer.

## Watch the short version

Five minutes instead of fifteen: [Privacy (short)](https://www.youtube.com/watch?v=XAAaSRiyBe4) makes the same argument without the worked example.

## Go deeper

* [Privacy Primer](/vcons/privacy-primer) — a short orientation to data and communications privacy for people building or reviewing vCon-based systems
* [Lawful Basis](/extensions/lawful-basis) — the extension that records legal grounds for processing, with cryptographic proof and per-purpose consent
* [Privacy-First Conversation Management](/deep-dives/privacy-first-conversation-management) — the technical whitepaper behind this episode
* [Lifecycle (SCITT)](/extensions/lifecycle) — append-only audit ledger for consent and deletion events
* [vCons and Increasing End User Agency](/deep-dives/vcons-and-increasing-end-user-agency) — the same argument from the data subject's side


# The Conversational Graph

A business process is really a series of conversations. This episode introduces the conversational graph, linking vCons by shared parties and causality so open loops become visible before they become

{% embed url="<https://www.youtube.com/watch?v=Wp-YRAlYf_8>" %}
Thinking in vCon: The Conversational Graph — 22:09, one case followed all the way through.
{% endembed %}

## The argument

The running example is a single hospital discharge, followed end to end.

Seen from a database, a discharge is a status field. It moves from one value to the next and eventually reads complete. Seen from the outside, the discharge is a chain of conversations: the physician and the patient, the nurse and the family, the pharmacy, the follow-up call that was promised and the follow-up call that actually happened. The distinction the episode turns on is that status in a database is not the same as state in a conversation thread. A record can say discharged while the conversation still contains an unresolved promise.

That gap is where expensive failures live. A commitment made out loud and never closed does not surface as a red field on a dashboard, because no dashboard was watching the conversation. The process was real. It simply never existed in a form that anything could inspect.

## The graph

The proposal is to stop treating each conversation as an isolated artifact. A vCon already carries its parties and can reference other vCons, so individual conversations can be linked into a graph: by shared participants, by subject, and by causality, meaning this call happened because that one did.

Once the graph exists, questions that were previously unanswerable become queries. Which threads have an open loop? Which promise was made and never closed? Where did a thread jump systems and lose its state? Those are things a person can act on, and they are also exactly what an AI agent needs in order to be useful rather than merely fluent. An agent that can see the thread can pick it up, and an agent that cannot will confidently start over.

The line the talk closes on is the whole idea in five words: the conversation is the record.

## Go deeper

* [Concepts](/vcons/concepts) — parties, dialog, analysis, and how vCons reference one another
* [Day In the Life of a vCon](/conserver/day-in-the-life-of-a-vcon) — the end-to-end flow through the Conserver
* [The Journey of a vCon](/vcons/vcon-conveyor-infographic) — the same path, as a diagram
* [MCP Server](/mcp-server) — how AI assistants query vCon data directly, which is what makes graph questions answerable in practice
* [Why Conversations Need a File](/vcons/why-vcons) — the written form of the underlying argument


# Talks, Articles & Press

The public record of vCon — talks, podcasts, articles, and IETF sessions where vCon has been discussed in public venues.

This section is the curated public record. Anything we link here is on someone else's site, under their byline, in their own words. It is the place to go when you want to see what is actually being said about vCon in public, by whom, and where.

If you arrived here from the home page looking for evidence that vCon is more than a single vendor's idea, the layers below are where that evidence lives.

## Where to look, by what you want to verify

The IETF process is itself the strongest evidence that vCon is being built in the open. Specific public statements from carriers, platforms, regulators, and civil society organizations show up across the subsections.

* **The standards process.** The [IETF VCON working group datatracker page](https://datatracker.ietf.org/wg/vcon/) is the primary source. Charter, drafts, mailing list archives, and meeting minutes are all public and permanent. See also [IETF Sessions](/talks-articles-press/ietf-sessions) below.
* **Industry adoption and analyst framing.** [Conference Keynotes](/talks-articles-press/conference-keynotes) and [Articles & Press](/talks-articles-press/articles-and-press) collect talks and longform writing from TADSummit, TADHack, Telecom Reseller, Medium, and analyst blogs.
* **Audio commentary.** [Podcasts](/talks-articles-press/podcasts), anchored by Jeff Pulver's ongoing Substack series, is the rolling commentary track on vCon's adoption.
* **Related standards work.** SCITT is the IETF effort vCon pairs with for lifecycle integrity. Talks linking the two appear in [Conference Keynotes](/talks-articles-press/conference-keynotes) and [Podcasts](/talks-articles-press/podcasts); the deep dive lives at [SCITT and vCon](/deep-dives/scitt-supply-chain-integrity-transparency-and-trust).

## How to use this section

* **Get oriented quickly.** Pick one talk from [Conference Keynotes](/talks-articles-press/conference-keynotes) and one article from [Articles & Press](/talks-articles-press/articles-and-press). Forty minutes will give you a working mental model.
* **Cite vCon.** When you need a durable link for a deck, blog post, or RFP, prefer pinned items at the top of each subsection.
* **Track the working group.** [IETF Sessions](/talks-articles-press/ietf-sessions) is the closest thing to a primary source for where the spec is going next.
* **Find a quote.** Each subsection is reverse chronological. The most recent public statements are at the top.

## Subsections

* [IETF Sessions](/talks-articles-press/ietf-sessions) — every IETF VCON working group session with recording and minutes
* [Conference Keynotes](/talks-articles-press/conference-keynotes) — TADSummit, TADHack, Generative AI Expo, and other event talks
* [Podcasts](/talks-articles-press/podcasts) — audio interviews, including Jeff Pulver's ongoing Substack series
* [Articles & Press](/talks-articles-press/articles-and-press) — longform writing, press coverage, and analyst commentary

## A note on attribution

We link to public material by the speakers and authors who produced it. We do not characterize positions of organizations beyond what those organizations have said themselves in public, on the record. If you want to know what a given carrier, platform, regulator, or civil society organization thinks about vCon, follow the links to their own words.

## Submitting links

Recorded a talk, written an article, or run a podcast episode about vCon? The lightest path is a pull request against `talks-articles-press/` in [vcon-dev/vcon-docs](https://github.com/vcon-dev/vcon-docs). Keep entries to one line plus a short takeaway, reverse chronological within each subsection.


# IETF Sessions

IETF VCON working-group session recordings and minutes, in reverse chronological order.

The IETF VCON working group meets at each IETF plenary plus occasional interims. Sessions are recorded; minutes, agendas, and slides are posted on the [IETF datatracker](https://datatracker.ietf.org/wg/vcon/meetings/) and stay there permanently.

If you want the canonical record of where the spec is — what's settled, what's contested, what's about to merge — these recordings are it.

## Sessions

### IETF interim — 2026-01-27

* **Video:** [YouTube](https://www.youtube.com/watch?v=jSqQewWSNJI)
* An interim working session between IETF 121 and IETF 122. Used to push extension drafts forward outside the plenary cadence.

### IETF 122, Bangkok — 2025-03-21

* **Video:** [YouTube](https://www.youtube.com/watch?v=gmNBSwnVNac)
* Continued WG progress on the core draft and extensions.

### IETF 121, Dublin — 2024-11-07

* **Video:** [YouTube](https://www.youtube.com/watch?v=DfNKgMvbn1o)
* Core draft consolidation; extension drafts beginning to land.

### IETF 120, Vancouver — 2024-07-25

* **Video:** [YouTube](https://www.youtube.com/watch?v=7ncPbTX3x-0)
* First full plenary after the working group's formal chartering.

### IETF 119, Brisbane — 2024-03-21

* **Video:** [YouTube](https://www.youtube.com/watch?v=t8f3-V_hpD0)
* **Datatracker:** [session materials](https://datatracker.ietf.org/meeting/119/session/vcon)
* Post-chartering session focused on the syntax and lifecycle drafts.

### IETF 118, Prague — 2023-11-08

* **Datatracker minutes:** [minutes-118-vcon-202311081330-00](https://datatracker.ietf.org/meeting/118/materials/minutes-118-vcon-202311081330-00)
* WG-forming discussion; charter and scope.

### IETF 116, Yokohama — 2023-03-29

* **Video:** [YouTube](https://www.youtube.com/watch?v=EF2OMbo6Qj4)
* **Datatracker minutes:** [minutes-116-vcon-202303290030-00](https://datatracker.ietf.org/meeting/116/materials/minutes-116-vcon-202303290030-00)
* First formal IETF session for vCon, including the Birds of a Feather discussion.

## Other IETF materials

* [VCON working group page](https://datatracker.ietf.org/wg/vcon/) — charter, current draft list, mailing list
* [Active drafts](https://datatracker.ietf.org/wg/vcon/documents/) — the full document list
* [Meetings index](https://datatracker.ietf.org/wg/vcon/meetings/) — every past and upcoming session

If you're looking for non-IETF talks (TADSummit, conferences, etc.), see [Conference Keynotes](/talks-articles-press/conference-keynotes).


# Conference Keynotes

TADSummit, TADHack, and other conference talks introducing or advancing vCon.

vCon got its first sustained public exposure at TADSummit (Telecom Application Developer Summit) and TADHack (the corresponding hackathon). Those venues remain the place where the working group, the vCon Foundation, and the implementer community meet face-to-face. The talks below are organized reverse-chronologically.

## 2026

### VCONIC TADHack 2026

* **TADHack blog:** [VCONIC TADHack March 7-8](https://blog.tadhack.com/2025/12/19/vconic-tadhack/)
* The 2026 vCon-focused TADHack. The hackathon review with all 16 submissions and winners is also on this site at [VCONIC TADHack 2026: Hackathon Review](/helps-and-hacks/vconic-tadhack-2026-hackathon-review).

## 2025

### "vCon Progress Report" — TADSummit 2025

* **TADSummit blog:** [vCon Progress Report](https://blog.tadsummit.com/2025/08/20/vcon-progress-report/)
* Status update covering the 211-contact-center pilot, the Strolid / Frontline alliance, and upcoming features (selective disclosure, transcription schema, consent).

### Matthew Smith — "vCon + UNS for Manufacturing / Process Industries"

* **TADSummit blog:** [End of Year Special](https://blog.tadsummit.com/2025/12/17/matthew-smith-vcon-and-uns/)
* vCon applied outside customer-experience contexts — combining vCon with Unified Namespace (UNS) patterns for manufacturing and process industries.

### Jeff Pulver — Generative AI Expo 2025

* **Video:** [YouTube](https://www.youtube.com/watch?v=DjByCcpe-ws)
* Jeff Pulver, CEO of the vCon Foundation, frames vCon as the missing data layer for the AI era. Good elevator pitch for non-telecom audiences.

### Dan Petrie — "The Geeky Side of vCon" — TADSummit Online, April 2025

* **TADSummit blog:** [Dan Petrie, The Geeky Side of vCon](https://blog.tadsummit.com/2025/05/02/dan-petrie-the-geeky-side-of-vcon/)
* Implementation-focused walkthrough of the py-vcon and py-vcon-server reference implementations.

### "Voice AI Space is born" — Thibault Mardinli (T-Bot) — TADSummit Online 2025

* **TADSummit blog:** [Voice AI Space is born](https://blog.tadsummit.com/2025/07/23/t-bot/)
* Adjacent context: how vCon fits into the broader voice-AI landscape.

## 2024

### Thomas Howe — "The Rise and Rise of vCon" — TADSummit 2024

* **TADSummit blog with video:** [The Rise and Rise of vCon](https://blog.tadsummit.com/2024/10/29/the-rise-and-rise-of-vcon/)
* The TADSummit 2024 sponsor keynote, covering the trajectory from the original whitepaper to the IETF working group and the explosion of LLM use cases driving adoption.

### Steve Lasker — "SCITT and vCon AI Governance for Conversations, at Scale" — TADSummit 2024

* Same blog post as above.
* The complementary keynote: why SCITT (Supply Chain Integrity, Transparency, and Trust) belongs alongside vCon for Right to Know and Right to Erasure compliance. Steve also appeared on the TADSummit Innovators podcast — see [Podcasts](/talks-articles-press/podcasts).

### Thomas Howe — "Grooving on vCons" — TADSummit Keynote

* **Video:** [YouTube](https://www.youtube.com/watch?v=TVq7Y1SoGo4)
* An earlier-in-the-year Thomas Howe keynote.

## 2022–2023

### vCon Workshop — 2022-06-09

* **Video:** [YouTube](https://www.youtube.com/watch?v=oMZLqOfW6sw) · **Summary:** [Alan Quayle's writeup](https://alanquayle.com/2022/06/09/vcon-workshop-summary/)
* The pre-IETF community workshop where the case for an open standard was made publicly.

### "vCon presentation at TADSummit, Portugal" — November 2022

* Referenced in [vCons → More Information](/vcons/more-information).

### "vCon presentation at IETF 115, London" — November 2022

* Referenced in [vCons → More Information](/vcons/more-information).

### "vCon presentation at IIT" — Chicago, October 2022

* Referenced in [vCons → More Information](/vcons/more-information).

## Explainers and overviews

* ["Unlocking Customer Conversations: New IETF Standard for ..."](https://www.youtube.com/watch?v=d0X-5WlTfhU) — vCon overview video.
* ["The Future of Conversations: Why #vCon Is Emerging as a New Standard"](https://www.youtube.com/watch?v=L8P7RdO0uPA) — adoption-focused framing.

## See also

* [IETF Sessions](/talks-articles-press/ietf-sessions) for the working-group sessions themselves
* [Podcasts](/talks-articles-press/podcasts) for audio-only interviews
* [Articles & Press](/talks-articles-press/articles-and-press) for written coverage


# Podcasts

Podcasts and audio interviews about vCon, anchored by Jeff Pulver's ongoing Substack series.

If you'd rather listen than read, this is the section to bookmark. Jeff Pulver — vCon Foundation CEO and the person who coined "Voice on the Net" in 1995 — runs an ongoing podcast and Substack series that's effectively the rolling commentary track on vCon's adoption.

## Pin this

### The Pulver vCon Report (Substack)

* **Substack:** [thejeffpulver.substack.com](https://thejeffpulver.substack.com/)
* Jeff Pulver's ongoing series on vCon adoption. This is the single best subscription if you want to track the industry-side momentum.

## 2026

### Jeff Pulver — "vCon Foundation and CarrierX: The Future of Programmable Conversations"

* **Telecom Reseller, 2026-03-17:** [podcast](https://telecomreseller.com/2026/03/17/vcon-foundation-and-carrierx-jeff-pulver-and-david-erickson-discuss-the-future-of-programmable-conversations-podcast/)
* Jeff Pulver and CarrierX's David Erickson on the practical side of programmable conversations.

### Jeff Pulver — "vCon Foundation: Structuring Conversations for the AI Era"

* **Telecom Reseller, 2026-03-12:** [podcast](https://telecomreseller.com/2026/03/12/vcon-foundation-jeff-pulver-on-structuring-conversations-for-the-ai-era-podcast/)
* The 2026 elevator-pitch episode: why structured conversation data is the missing layer in enterprise AI.

## 2025

### Jeff Pulver — "The vCon Revolution: The File Format Transforming Business Conversations"

* **Telecom Reseller, 2025-06-02:** [podcast](https://telecomreseller.com/2025/06/02/the-vcon-revolution-jeff-pulver-on-the-file-format-transforming-business-conversations-podcast/)
* Framing vCon as the equivalent of PDF for conversations.

### "vCon: The Next Evolution in Communication"

* **Telecom Reseller, 2025-02-18:** [podcast](https://telecomreseller.com/2025/02/18/vcon-the-next-evolution-in-communication-podcast/)

## 2024

### Steve Lasker (DataTrails) — TADSummit Innovators Ep 85

* **TADSummit blog:** [Steve Lasker podcast](https://blog.tadsummit.com/2024/08/20/steve-lasker/)
* Steve Lasker on SCITT and the supply-chain-integrity story for conversation data.

## Earlier appearances

### Jeff Pulver — "If you're looking for the future today, it's all about the vCon"

* **Cloud Communications:** [podcast](https://www.cloudcommunications.com/podcasts/jeff-pulver-if-youre-looking-for-the-future-today-its-all-about-the-vcon-podcast)

### Jeff Pulver — "vCon and the Birth of the AI Communications Industry"

* **Cloud Communications:** [podcast](https://www.cloudcommunications.com/podcasts/jeff-pulver-on-vcon-and-the-birth-of-the-ai-communications-industry-podcast)

### Jeff Pulver — "VCon and Beyond: Revolutionizing Business Communication"

* **KP Global Logistics, Ep 23:** [podcast](https://kpglc.com/ep-23-vcon-and-beyond-jeff-pulver-on-revolutionizing-business-communication/)

## See also

* [Conference Keynotes](/talks-articles-press/conference-keynotes) — many of these podcast guests also have video keynote appearances
* [Articles & Press](/talks-articles-press/articles-and-press) — the written counterpart to this list


# Articles & Press

Longform writing, press coverage, and LinkedIn commentary about vCon.

Written coverage of vCon — analyst posts, vendor blogs, dev community writeups, and ongoing commentary on LinkedIn. Reverse-chronological.

## 2026

### Thomas Wieberneit — "The vCon Reality Check: Moving Beyond Generative Hype to Actual Conversational Architecture"

* **Medium, April 2026:** [The vCon Reality Check](https://aheadcrm.medium.com/the-vcon-reality-check-moving-beyond-generative-hype-to-actual-conversational-architecture-41197017fb9b)
* **Mirror:** [aheadcrm.co.nz](http://blog.aheadcrm.co.nz/2026/04/the-vcon-reality-check-moving-beyond.html)
* A CRM-industry analyst's take on why vCon matters in the gap between generative-AI hype and durable conversational architecture.

### "From Voice to Data: How vCons Are Changing Business Communication"

* **Telecom Reseller, 2026-02-17:** [article](https://telecomreseller.com/2026/02/17/vcons-changing-business-communication/)
* Adoption-side framing: what changes for an enterprise when conversation data becomes a first-class asset.

## 2025 and earlier

### "Next Stop — Fall '25 vCon" — J Arnold & Associates

* **JAA blog:** [Next Stop — Fall '25 vCon](https://www.jarnoldassociates.com/blog/search/2025/12/1/next-stop-fall-25-vcon)
* Industry-analyst writeup connecting vCon to broader UC and CX trends.

### "What Are vCons? The New AI Standard for Business Conversations"

* **Telecloud blog:** [What are vCons?](https://telecloud.net/blog/what-are-vcons)
* Plain-language introduction aimed at IT buyers.

### "What is vCon and Why Should We Care?"

* **dev.to community post:** [dev.to](https://dev.to/yoditdevn8n/what-is-vcon-and-why-should-we-care-8l2)
* Developer-community introduction with the basic shape of a vCon JSON object.

## Ongoing voices to follow

These authors post about vCon regularly. Subscribe / follow rather than treating any single post as canonical.

* **Jeff Pulver** — vCon Foundation CEO. [LinkedIn](https://www.linkedin.com/in/jpulver) · [Substack](https://thejeffpulver.substack.com/)
* **Ken Herron (PRV8)** — writes about vCons in the context of "intelligent agreement management." [LinkedIn](https://www.linkedin.com/in/kenherron)
* **Thomas Howe (Strolid)** — recurring TADSummit author. [TADSummit author archive](https://blog.tadsummit.com/tag/thomas-howe/)
* **Alan Quayle (TADSummit / TADHack organizer)** — covers vCon community events. [TADSummit blog](https://blog.tadsummit.com/)

## See also

* [IETF Sessions](/talks-articles-press/ietf-sessions) for the primary-source spec material
* [Conference Keynotes](/talks-articles-press/conference-keynotes) for the spoken version of much of this material
* [Podcasts](/talks-articles-press/podcasts) for audio interviews


# Use Cases / Studies


# Overview

Please note most of these use cases are voice-centric, we see voice conversations as an initial opportunity. However, vCon works for conversations across any communications media, e.g. email, video, SMS, web chat, social, chat in IP messaging like WhatsApp, etc.

Think of vCon as ‘robot food’, enabling conversation data to be presented in a common format and more easily cleaned for training of machine learning. ASR and conversation AI solutions do not meet the needs of some businesses with respect to accuracy, vCon will help our industry close the gap with respect to the hype.

The performance of ASR varies greatly depending on the application, quality of the recording, and engine/training. ASR continues to improve, some of these applications could be a stretch for a legacy call center, however, for some scenarios they are attainable today.

\\

\\


# Service Provider Migration

When companies merge or are acquired, they consolide suppliers to gain economies of scale. However, migrating data from an acquired company's UCaaS / CCaaS onto the acquiring company’s platform can be problematic.

Often the acquired company’s customer data and historical conversations are lost, or an expensive data migration project is undertaken. Businesses discover their customer conversation data is not really their data.

With vCon such a migration becomes an export / import of the historical conversations.

This could also be applied to a personal use case where an individual wants to download all their communications from a social network.\\

###

\\


# Speech Recognition Test Set

ASR (Automatic Speech Recognition) for many languages is still in development. That is why [Le Voice Lab](http://www.levoicelab.org/) exists, a French association that brings together various institutional players (universities, research laboratories, etc.) and private companies whose common interest is to build an independent ecosystem and common standards to enable France and Europe to remain competitive in the global voice market. It’s not just Europe, around the world there is a substantial quality gap compared to the English speaking world for ASR.

But which ASR works best for business’s customers, and is it good enough for the intended applications? Enterprises are now equipped to easily compare the different ASR engines from global and regional providers. vCon enables a single source of test data to accurately and repeatedly measure speech recognition performance across 100s or 1000s of samples, to gather statistically meaningful performance data.

As it’s a computer file format the vCons can be processed through an Excel sheet or business intelligence application. Businesses can make quantified decisions based on their specific situation. The ‘Rolls Royce’ ASR may be the best with an accuracy range of 94-96%, but the ‘Honda Civic’ ASR is good enough at 92-94% for the intended application. The vCons from the different ASRs can be processed through the business application, and the business results compared, not just word error rates.

A business may receive 95% of their voice calls from 3G mobile networks with a range of dialects. They can build their own vCon test set, run them through the ASRs, and with nothing more than Excel compare the results. It could be that ASR in general is not currently up to the task, this will change given the continued performance improvements, but better to make an informed decision and revisit; than assume ASR is inadequate until the gap with competitors becomes clear and leaves your business struggling to catch up.

vCons democratize an opaque industry, which relies on fear, uncertainty, and doubt to stop the buyer making a quantified decision that is best for their situation.


# AI Integration

### Simplifying Working with Bots

Many programmable communication developers share the same experiences when they begin working on voice bots and transcriptions. They begin by capturing the conversation data with MS Word or Excel files. Often a combination of both to better represent conversations and enable the transcriptions to be processed / cleaned-up.

Later they build a JSON format that includes conversation data, transcriptions, context, etc. Soon they begin writing macros / functions to automate conversation data processing and analysis. With that comes maintaining both the JSON format and the macros / functions, and a realization they do not have the time to build everything themselves. vCon solves this common problem and enables macros / functions to be written to this common format and contributed to the open source project.


# PII Compliance

The problems created by not maintaining PII compliance is more than negative publicity. The fines have been massive, for example the FTC (Federal Trade Commission) fined Facebook $5 billion in 2012, Equifax was fined at least $575 million in 2017 and 2019, and British Airways was fined $230 million in 2018. There’s a long list of regulations including: [GDPR](https://gdpr-info.eu/), [HIPAA](https://www.hhs.gov/hipaa/index.html), [CCPA](https://oag.ca.gov/privacy/ccpa), [PCI DSS](https://www.pcisecuritystandards.org/).

It’s not just the big brands that get caught, a PII audit can happen to any business. They are not scheduled, and can be triggered by a complaint that can come from an unhappy ex-customer or even a competitor. There are fines and possible incarceration for not reporting PII breaches as well.

PII can be a person's name, in combination with any of the following information:

• Mother's maiden name

• Driver's license number

• Bank account information

• Credit card information

• Relatives' names

• Home Postal address

• Personal E-mail address

• Home or cellular telephone number

• Personal characteristics / biometric data

• Social Security Number (SSN)

• Date or place of birth

• An individual's title

• Work telephone number

• Official work location/address

• Work email address

• Asset information, such as a car’s Vehicle Identification Number (VIN) or title number. Even MAC (Machine Address Code) or IMSI (International Mobile Subscriber Identity)

• Even a vCon record given voice finger-printing

• Other information that would make the individual's personal identity easily traceable

\
Across all the conversations a business has, both internally and externally, which are often recorded for training purposes or by company policy, there is a significant repository of customer data missed by most PII tools because they tend to focus on text based files.

Existing PII tools search the company’s storage for files containing customer data, e.g. usernames and password. vCon makes conversation data more easily available to the existing PII audit tools, so your business is better protected. Note, some PII audits are now warning of the emerging need of including audiovisual data for PII Compliance.

Through the open standard vCon the existing PII Compliance tools can be extended to files containing audio visual conversations that are often overlooked. A business is not trapped into multiple specialized PII audits across its different communication silos.

###


# Customer Experience Improvement

Many of us have seen the data, 58% of customers say that customer service is a very important factor that affects their choice of a brand, source [Microsoft’s State of Global Customer Service Report](https://info.microsoft.com/rs/157-GQE-382/images/2018StateofGlobalCustomerServiceReport.pdf).

Call recording has been in place for decades, yet why have contact centers remained the same for decades? “Please listen carefully as our menus have changed.” No they have not, the menu has been the same for the past two decades! Why hasn’t call recording led to a virtuous circle of improvement?

Here are some of the claims made by call recording vendors:

* Call center managers can review the calls to get a better understanding of how agents handle customer conversations.
* Learn whether support representatives are following the protocols.
* Figure out specific customer support aspects that can be improved.
* With call recordings, call center managers can save time as they do not have to listen to each call in real time.
* Identify the gaps in terms of training and best practices whether they are followed or not.
* Learn first-hand customer feedback or issues and train your team to handle them better.
* Listening to call recordings one on one with employees will empower managers to identify the skills that need to improve and work upon.
* Based on the recorded audios, call center managers can prepare presentations to teach about the proper way of making and taking calls.

It’s rather manual and lacks quantified data and analysis, this reflects the historical limits of ASR. The call center manager will call a meeting based on their analysis, or bring in a consulting firm that analyzed the data, and based on ‘industry best practices’ make improvement recommendations. The training to implement the improvements is given, and when the call center manager evaluates the results, not much has changed. It’s been going on for decades. Often the sample size is too small given the highly manual approach, and sometimes changes to the process have unintended consequences blurring customer feedback.

vCon enables tens of thousands, even hundreds of thousands of calls to be analyzed by a broad ecosystem of innovative companies. Changes in the process can be analyzed by A/B analysis on possible ways to diffuse customer frustration about an overage charge. The vCons for the calls can be analyzed to make sure the A/B script is being followed, sentiment analysis, and customer feedback during / post call can be combined to produce quantified results.

Often great customer service is exemplified by employees going above and beyond what is economic for the business. While some companies like Zappos simply ensure a human approach to the customer. This may not work for all brands and situations. But being able to test and quantify the results over thousands or tens of thousands of conversations gives the business, and most importantly the agents, confidence in the process change. This is an example of a complex human factor problem that will require new approaches to conversation intelligence, enabled through the ecosystem made possible through vCon.


# Sharing Conversations with Third Parties

Working with contractors has its ups and downs. During meetings note taking can miss agreements on deliverables and their timing, simply not happen, or not be fully understood by all parties. Everyone leaves with a slightly different perception of the action. Missed deliverables become finger pointing exercises that do little to help the working relationship.

Most conference calls are recorded, yet are only used for those that missed the meeting and listened to at 1.5-2 X speed. A call recording is generally not used by the people who attended, because they already attended. However, the content in that call is invaluable. It’s not that we forget what was agreed on the call, it’s that we get busy, when the actions / deliverables are in black and white on our to-do list, we are reminded every day.

Imagine after the meeting a vCon is produced. The vCon is processed by a Meeting Minder - Construction Edition app, which sends a summary to all participants on the conference call that includes the actions and deliverables. The app is trained to the specific vocabulary and processes used in an industry vertical, construction.

Each action/deliverable includes a reference to the section in the conversation. For example, “Action: Plan for Floor 5 will be sent by Joe to Mary at Company X by end of day June 7th, spoken by Anne,\<link to specific point in the conversation>”. There is no finger pointing, immediately after the meeting the summary was sent to everyone within the meeting minutes, and transferred onto each participant’s to-do list. If there was an error, it would have been picked up then.

Practically, ASR on a conference call is far from perfect, especially if someone calls in using a mobile phone. But the actions and summary will be checked by some of the participants to make sure everything is captured adequately.

vCon enables a range of value added services to be created around this open standard, an ecosystem of innovators taking conversation intelligence beyond transcription. No longer does the Meeting Minder app provider need to waste time and expense joining every conference call / collaboration platform’s partner ecosystem. With vCon they can implement one format and cover all communication platforms that use vCon. Instead focusing on building best in class industry vertical solutions.

The integration headache for the Meeting Minder app provider results in less competition. They will focus on the top 5 conference / collaboration platforms. The open source conferencing platforms, the regional platforms, and smaller providers are skipped. vCon helps maintain healthy competition and the reach of vCon apps to extend across all programmable communication platforms.


# Verbal Agreements - Converted to Writing

As a general rule, the law in the UK considers verbal contracts to be as legally binding as written ones, and therefore they do hold up in court. But this is an exception compared to the rest of the world. Generally, for established business relationships it makes doing business for small projects easy. Often just an email that summarizes the offer, a consideration, and acceptance is enough, the project gets delivered, and everyone is happy.

However, when legal gets wind of such dealings the $5k project that was supposed to be delivered next week doesn’t happen as the ‘standard’ T\&Cs require contractor insurance with $X million+ liability coverage that takes more than a week to arrange. Plus all the time and effort in word-smithing far exceeds the project size.

Using a vCon on the call, chat, or email thread to discuss and agree the project, and using an app such as Meeting Minder - Contract Edition, a verbal or text agreement can easily be converted into a written one, with lightweight T\&Cs begrudgingly accepted by legal for small projects. Now legal are not unhappy and the business can focus on operations, not document editing, with just a call creating a lightweight contract for review and signing.

Note ASR is not necessarily required in this use case. The conversation could be transcribed by a person, in some medical use cases such manual transcription continues to be required. A contract could have a value to make manual transcription economic or potentially preferred.


# Authenticating and Certifying Conversations

A carrier / telco knows both participants of a conversation were in Germany as the call was made over their network by devices physically connected to their network. Certifying the conversation was made in Germany places additional restrictions on how the data can be used to protect both parties. Personal data is constitutionally protected in Germany. This means individuals have the power to decide when and to what extent personal information is published.

To comply with the Dodd-Frank act's call recording regulations, companies must keep all communication records made through the telephone, voicemail, email, and others, and these records must be uniformly time stamped. A carrier could provide this as a service, recording all conversations made by a business’s mobile phones, and sharing with the business as a vCon. The conversation could additionally be processed by the carrier or CPaaS/UCaas/CCaaS to confirm no PII was shared in this conversation, or certify no keywords specified by the business were found. A business would only want to pay once for that service and attach the certification to the vCon.

Phone numbers can be spoofed. But within a carrier’s network they have knowledge of the device’s identity, its location, its phone number, its SIM (Subscriber Identity Module) card identity, the owner’s account, how long they have had that number or device, etc. There is no other agency that can confirm the identities of the parties on a call with such confidence. A carrier could certify the identities on a vCon.


# Insights from Customer Conversations

The customer is constantly providing information to your business: on which competitors they compare you to, what features they value in your product, what problems they have with your product, how they use your product, what features they do not use, how they like to be billed, how they view the pricing, why they moved from / to a competitor, how they use your product, how they mash-up your product and others to meet their needs, how they work around gaps in your product, etc.

Every conversation (voice, email, video, SMS, web chat, IP messaging, social, etc.) across sales, support, customer care, etc. is capturing this data. Extracting this data is not that easy, it's still a work in progress. vCon enables an ecosystem of analytics and data mining companies that can extract such information across all interactions. Determining that a customer is talking about a complementary product not a competitive product is not easy. But insights continue to improve.

vCon enables all customer data to be used to generate insights from the conversations stored but not fully mined. Surveys are often performed to capture minable data, often the surveys have leading questions or the survey is filled as fast as humanly possible. This does not provide accurate information, and why most surveys seem to enforce a marketing view not necessarily a customer view. The conversations taking place with sales, support, customer care are the most accurate customer data a business has access to, and remain relatively poorly mined.


