For the complete documentation index, see llms.txt. This page is also available as Markdown.

Architecture Overview

Version: 6.2.0

This document provides a comprehensive overview of EDDI's architecture, design principles, and internal workflow.

Table of Contents


Overview

E.D.D.I. (Enhanced Dialog Driven Interface) is a multi-agent orchestration middleware for conversational AI systems, not a standalone agent or language model. It sits between user-facing applications and multiple AI agents (LLMs like OpenAI, Claude, Gemini, or traditional REST APIs), intelligently routing requests, coordinating responses, and maintaining conversation state across agent interactions.

Core Purpose: Orchestrate multiple AI agents and business systems in complex conversational workflows without writing code.

What EDDI Is (and Isn't)

  • A Multi-Agent Orchestration Middleware: Coordinates multiple AI agents (LLMs, APIs) in complex workflows

  • An Intelligent Router: Directs requests to appropriate agents based on patterns, rules, and context

  • A Conversation Coordinator: Maintains stateful conversations across multiple agent interactions

  • A Configuration Engine: Agent orchestration defined through JSON configurations, not code

  • A Middleware Service: Acts as an intermediary that adds intelligence and control to conversation flows

  • Business System Integrator: Connects AI agents with your existing APIs, databases, and services

  • Cloud-Native: Built with Quarkus for fast startup, low memory footprint, and containerized deployment

  • Stateful: Maintains complete conversation history and context throughout interactions

EDDI Is Not:

  • Not a standalone LLM: It doesn't train or run machine learning models

  • Not a chatbot platform: It's the infrastructure that powers conversational agents

  • Not just a proxy: It provides orchestration, state management, and complex behavior rules beyond simple API forwarding


Core Architecture

Architectural Principles

EDDI's architecture is built on several key principles:

  1. Modularity: Every component is pluggable and replaceable

  2. Composability: Agents are assembled from reusable workflows and extensions

  3. Asynchronous Processing: Non-blocking I/O for handling concurrent conversations

  4. State-Driven: All operations transform or query the conversation state

  5. Cloud-Native: Designed for containerized, distributed deployments

High-Level Architecture Diagram


The Lifecycle Pipeline

The Lifecycle is EDDI's most distinctive architectural feature. Instead of hard-coded agent logic, EDDI processes every user interaction through a configurable, sequential pipeline of tasks called the Lifecycle.

How the Lifecycle Works

  1. Pipeline Composition: Each agent defines a sequence of ILifecycleTask components

  2. Sequential Execution: Tasks execute one after another, each transforming the IConversationMemory

  3. Stateless Tasks: Each task is stateless; all state resides in the memory object passed through

  4. Interruptible: The pipeline can be stopped early based on conditions (e.g., STOP_CONVERSATION)

Standard Lifecycle Tasks

A typical agent lifecycle includes these task types:

Task Type
Purpose
Example

Input Parsing

Normalizes and understands user input

Extracting entities, intents from text

Semantic Parsing

Uses dictionaries to parse expressions

Matching "hello" → greeting(hello)

Behavior Rules

Evaluates IF-THEN rules to decide actions

"If greeting(*) then action(welcome)"

Property Extraction

Extracts and stores data in conversation memory

Saving user name, preferences

HTTP Calls

Calls external REST APIs

Weather API, CRM systems

LangChain Task

Invokes LLM APIs (OpenAI, Claude, etc.)

Conversational AI responses

Output Generation

Formats final response using templates

Qute templating with conversation data

Lifecycle Task Interface

Every task receives:

  • IConversationMemory: Complete conversation state

  • component: Task-specific configuration/resources


Conversation Flow

Step-by-Step: User Interaction Flow

Here's what happens when a user sends a message to an EDDI agent:

1. API Request

2. RestAgentEngine Receives Request

  • Validates agent ID and environment

  • Wraps response in AsyncResponse for non-blocking processing

  • Increments metrics counters

3. ConversationCoordinator Queues Message

  • Ensures messages for the same conversation are processed sequentially

  • Allows different conversations to process concurrently

  • Prevents race conditions in conversation state

4. IConversationMemory Loaded/Created

  • If existing conversation: Loads from MongoDB

  • If new conversation: Creates fresh memory object

  • Includes all previous steps, user data, context

5. LifecycleManager Executes Pipeline

Each task in sequence:

  • Reads current conversation state

  • Performs its operation (parsing, rule evaluation, API call, etc.)

  • Writes results back to conversation memory

  • Passes control to next task

6. State Persistence

  • Updated IConversationMemory saved to MongoDB

  • Cache updated with latest conversation state

  • Metrics recorded (duration, success/failure)

7. Response Returned


Agent Composition Model

EDDI agents are not monolithic. They are composite objects assembled from version-controlled, reusable components.

Hierarchy: Agent → Workflow → Extensions

1. Agent Level

File: {agentId}.agent.json

A agent is simply a list of workflow references:

2. Workflow Level

File: {workflowId}.workflow.json

A workflow is a container of functionality with a list of steps:

3. Extension Level

Files: {extensionId}.{type}.json

Extensions are the actual agent logic:

Behavior Rules Extension

HTTP Calls Extension

LangChain Extension

What Lives Where: A Decision Guide

When adding a new feature, use this guide to decide where configuration belongs:

Question
Config Level
Example

Does it affect the entire agent across all conversations?

Agent level (AgentConfiguration)

enableMemoryTools, enableStreaming

Does it control how a pipeline step behaves?

Extension level (e.g., langchain.json, property.json)

LLM parameters, property instructions

Does it define which extensions run and in what order?

Workflow level (workflow.json)

Extension types and URIs

Is it a user-facing runtime setting?

Agent level

User memory config, audit settings

Is it a tool/capability the LLM can use?

Extension level (in langchain.json)

builtInToolsWhitelist

Rule of thumb: If a feature is a cross-conversation concern (e.g., persistent memory, user preferences, GDPR compliance), it belongs at the agent level. If it's a per-turn processing concern (e.g., LLM parameters, HTTP call config), it belongs at the extension level.


Key Components

RestAgentEngine

Location: ai.labs.eddi.engine.internal.RestAgentEngine

Purpose: Main entry point for all agent interactions

Responsibilities:

  • Receives HTTP requests via JAX-RS

  • Validates agent and conversation IDs

  • Handles async responses

  • Records metrics

  • Coordinates with IConversationCoordinator

ConversationCoordinator

Location: ai.labs.eddi.engine.runtime.internal.ConversationCoordinator

Purpose: Ensures proper message ordering and concurrency control

Key Feature: Uses a queue system to guarantee that:

  • Messages within the same conversation are processed sequentially

  • Different conversations can be processed in parallel

  • No race conditions occur in conversation state updates

IConversationMemory

Location: ai.labs.eddi.engine.memory.IConversationMemory

Purpose: The stateful object representing a complete conversation

Contains:

  • Conversation ID, agent ID, user ID

  • All previous conversation steps (history)

  • Current step being processed

  • User properties (name, preferences, etc.)

  • Context data (passed with each request)

  • Actions and outputs generated

Key Methods:

LifecycleManager

Location: ai.labs.eddi.engine.lifecycle.internal.LifecycleManager

Purpose: Executes the lifecycle pipeline

Key Method:

How It Works:

  1. Iterates through registered ILifecycleTask instances

  2. For each task, calls task.execute(conversationMemory, component)

  3. Checks for interruption or stop conditions

  4. Continues until all tasks complete or stop condition is met

WorkflowConfiguration

Location: ai.labs.eddi.configs.workflows.model.WorkflowConfiguration

Purpose: Defines the structure of an agent workflow

Model:

ToolExecutionService

Location: ai.labs.eddi.modules.langchain.tools.ToolExecutionService

Purpose: Unified execution pipeline for all AI agent tool invocations

Pipeline:

Features:

  • Token-bucket rate limiting per tool (configurable per-tool or global default)

  • Smart caching — deduplicates calls with identical arguments

  • Cost tracking with per-conversation budgets and automatic eviction

  • Security: tools that accept URLs are validated against private/internal addresses (SSRF protection via UrlValidationUtils)

  • Security: math expressions are evaluated in a sandboxed parser (SafeMathParser)

See the Security documentation for details.

System Prompt Modifiers

Location: ai.labs.eddi.modules.llm.impl

Two services modify the system prompt before it is sent to the LLM. Both are configured per-task in the LLM configuration (langchain.json).

Service
Purpose
Config Key

IdentityMaskingService

Prepends identity concealment rules (agent name, refusal patterns)

task.identityMasking

CounterweightService

Appends behavioral safety instructions (cautious/strict presets)

task.counterweight

Execution order: Identity masking → Counterweight → LLM call.

Counterweight presets are resolved from Prompt Snippets first (counterweight-cautious, counterweight-strict), falling back to built-in defaults. This ensures admins can customize safety language without code changes.

See LLM Integration — Behavioral Safety for configuration details.

Attachment Storage

Location: ai.labs.eddi.engine.attachments

The attachment subsystem handles binary file storage for multimodal conversations:

Component
Purpose

IAttachmentStore

Interface for storing/loading binary attachments (GridFS for MongoDB, BLOB for PostgreSQL)

MimeValidator

Magic-byte detection (16+ formats) and declared-vs-detected MIME compatibility checking

MultimodalMessageEnhancer

Converts stored attachments into langchain4j Content objects (images → ImageContent via base64 data URI, others → text markers)


Technology Stack

Core Framework

  • Quarkus: Supersonic, subatomic Java framework

    • Fast startup times (~0.05s)

    • Low memory footprint

    • Native compilation support

    • Built-in observability (metrics, health checks)

Language & Runtime

  • Java 25: Latest LTS with modern language features

  • GraalVM: Optional native compilation for even faster startup

Dependency Injection

  • CDI (Contexts and Dependency Injection): Jakarta EE standard

  • @ApplicationScoped, @Inject: Clean, testable component wiring

REST Framework

  • JAX-RS: Jakarta REST API standard

  • AsyncResponse: Non-blocking, scalable request handling

  • JSON-B: JSON binding for serialization/deserialization

Database (DB-Agnostic)

  • MongoDB 6.0+ (default): Document store for agent configurations and conversation logs

  • PostgreSQL (alternative): JDBC + JSONB storage, switchable via eddi.datastore.type=postgres

  • Both backends support:

    • Agent, workflow, and extension configuration storage

    • Conversation history persistence

    • Version control of agent components

    • Automatic schema migration on startup

Caching

  • Caffeine: High-performance in-memory cache (replaced Infinispan in v6)

    • Caches conversation state and agent configurations

    • Configurable size limits per cache type

    • Zero external dependencies — provided transitively by quarkus-cache

LLM Integration

  • LangChain4j: Java library for LLM orchestration

    • Unified interface to multiple LLM providers

    • Supports OpenAI, Claude, Gemini, Ollama, Hugging Face, etc.

    • Handles chat message formatting, streaming, tool calling

Observability

  • Micrometer: Metrics collection

  • Prometheus: Metrics exposition

  • Kubernetes Probes: Liveness and readiness endpoints

Security

  • OAuth 2.0: Authentication and authorization

  • Keycloak: Identity and access management

Templating

  • Qute: Output templating engine

    • Dynamic output generation

    • Access to conversation memory in templates

    • Expression language support


Design Patterns Used

1. Strategy Pattern

  • Where: Lifecycle tasks

  • Why: Different behaviors (parsing, rules, API calls) implement the same ILifecycleTask interface

2. Chain of Responsibility

  • Where: Lifecycle pipeline

  • Why: Each task processes the memory object and passes it to the next task

3. Composite Pattern

  • Where: Agent composition (Agent → Workflows → Extensions)

  • Why: Agents are built from hierarchical, reusable components

4. Repository Pattern

  • Where: Data access (stores: agentstore, workflowstore, etc.)

  • Why: Abstracts data persistence from business logic

5. Factory Pattern

  • Where: IAgentFactory

  • Why: Complex agent instantiation from multiple workflows and configurations

6. Coordinator Pattern

  • Where: ConversationCoordinator

  • Why: Manages concurrent access to shared conversation state


Performance Characteristics

Startup Time

  • JVM mode: < 2 seconds

  • Native mode: < 50ms (with GraalVM)

Memory Footprint

  • JVM mode: ~200MB baseline

  • Native mode: ~50MB baseline

Request Latency

  • Without LLM: 10-50ms (parsing, rules, simple API calls)

  • With LLM: 500-5000ms (depends on LLM provider)

Scalability

  • Vertical: Handles thousands of concurrent conversations per instance

  • Horizontal: Stateless design allows infinite horizontal scaling

  • Agenttleneck: MongoDB becomes agenttleneck; use replica sets and sharding


Cloud-Native Features

Containerization

  • Official Docker images: labsai/eddi

  • Certified by IBM/Red Hat

  • Multi-stage builds for minimal image size

Orchestration

  • Kubernetes-ready

  • OpenShift certified

  • Health checks built-in

Configuration

  • Externalized configuration via environment variables

  • ConfigMaps and Secrets support

  • No rebuild needed for configuration changes

Observability

  • Prometheus metrics endpoint: /q/metrics

  • Health checks: /q/health/live, /q/health/ready

  • Structured logging with correlation IDs


Case Study: The "Agent Father"

The Agent Father is a meta-agent that demonstrates EDDI's architecture in action. It's an agent that creates other agents.

For a comprehensive, step-by-step walkthrough of Agent Father, see Agent Father: A Deep Dive

How It Works

  1. Conversation Start: User starts chat with Agent Father

  2. Information Gathering: Agent Father asks questions:

    • "What do you want to call your agent?"

    • "What should it do?"

    • "Which LLM API should it use?"

  3. Memory Storage: Property setters save answers to conversation memory:

    • context.agentName

    • context.agentDescription

    • context.llmType

  4. Condition Triggers: Behavior rule monitors memory:

  5. API Call Execution: HTTP Calls extension triggers:

  6. Self-Modification: Agent Father calls EDDI's own API to create a new agent configuration

Key Insight

Agent Father isn't special code—it's a regular EDDI agent that uses:

  • Behavior rules to control conversation flow

  • Property extraction to gather data

  • HTTP Calls to invoke EDDI's REST API

  • Output templates to guide the user

This demonstrates EDDI's power: the same architecture that powers conversational agents can orchestrate complex, multi-step workflows, even self-modifying the system itself.

See the Agent Father Deep Dive for complete implementation details, code examples, and real-world applications.


Summary

EDDI's architecture is built on principles of modularity, composability, and orchestration. It's not a chatbot—it's the infrastructure for building sophisticated conversational AI systems that can:

  • Orchestrate multiple APIs and LLMs

  • Apply complex business logic through configurable rules

  • Maintain stateful, context-aware conversations

  • Scale horizontally in cloud environments

  • Be assembled from reusable, version-controlled components

The Lifecycle Pipeline is the heart of this architecture, providing a flexible, pluggable system where agent behavior is configuration, not code.


Configuration Model Deep Dive

EDDI's configuration model is a 4-level tree:

Agent → Workflows → Extensions

Level
Purpose

Agent

List of workflow URIs + channels. The top-level container.

Workflow

Ordered list of workflow extensions — each extension = one lifecycle task type. Order matters: tasks execute sequentially.

Extension

The actual configuration that drives each ILifecycleTask. Referenced by URI from the workflow.

Descriptor

Metadata (name, description, timestamps) for any resource. Not functional, purely for UI/management.

URI-Based References

Every resource references its dependencies by eddi:// URI:

Extension Types & Their Pipeline Role

Each workflow runs its extensions in order: Parser → Behavior → Property → HttpCalls → LLM → Output (typical order).

Extension Type
Input
Output
Key Feature

Parser

Raw user text

Expressions (semantic representation)

expressionsAsActions: true — parser expressions become actions

Behavior Rules

Actions and expressions

New actions that drive subsequent tasks

IF-THEN condition engine — the routing logic

Property Setter

Current memory data

Stored properties (conversation-scoped or long-term)

Slot-filling using {memory.current.input} templates

HTTP Calls

Actions, template variables

Response data stored in memory

Pre/post request property instructions, retry support

LLM

Conversation memory, system prompt, tools

LLM response text

Legacy chat (simple) or Agent mode (tool-calling loop)

Output Templates

Actions from current step

Text responses + quickReplies

Template variables, response variation via valueAlternatives

Parser & Expression System

The parser uses a recursive expression model with Prolog heritage:

QuickReply → Expression → Action flow: When a user clicks a quickReply, the parser matches the text against the previous step's quickReply value fields, extracts the corresponding expressions, and (if expressionsAsActions is enabled) converts them to actions that drive behavior rules.

Available NLP Extensions

Type
Extensions

Dictionaries (7)

RegularDictionary, IntegerDictionary, DecimalDictionary, EmailDictionary, TimeExpressionDictionary, OrdinalNumbersDictionary, PunctuationDictionary

Normalizers (4)

ContractedWordNormalizer, ConvertSpecialCharacterNormalizer, PunctuationNormalizer, RemoveUndefinedCharacterNormalizer

Corrections (3)

DamerauLevenshteinCorrection, MergedTermsCorrection, PhoneticCorrection


Database Architecture

EDDI's data layer is fully DB-agnostic via the IResourceStorageFactory SPI:

Switching databases requires only a config change:


Multi-Agent Orchestration

Beyond single-agent conversations, EDDI supports group conversations — structured multi-agent discussions where multiple agents collaborate on a question under the governance of a moderator agent.

A GroupConversationService orchestrates discussions through configurable phases. Each participating agent runs through its normal lifecycle pipeline — agents are group-unaware by design. The moderator serializes all contributions, preventing concurrent writes to shared state.

Key capabilities:

  • 6 built-in discussion styles: Round Table, Peer Review, Devil's Advocate, Delphi, Debate, and Task Force — each with distinct phase flows and turn-taking rules. Task Force uses a 4-phase pipeline (PLAN→EXECUTE→VERIFY→SYNTHESIS) for structured task decomposition and parallel execution

  • Custom phases: Define your own phase sequences with configurable context scopes (independent, full transcript, anonymous, own-feedback-only)

  • Group-of-groups: Members can themselves be groups, enabling hierarchical multi-agent composition with configurable depth limits

  • Fault tolerance: Per-agent timeouts, configurable failure policies (skip, retry, abort), and graceful degradation when members are unavailable

  • Dynamic agents: Agents can create, recruit, delegate to, and teardown new agents at runtime during discussions, with configurable guardrails (provider/model whitelists, per-discussion caps, lifecycle policies)

See Group Conversations for full configuration reference, and A2A Protocol for peer-to-peer agent communication.


MCP Integration (Bilateral)

EDDI provides bilateral Model Context Protocol (MCP) integration — it is both an MCP Server and an MCP Client simultaneously.

As MCP Server: EDDI exposes its full API surface (conversations, administration, diagnostics, scheduling, group discussions) as MCP tools. This enables AI assistants (Claude Desktop, IDE plugins, custom MCP clients) to interact with deployed agents and manage the platform programmatically. Documentation is also exposed as MCP resources (eddi://docs/{name}).

As MCP Client: Individual agents can consume external MCP servers as tool providers. MCP server connections are configured as mcpcalls workflow extensions (versioned configuration resources, the MCP equivalent of httpcalls), support vault-based API key resolution, and are subject to the same rate limiting, caching, and cost tracking as built-in tools. The LLM auto-discovers them from the workflow (enableMcpCallTools, default true); behavior rules can also trigger specific MCP tools deterministically. Failed MCP connections degrade gracefully — they never kill the pipeline.

See MCP Server for the full tool reference and client configuration.


Persistent User Memory

EDDI's memory model extends beyond single conversations. The IUserMemoryStore provides persistent key-value memory scoped per user, per agent, with visibility controls (self, group, global).

How it integrates with the pipeline:

  • At conversation init, visible user memories are loaded as longTerm properties and made available in all templates via {properties.key}

  • During the pipeline, the LLM can autonomously store and recall facts using built-in memory tools (when enabled)

  • At conversation teardown, longTerm properties are persisted back to the user memory store

  • Background consolidation (the "Dream" service) performs stale pruning, contradiction detection, and optional LLM-driven summarization. It runs on the same cluster-aware schedule machinery as every other background job — a ScheduleConfiguration whose metadata carries {"dreamType": "dream_consolidation"} is claimed by SchedulePollerService and dispatched by ScheduleFireExecutor to DreamService, which reads the agent's userMemoryConfig.dream block and runs one cycle. The target agent and user come from the schedule's top-level agentId / agentVersion / userId fields — metadata carries only the dreamType marker. Spend is bounded per cycle by dream.maxCostPerRun (US dollars), and because Dream has no parent LLM task to inherit credentials from, its model credentials come from dream.parameters (which resolves ${vault:…} and ${vars:…} like any LLM task's parameters). A cycle that cannot run — no userId, dream disabled on the agent, or a failing LLM call — is logged at ERROR and marked FAILED on the fire log, so it retries with backoff and dead-letters rather than silently doing nothing

Creating a Dream schedule — use the raw REST body, POST /schedulestore/schedules, with the cron expression from dream.schedule:

Three things this body does that are easy to get wrong:

  • The create_schedule MCP tool cannot do this. Its arguments (agentId, triggerType, cron, heartbeatIntervalSeconds, message, name, timeZone, conversationStrategy, userId, environment) contain no metadata, so a schedule created that way has metadata == null, DreamService.isDreamSchedule(…) returns false, and the schedule fires an ordinary chat turn against the agent on the dream cron forever — logged COMPLETED, consolidating nothing. REST is the only route that produces a working Dream schedule today.

  • message is required even though Dream never reads it. RestScheduleStore.validateSchedule rejects a CRON schedule without a non-blank message; the Dream fast-path bypasses say() entirely, so the value is inert — supply any placeholder.

  • userId must name the real user whose memories are consolidated. Left unset it defaults to system:scheduler, which DreamService rejects (the cycle is marked FAILED rather than consolidating an empty memory set).

Memory visibility is enforced at the storage level — agents can only see memories matching their visibility scope, preventing cross-tenant memory leaks.

See Persistent User Memory for configuration, LLM tools, REST API, and the Dream consolidation service.


Agent Sync & Portability

Agent configurations are fully portable — exportable, importable, and synchronizable between EDDI instances.

The sync pipeline:

  1. Transport abstraction: IResourceSource abstracts the source — either a ZIP file (ZipResourceSource) or a live remote instance (RemoteApiResourceSource)

  2. Structural matching: Resources are paired deterministically by position, type, and name — not by ID. This works even for independently-created agents

  3. Content sync: The UpgradeExecutor updates target resources in-place, preserving IDs and URI references. Version numbers increment; no broken links

  4. Preview before apply: All sync operations support a preview step showing exactly what will be created, updated, or skipped

Agent ZIP exports automatically scrub secrets before packaging to prevent credential leaks during transfer.

See Agent Sync Architecture for the matching algorithm and data flow, and Agent Sync Guide for REST API usage.


Security Architecture

EDDI enforces security at multiple layers so individual failures don't result in full compromise.

SSRF Prevention (3-Layer Model)

Outbound HTTP from LLM tools is the primary attack surface. Three layers prevent Server-Side Request Forgery:

Layer
Component
What It Blocks

Layer 1: URL Validation

UrlValidationUtils.validateUrl()

Private IPs (10.x, 172.16-31.x, 192.168.x), loopback (127.x, ::1), link-local (169.254.x, fe80::), cloud metadata (169.254.169.254), non-HTTP schemes (file://, ftp://), hostnames resolving to private IPs

Layer 2: Redirect Validation

SafeHttpClient.sendWithRedirects()

Each redirect hop is validated against Layer 1 rules. HttpClient.Redirect.NEVER prevents the JDK from following redirects silently. Maximum 5 hops

Layer 3: Network Policy

Kubernetes NetworkPolicy

Restricts egress at the cluster level (optional, operator-configured)

Usage pattern:

DNS Rebinding: EDDI validates hostnames at request time. A TOCTOU (time-of-check-time-of-use) gap exists between DNS validation and TCP connect. This is an accepted risk — exploitation requires a cooperating DNS server AND a successful race condition AND bypassing Layer 2 redirect validation. Defense-in-depth makes this impractical in practice.

Vault Encryption Model

Secrets (API keys, credentials) never appear as plaintext in the database:

  • Per-deployment salt: VaultSaltManager generates and stores a unique 32-byte salt per EDDI instance

  • Envelope encryption: Rotating the master key re-wraps KEK→DEK without touching individual secrets

  • Export scrubbing: Agent export/sync automatically strips secrets from ZIP files

Cryptographic Agent Identity

EDDI agents can sign their inter-agent messages using Ed25519 digital signatures. This protects multi-agent group conversations against identity spoofing, message tampering, and provides non-repudiation for audit trails.

Key lifecycle:

  1. Key generation: POST /agentstore/{id}/signing/keysAgentSigningService.generateKeyPair() creates an Ed25519 keypair. Public key stored in AgentConfiguration.identity.publicKey, private key encrypted in the Secrets Vault

  2. Key rotation: AgentPublicKey records support versioned keys with validFromMs/validUntilMs windows. Old and new keys overlap during rotation. Private keys use versioned vault paths (agent-signing-key:{agentId}:v{version})

  3. Signing: When security.signInterAgentMessages=true, the GroupConversationService creates a SignedEnvelope for each agent response. The envelope contains the message payload, a UUID nonce, and an epoch timestamp. The canonical JSON form (RFC 8785 via JacksonCanonicalizer) is signed with Ed25519

  4. Self-verification: Immediately after signing, the service verifies its own signature against the agent's public key. If self-verification fails, the signature is discarded (fail-safe to unsigned)

  5. Replay protection: The NonceCacheService registers each nonce with freshness (5min default) and clock-skew (30s default) checks. Duplicate nonces are rejected

  6. Peer verification: When security.requirePeerVerification=true on a receiving agent, the service reconstructs envelopes from stored TranscriptEntry fields and verifies each speaker's signature against their public key before sending context

What is NOT covered: MCP invocation signing is not yet implemented — the signMcpInvocations config field has been removed until the feature is built.

Authentication Model

Environment
OIDC Enabled
Behavior

Dev mode

No

Allowed — info log on startup

Dev mode

Yes

Full auth with configured Keycloak

Production

No + no opt-out

AuthStartupGuard fails startup with clear error

Production

No + explicit opt-out

Starts, but logs ERROR every 60s as a constant reminder

Production

Yes

Full OIDC with Keycloak multi-tenant support

The escape hatch (EDDI_SECURITY_ALLOW_UNAUTHENTICATED=true) exists for air-gapped deployments and quick demos. The periodic ERROR log ensures operators remain aware.

CI Security Scanning

Tool
Trigger
What It Checks

CodeQL

Every PR + weekly schedule

Java semantic analysis (injection, SSRF, crypto misuse)

Trivy

Docker image build

CVEs in OS packages and Java dependencies

Dependency Review

Every PR

License compliance and known vulnerabilities in new dependencies

Jackson 3 Ban

Every build (Maven Enforcer)

Prevents accidental Jackson 3.x introduction (incompatible with Quarkus)

Security Headers

Production response headers (configured via application.properties):

  • X-Content-Type-Options: nosniff

  • X-Frame-Options: DENY

  • Content-Security-Policy: default-src 'self'; ...

  • Strict-Transport-Security: max-age=31536000 (when TLS is configured)

Last updated

Was this helpful?