CONCORDIA is a real-time, conversational AI mediation system built on Google's Agent Development Kit (ADK) and Gemini Live API. It listens to each party in a dispute, silently builds a structured conflict knowledge graph (the TACITUS structure), identifies common ground, matches applicable resolution theories, and generates concrete common ground proposals — all through natural voice and text conversation.
Party 1 speaks ──► CONCORDIA listens ──► Extracts: actors, claims, interests,
Party 2 speaks ──► (silently) constraints, leverage, commitments,
events, narratives, edges
│
▼
TACITUS Conflict Graph
│
▼
Health Check (0-100%)
Theory Matching (11 frameworks)
Common Ground Analysis
│
▼
Resolution Proposals
(ZOPA-grounded, party-specific)
Key properties:
- Multi-party: Each party speaks to the same agent separately; the agent builds a shared graph without revealing what the other party said
- Real-time audio: Powered by Gemini Live API (
gemini-2.0-flash-live-001) — the agent speaks and listens in real time - Tool-augmented: 17 ADK tools that Gemini calls silently during conversation to build the graph
- Configurable: 8 mediator styles × 5 case types × free-text objective = thousands of configurations
- Deployable: Local, Docker, Google Cloud Run, Google Cloud Shell
concordia (root)
└── listener_agent
└── verifier_agent
└── resolver_agent
Each agent is a google.adk.agents.Agent with a distinct role:
| Agent | Role | Tools |
|---|---|---|
concordia (root) |
Welcomes parties, transfers immediately to listener | None |
listener_agent |
Natural conversation + silent graph building | 11 build tools |
verifier_agent |
Checks graph completeness, identifies gaps | 6 analysis tools |
resolver_agent |
Finds resolution paths, common ground proposals | 6 analysis tools |
The agents hand off using ADK's built-in transfer_to_agent mechanism — the graph is shared state in memory.
CONCORDIA's knowledge structure is based on UN Security Council mediation practice, adapted for AI extraction. Every conflict is decomposed into 8 primitives:
| Primitive | Description | Example |
|---|---|---|
| Actor | Person, org, state, group involved | "Maria Chen" (tenant) |
| Claim | Demand, accusation, grievance, proposal, justification | "Wants full deposit back" |
| Interest | Underlying need: security, economic, identity, autonomy, recognition, procedural | "Needs financial stability to cover next deposit" |
| Constraint | Legal, financial, temporal, normative, structural, relational limits | "30-day legal notice period" |
| Leverage | Coercive, reward, informational, normative, relational, structural power | "Landlord can withhold reference letter" |
| Commitment | Promises made (active, broken, fulfilled) | "Agreed to fix heating by November" |
| Event | Trigger, escalation, de-escalation, negotiation, agreement, violation | "Heating broke on Jan 5" |
| Narrative | How each party frames the conflict (victim, betrayal, injustice, etc.) | "Maria sees herself as exploited tenant" |
| Edge | Relationships between any two nodes | MAKES_CLAIM, HAS_INTEREST, HOLDS_LEVERAGE |
The graph is stored as a Pydantic ConflictGraph model in Python memory (per-session). Each node has a contributed_by field tracking which party provided it — enabling per-party health checks and confidentiality enforcement.
The health check evaluates 8 dimensions (0-100% score):
- Two or more actors identified
- All actors have claims
- All actors have interests
- Constraints identified
- Leverage mapped
- Events recorded
- Narratives captured
- Case metadata set (title + summary)
When score ≥ 75%, the agent transitions to resolution mode.
The find_common_ground() method on ConflictGraph:
- Shared interests: Groups interests by type; types held by 2+ actors = common ground
- Broken commitments: Lists promises that were broken — acknowledgment needed before resolution
- Leverage balance: Maps total leverage strength per actor; asymmetry flags need for safeguards
CONCORDIA scores 11 conflict resolution theories against the current graph:
| Theory | School | Best For |
|---|---|---|
| Fisher & Ury — Interest-Based | Harvard Negotiation Project | Commercial, demand-heavy |
| Galtung Triangle | Peace Research (Transcend) | Deep-rooted, identity conflicts |
| Glasl 9-Stage Escalation | European conflict studies | Escalating disputes |
| Bush & Folger — Transformative | Relational/transformative | Relationship repair |
| Winslade & Monk — Narrative | Social constructionist | Identity, cultural conflicts |
| Interest-Based Relational | ADR / workplace | Workplace, ongoing partnerships |
| Principled Negotiation (ZOPA) | Negotiation theory | Multi-issue, commercial |
| Rights-Based Framework | Legal / human rights | Employment, contractual |
| Power Analysis | Critical conflict theory | Power-imbalanced disputes |
| Restorative Justice | Zehr / Pranis | Post-harm, trust violations |
| Peacemaking Circles | Indigenous / community | Community, multi-party harm |
Scoring is based on: matching claim types, matching interest types, escalation level, narrative frames, broken commitments count.
The server exposes a bidi-streaming WebSocket at /ws/{user_id}/{session_id}.
Incoming messages (client → server):
{ "type": "text", "content": "I want to discuss the rent dispute..." }
{ "type": "image", "data": "<base64>", "mime": "image/jpeg" }Binary frames: raw PCM audio at 16kHz mono.
Outgoing messages (server → client):
{ "type": "text", "content": "I hear you...", "author": "concordia" }
{ "type": "tool_call", "tool": "add_claim", "args": {...} }
{ "type": "graph_update", "graph": {...}, "health": {...}, "objective": "..." }
{ "type": "transcript", "content": "...", "role": "user|model" }
{ "type": "error", "content": "...", "error_type": "quota_exhausted|internal" }Binary frames: PCM audio at 24kHz from the agent's voice.
Select at runtime — affects the agent's personality, questioning technique, and resolution philosophy:
| Style | Key Approach | Best For |
|---|---|---|
| Empathetic Facilitator | Emotional validation, reflective listening, slow pace | Family, personal disputes |
| Analytical Strategist | Systematic mapping, BATNA analysis, quantification | Commercial, workplace |
| Directive Mediator | Direct, efficient, reality-testing | Time-sensitive, high-stakes |
| Transformative Mediator | Empowerment + recognition moments (Bush & Folger) | Relationship repair |
| Narrative Mediator | Externalize problem, reauthor stories (Winslade & Monk) | Identity, cultural conflicts |
| Facilitative Mediator | Classic neutral process-manager, party-led solutions | Standard ADR |
| Evaluative Mediator | Assessments of merits, BATNA/WATNA, bracketing | Legal, rights-based |
| Restorative Circle Facilitator | Harm repair, accountability, community healing | Post-harm, justice contexts |
Adjusts the ontology focus, probing questions, and applicable theories:
| Type | Focus | Probing Questions |
|---|---|---|
| Workplace / HR | Hierarchy, HR policies, power dynamics, documentation | Reporting relationship, paper trails, career impact |
| Family / Relationship | Emotional bonds, children, shared history, financial ties | Relationship history, dependents, shared values |
| Commercial / Business | Contracts, financial amounts, industry standards, ongoing relationship | Written agreement, dispute amounts, BATNA |
| Community / Neighborhood | Shared space, community norms, local regulations, long-term coexistence | Length of relationship, daily impact, bylaws |
| Geopolitical / International | Sovereignty, security, international law, historical grievances | Core territorial issues, security concerns, external actors |
| Method | Path | Description |
|---|---|---|
| GET | / |
Frontend UI |
| GET | /api/health |
Server health + Gemini API status |
| GET | /api/status |
Current mediation state summary |
| GET | /api/graph |
Full conflict graph JSON |
| GET | /api/common-ground |
Shared interests, broken commitments, leverage balance |
| GET | /api/graph-summary |
Text summary of the graph (for display) |
| GET | /api/theories |
Ranked applicable theories for current graph |
| GET | /api/styles |
Available mediator styles |
| GET | /api/case-types |
Available case types |
| GET | /api/party-health/{party_id} |
Health check for one party's contributions |
| POST | /api/set-key |
Set Gemini API key at runtime |
| POST | /api/configure |
Set style + case type + objective, rebuild agents |
| POST | /api/set-objective |
Update mediation objective only |
| POST | /api/upload |
Upload a document (text) for ingestion |
| POST | /api/reset |
Reset graph (optionally keep config) |
| WS | /ws/{user_id}/{session_id} |
Bidi-streaming mediation session |
cd concordia
# Create virtual environment
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
# Install dependencies
pip install -r requirements.txt
# Configure
cp .env.example .env
# Edit .env — add your GOOGLE_API_KEY
# Run
python main.py
# Or: uvicorn main:app --reload --port 8080
# Open http://localhost:8080cd concordia
# Set your API key
export GOOGLE_API_KEY="AIzaSy..."
# Build and run
docker compose up --build
# Open http://localhost:8080cd concordia
docker build -t concordia .
docker run -p 8080:8080 \
-e GOOGLE_API_KEY="AIzaSy..." \
-e CONCORDIA_MODEL="gemini-2.0-flash-live-001" \
concordia
# Open http://localhost:8080Cloud Run is the recommended production deployment. It handles scaling, HTTPS, and WebSocket session affinity automatically.
# Install Google Cloud CLI
# https://cloud.google.com/sdk/docs/install
gcloud auth login
gcloud config set project YOUR_PROJECT_IDcd concordia
# Set your API key
export GOOGLE_API_KEY="AIzaSy..."
export PROJECT_ID="your-gcp-project"
./deploy.shThe script:
- Enables required GCP APIs (Cloud Run, Cloud Build, Container Registry)
- Builds the container image using Cloud Build
- Deploys to Cloud Run with session affinity and 1-hour timeout
- Prints the service URL
Key Cloud Run flags explained:
--session-affinity: Routes WebSocket connections from the same client to the same instance (required for bidi-streaming)--timeout 3600: Allows 1-hour sessions (WebSocket connections are long-lived)--memory 1Gi: Each instance holds the conflict graph in memory--min-instances 0: Scales to zero when idle (cost-efficient)--allow-unauthenticated: Public access (add IAM auth for production)
Open Google Cloud Shell, upload or clone the repo, then:
cd concordia
GOOGLE_API_KEY="AIzaSy..." bash deploy_cloudshell.shThis uses gcloud run deploy --source . which builds and deploys in one command.
cd concordia
PROJECT_ID="your-project"
REGION="us-central1"
IMAGE="gcr.io/${PROJECT_ID}/concordia"
# Build
gcloud builds submit --tag "${IMAGE}"
# Deploy
gcloud run deploy concordia \
--image "${IMAGE}" \
--region "${REGION}" \
--allow-unauthenticated \
--timeout 3600 \
--session-affinity \
--memory 1Gi \
--cpu 1 \
--set-env-vars "GOOGLE_GENAI_USE_VERTEXAI=FALSE,GOOGLE_API_KEY=YOUR_KEY,CONCORDIA_MODEL=gemini-2.0-flash-live-001"For production, set the API key via Secret Manager instead of environment variables:
# Create secret
echo -n "AIzaSy..." | gcloud secrets create gemini-api-key --data-file=-
# Grant Cloud Run service account access
gcloud secrets add-iam-policy-binding gemini-api-key \
--member="serviceAccount:$(gcloud run services describe concordia --region us-central1 --format='value(spec.template.spec.serviceAccountName)')" \
--role="roles/secretmanager.secretAccessor"
# Redeploy with secret
gcloud run deploy concordia \
--image "${IMAGE}" \
--region "${REGION}" \
--update-secrets="GOOGLE_API_KEY=gemini-api-key:latest"# View logs
gcloud run services logs read concordia --region us-central1
# Stream logs
gcloud run services logs tail concordia --region us-central1
# Check service status
gcloud run services describe concordia --region us-central1To use Vertex AI instead of the public Gemini API:
- Enable Vertex AI API:
gcloud services enable aiplatform.googleapis.com - Update
.env:GOOGLE_GENAI_USE_VERTEXAI=TRUE GOOGLE_CLOUD_PROJECT=your-project-id GOOGLE_CLOUD_LOCATION=us-central1 # Remove GOOGLE_API_KEY (not needed for Vertex AI)
- Cloud Run service account needs
roles/aiplatform.userIAM role
- Set your Gemini API key (top-left input or via
/api/set-key) - Choose a Mediator Style that fits the dispute:
- Family conflict → Empathetic or Transformative
- Business dispute → Analytical or Evaluative
- Community issue → Facilitative or Restorative Circle
- Rights violation → Evaluative or Rights-Based
- Choose a Case Type (workplace, family, commercial, community, geopolitical)
- Enter a Mediation Objective (optional but recommended)
- Example: "Reach agreement on lease termination terms by end of month"
- Example: "Establish a co-parenting schedule that prioritizes the children's needs"
- Set party to "Party 1", click Connect
- Party 1 speaks naturally — the agent listens and builds the graph silently
- Watch the graph stats update as data is extracted
- The health bar shows readiness for resolution (75% threshold)
- Document upload: drag-drop a contract, email, or letter for automatic extraction
- Change party to "Party 2", click Connect (creates a new session)
- Party 2 speaks independently — the agent says "I have some background" but reveals nothing from Party 1
- The agent uses the same graph, adding Party 2's perspective
- Graph tab: Visual network of actors, claims, interests and their relationships
- Theories tab: Auto-ranked applicable conflict resolution frameworks
- Resolve tab: Click "Analyze Common Ground" to see shared interests, broken commitments, and leverage balance
- When graph health ≥ 75%, the agent transfers to
resolver_agent - The resolver proposes 2-3 concrete resolution paths grounded in graph data
- Each path includes: what each side gets/gives, why it works, risks, and implementation steps
- The "Ask Agent for Resolution Paths" button in the Resolve tab triggers this directly
- Let the agent drive: It knows when to probe for more information
- Be specific: "They fired me on January 5 with no warning" extracts better data than "it was unfair"
- Upload documents: Contracts, emails, letters — the agent extracts all primitives automatically
- Use voice mode: The Gemini Live API enables natural back-and-forth — the agent handles interruptions
- Switch styles mid-session: If analytical isn't working, switch to empathetic without losing data
concordia/
├── main.py # FastAPI server, WebSocket, REST endpoints
├── requirements.txt # Python dependencies
├── Dockerfile # Production container
├── docker-compose.yml # Local development
├── deploy.sh # Cloud Run deployment script
├── deploy_cloudshell.sh # Google Cloud Shell one-liner
├── .env.example # Environment variable template
├── .gitignore
├── static/
│ └── index.html # Full-featured frontend UI
└── concordia_agent/
├── __init__.py # Exports: root_agent, graph, build_agents
├── agent.py # Agent factory (listener/verifier/resolver hierarchy)
├── ontology.py # ConflictGraph + 8 primitives + health check
├── tools.py # 17 ADK tool functions
├── styles.py # 8 mediator style configurations
├── case_types.py # 5 case type configurations
└── resolution_library.py # 11 conflict resolution theories
| Variable | Default | Description |
|---|---|---|
GOOGLE_API_KEY |
(required) | Gemini API key from Google AI Studio |
GOOGLE_GENAI_USE_VERTEXAI |
FALSE |
Set TRUE to use Vertex AI instead |
GOOGLE_CLOUD_PROJECT |
— | GCP project ID (Vertex AI only) |
GOOGLE_CLOUD_LOCATION |
— | GCP region (Vertex AI only) |
CONCORDIA_MODEL |
gemini-2.0-flash-live-001 |
Gemini model to use |
CONCORDIA_VOICE |
Aoede |
Voice for audio output |
PORT |
8080 |
Server port (Cloud Run injects this) |
Available voices: Aoede, Charon, Fenrir, Kore, Puck, Orbit, Zephyr
Available models:
gemini-2.0-flash-live-001— Real-time audio + text (recommended)gemini-2.0-flash-exp— Text only (fallback, lower latency for text)
In concordia_agent/styles.py, add an entry to MEDIATOR_STYLES:
"my_style": {
"name": "My Custom Style",
"icon": "🎯",
"description": "Description of when to use this style.",
"instruction_modifier": """
STYLE: MY CUSTOM STYLE
- Your behavioral instructions here...
- How to open conversations
- What to prioritize
- How to summarize
""",
},In concordia_agent/case_types.py:
"healthcare": {
"name": "Healthcare / Patient Dispute",
"icon": "🏥",
"description": "Medical negligence, patient rights, provider disputes",
"focus_interests": ["security", "autonomy", "procedural"],
"focus_claims": ["accusation", "grievance", "demand"],
"context_prompt": """This is a HEALTHCARE dispute. Focus on:
- Patient rights and informed consent
- Medical standards of care
...
""",
"probing_questions": [
"What treatment decision is at the center of this dispute?",
...
],
},In concordia_agent/resolution_library.py:
"my_theory": {
"name": "My Theory Name",
"short": "Tagline",
"school": "Academic school",
"principles": ["Principle 1", "Principle 2"],
"best_for": ["conflict type 1", "conflict type 2"],
"techniques": ["Technique 1", "Technique 2"],
"indicators": {
"claim_types": ["demand", "grievance"], # Boosts score when present
"interest_types": ["economic"],
"escalation_min": "emerging", # Minimum escalation level
"has_broken_commitments": True, # Requires broken commitments
"min_narratives": 2, # Minimum narratives in graph
},
},In concordia_agent/tools.py:
def my_new_tool(arg1: str, arg2: int) -> dict:
"""Tool description — Gemini uses this docstring to understand when to call it.
Args:
arg1: Description of arg1.
arg2: Description of arg2.
Returns:
dict with the result.
"""
graph = _graph()
# Do something with the graph
return {"status": "ok"}Then add it to the appropriate tool list:
LISTENER_TOOLS = [..., my_new_tool]
ANALYZER_TOOLS = [..., my_new_tool]Set your Gemini API key: POST /api/set-key {"key": "AIzaSy..."}
- Check that the server is running:
GET /api/health - For Cloud Run: ensure
--session-affinityflag is set - For Cloud Run: ensure
--timeout 3600is set (default is 300s, too short for WebSocket sessions)
- Check logs for Gemini API errors
- Try text-only model: set
CONCORDIA_MODEL=gemini-2.0-flash-exp - Check quota: Gemini Live API has separate quotas from standard Gemini
- Audio requires HTTPS in production (or
localhostfor development) - The model must support audio: only
*-live-*and*-native-audio-*models do - Check browser microphone permissions
- The graph updates happen on tool calls — check the chat for tool call pills (purple ⚙ labels)
- If no tool calls appear, the agent may not be recognizing extraction triggers — try being more specific in your messages
MIT License — See LICENSE file.
Built with: