EDDI 6.4.0 makes EDDI multi-user and gives agents a governed way to reach external systems.
Per-user workspaces introduce ownership, spaces and sharing for every configuration resource. Connections provide a single credential model for outbound calls, covering organisation-wide keys, service-account OAuth, each user's own OAuth account, and credentials supplied by the calling system. Both are backed by an outbound hardening pass: tool results carry provenance and a directive guardrail, credentials are kept out of URLs and console logs, and /mcp and /secretstore require an explicit opt-in to run unauthenticated in production. The release also adds Slack Observe Mode, moves to a UBI 10 base image, and publishes to Red Hat's certified catalog.
The rest of the release is a correctness effort driven by a whole-repository code review and a full end-to-end run of 6.4.0 (342 REST operations, all 84 MCP tools, every group style). Many of the resulting fixes apply to code that shipped in 6.3.0 or earlier, including audit ledger verification, agent sync, scheduling on PostgreSQL and configuration deletes, so upgrading is recommended independently of the new features. Please review the Breaking Changes before upgrading.
🚀 What's New
👥 Per-User Workspaces & Resource Sharing
Configuration resources previously had no ownership: any editor could read, change or delete any resource. Every descriptor now carries an owner, a space (user:<principal> or team:<keycloak group>), a visibility (private / space / published) and explicit grants.
| Level | Permits |
|---|---|
USE |
Talking to an agent |
VIEW |
Reading its configuration (prompts, tools, vault references) |
EDIT |
Changing it, without deleting or re-sharing |
OWN |
Everything, including delete and share |
Disabled by default (eddi.workspaces.enabled=false) and designed for a gradual rollout:
- 📝 Ownership is recorded whenever authentication is on, so attribution can be verified before enforcement is enabled.
- 🗂️ Nothing disappears on upgrade. Existing descriptors are backfilled, and
eddi.workspaces.legacy-visibility=sharedkeeps them visible to everyone. - 🕸️ Sharing an agent covers its workflows and extensions, skipping and naming any resource the sharer does not own.
- 🪪 Listings carry the caller's
callerLevel,?space=filters server-side, andGET /workspacesreports the caller's spaces and whether enforcement is active. - 🔑 Administrators can transfer ownership of resources whose owner has left.
The USE check applies to every path that reaches an agent: REST, MCP conversation tools, schedules, triggers, channel targets, group membership, and the OpenAI-compatible /v1 API (published agents only under enforcement). The shipped Keycloak realm adds a groups mapper and defines the eddi-approver role. Built-in sub-agent tools remain governed by tool approval rather than workspaces. Guide: docs/workspaces.md.
🔌 Connections
A new ConnectionConfiguration resource describes how to authenticate to one external system. HTTP calls, MCP servers and A2A peers reference it as ${connection:name}, and it resolves per request. The feature is off until enabled.
| Shape | Credential source |
|---|---|
STATIC / BASIC, binding SERVICE |
An organisation-wide key built from vault references |
OAUTH2_CLIENT_CREDENTIALS, binding SERVICE |
A service-account grant that EDDI obtains and refreshes |
OAUTH2_AUTHORIZATION_CODE, binding PER_USER |
Each user's own linked account |
STATIC, binding CALLER_SUPPLIED |
The calling system, per request, via X-EDDI-Connection-Credential. Never stored, so the agent can do no more than its user |
- 🔐 Secrets are always references. Client secrets, passwords and template segments must be
${vault:…}or${vars:…}; literals are refused at write time. - 🧱 Two separate allowlists. A connection decides where its access token may be sent; the deployment decides where client secrets may be sent. Token endpoints must use https and must not redirect.
- 🔁 Safe token refresh across replicas, using an atomic lease so rotating refresh tokens are never redeemed twice. Transient provider errors do not invalidate a grant.
- 🍪 Hardened OAuth flow with persisted state, mandatory PKCE, a browser-bound nonce cookie, and binding to the connection's id.
- 🪪
PER_USERrequires a verified identity and never falls back to a service grant. On a HITL resume, the credential follows the conversation owner, not the approver. - ⚙️ Runtime settings via
PUT /connectionstore/settings(eddi-admin), with properties able to pin values. Agent exports include referenced connections (never grants).
Guide: docs/connections.md.
🛡️ Outbound Hardening
- 🏷️ Tool results carry provenance. Results from all seven tool sources are wrapped in an envelope that marks them as data rather than instructions.
- 🧹 Configurable tool-result guardrail (
toolResultGuardrails) that marks, redacts, warns on or blocks directive-shaped text, per source and with tool exemptions. The default is marking plus redaction. - 🤝 A2A Agent Card descriptions are governed like MCP tool descriptions, and MCP and A2A calls now show approvers their target and a request fingerprint.
- ♻️ Rotated secrets are evicted from the MCP client and channel caches immediately.
- 🧰 Stdio-only MCP servers can be reached through a sidecar bridge (
docker-compose.mcp-sidecar.yml,docs/mcp-client.md).
👀 Slack Observe Mode
observeMode is now implemented. An observer agent watches channel messages that do not mention the bot and replies in a thread when all four checks pass:
| Check | Rule |
|---|---|
| Trigger | A keyword, or a file of a listed MIME type (neither configured: all messages) |
| Cooldown | cooldownSeconds since the observer's last reply in the channel |
| Daily count | maxDailyResponses per UTC day |
| Daily cost | maxCostPerDay per UTC day |
Observers support AGENT targets only. Decisions are counted in eddi_channel_observe_decisions_total{reason}. See docs/slack-integration.md.
🐧 UBI 10 & Red Hat Certified Catalog
- The production image now uses
ubi10/openjdk-25-runtime. It scans clean at every severity (UBI 9: 10 HIGH, 223 MEDIUM/LOW), is ~19 MB smaller, and ships the same JDK build. See Breaking Changes for the two platform constraints. - Stable releases are also published to Red Hat's certified catalog (
registry.connect.redhat.com) as a retag of the released image, with the same digest.
📊 Observability & Developer Experience
- 📈 Full Metrics Reference dashboard in Grafana with a panel for every registered meter, enforced by a coverage test. Two existing panels that could never display data are fixed.
- 🧾
docs/configuration-reference.mddocuments everyeddi.*property, 61 of which were previously undocumented. - 🚶 The developer quickstart is rewritten and verified end to end, and every documentation page was checked against the source.
- 🦙
docker-compose.ollama.ymlruns Ollama 0.34.0 alongside EDDI with the base URL pre-filled. Ollama also gainsthinkandreturnThinking. - 🗺️ Clearer API errors. Malformed configuration bodies report the failing JSON path, validation messages reach the client, and missing conversations or agents return 404 instead of 500 or 202.
⚠️ Breaking Changes & Upgrade Notes
| Change | Impact |
|---|---|
🚪 Unauthenticated /mcp and /secretstore require an opt-in |
A production boot with authorization.enabled=false now fails unless EDDI_MCP_ALLOW_UNAUTHENTICATED=true and EDDI_SECRETSTORE_ALLOW_UNAUTHENTICATED=true are set. The shipped compose files, Kubernetes manifests and Helm chart set them. Dev and test profiles are exempt. |
| 🖥️ UBI 10 platform requirements | The host CPU must support x86-64-v3 (Intel Haswell / AMD Excavator or newer). Static-RSA TLS 1.2 cipher suites are disabled, so endpoints that offer only non-forward-secret suites can no longer be reached. Red Hat does not support UBI 10 images on RHEL 8 hosts. |
| 🔑 Discovery endpoints moved to POST | discover-tools takes the key in an X-Mcp-Authorization header. discover-endpoints takes authHeaderRef, which must be a ${vault:…}, ${vars:…} or ${caller:…} reference. The GET forms remain for public specs and return 400 if a credential parameter is present. |
| 📈 Metric renames | eddi.tool.costs.total is now eddi.tool.costs.accrued. Its name collided with the eddi.tool.costs counter, which caused priced tool calls to fail in production. The dream.* and summarization.* meters gained the eddi. prefix. eddi.tenant.quota.denied is now exposed only with tenant and type labels. |
| 🏷️ Tool results are wrapped and screened | Results arrive in a provenance envelope, and directive-shaped text inside them is redacted by default. Adjust per LLM task with toolResultGuardrails. |
| ☸️ Kubernetes & Helm | k8s/base/eddi-secret.yaml is now an example that is not applied; create the secret with k8s/create-secrets.sh. The Keycloak component reads its admin password from an operator-created keycloak-admin Secret with no default. Helm chart 2.0.0 removes the unused manager, monitoring and namespace values. |
🐳 docker-compose.postgres.yml removed |
Use docker-compose.postgres-only.yml. |
📜 Rule groups serialize as behaviorRules |
Reads previously returned rules. Both names are accepted on write. |
| 📤 GDPR partial results | Export returns 207 (previously 206) with complete and omittedCategories. Erasure returns 207 with per-step outcomes when a step fails. |
| 🌍 Stricter request validation | An unknown environment returns 400 instead of targeting production. Input above eddi.conversations.max-input-chars (default 200,000) returns 413 input_too_large. TRACE and TRACK return 405. |
| 🗑️ Deletes, import and A2A | Deleting an agent undeploys its live versions. Cascade deletes skip resources still referenced elsewhere (X-Cascade-Skipped). strategy=upgrade requires targetAgentId. Exported archives are removed after eddi.backup.export.retention-minutes (default 60). A2A task requests are refused for agents without a2aEnabled. |
| 🛠️ Build (contributors) | ./mvnw compile now fails on unused imports and unformatted sources instead of rewriting files; run ./mvnw formatter:format. |
🔒 Security
The following fixes apply to code that shipped in 6.3.0 or earlier.
Kubernetes quick start could replace the vault master key
k8s/base applied eddi-secret.yaml as a live resource, so each kubectl apply -k re-applied the repository's placeholder key. A new installation ran with a publicly known key, and a later apply could replace a real key, leaving previously sealed secrets unreadable. The manifest is no longer applied, and the secret scripts no longer replace an existing secret without --force.
Secret-scoped input retained in parser data and the audit ledger
Input captured with scope: "secret" was removed from the property itself but remained in parser output and in audit entries recorded earlier in the same turn. Derived copies are now discarded, and audit entries are buffered and redacted before they are written.
Cloud metadata endpoints reachable from configured calls
With SSRF protection disabled (the default), HTTP call, MCP and A2A targets could address cloud metadata services. These addresses are now refused regardless of that setting, including via DNS resolution and redirects.
Credentials in logs, URLs and exports
- Console output was not redacted; only the in-memory log buffer was. Records are now redacted before reaching any handler, including parameters and exception chains.
- The discovery endpoints accepted credentials as query parameters (see Breaking Changes).
- Export scrubbing did not examine values inside arrays, unconventional header names such as
X-Api-Token, or credentials embedded in URLs and connection strings.
Additional fixes
/mcpand/secretstorerelied on role checks that are inactive when authorization is disabled; they now require an explicit opt-in (see Breaking Changes).- An unresolvable
${vault:…}reference now fails closed instead of being sent to the provider as the API key. - Agent-sync source URLs now block IPv6 unique-local, CGNAT and multicast addresses.
- Uploaded images and PDFs whose content does not match the declared type are rejected.
- DEK rotation is additive and resumable, so a partially completed rotation leaves every secret readable.
- The Keycloak development realm requires TLS for external clients and ships no default administrator password.
- GDPR erasure logs a pseudonym instead of the erased user id.
- Release tags are fully validated in CI before use in workflow scripts.
- Base image vulnerabilities in curl and sqlite are resolved.
🐛 Bug Fixes
- 🔌 Platform Operator, agent wizard and setup API on Keycloak deployments. EDDI's internal REST calls sent no credentials, so these features returned 401 when
authorization.enabled=true. The caller's token is now forwarded to the local process only. - 📒 Audit ledger. Verification reported every entry as invalid, because signed timestamps and payloads did not match what the database stores. A new signature format fixes this, and existing entries are verified through a bounded recovery search rather than re-signed. PostgreSQL now stores agent signatures and no longer loses a batch when an entry has no agent version.
- ⏰ Scheduling. PostgreSQL did not persist a schedule's message and several other fields, so scheduled turns ran with empty input. Failed fires were recorded as successful, persistent fires could run twice concurrently, heartbeats drifted, and startup and maintenance jobs ran minutes or hours later than intended.
- 🔁 Backup & agent sync. Granular sync applied no changes while reporting success. Schedules were exported but not imported, v5 archives imported nothing, and v5 stored workflows loaded with no steps.
- 🗑️ Configuration deletes and listings. The orphan purge could delete configuration still referenced at an older version, cascade deletes could remove shared workflows, and rule set, API call and dictionary listings were always empty.
- 🧠 Conversations & memory. Turns that paused for approval were missing from the conversation log and the model's history. Redo and rerun lost the restored or regenerated answer. Persistent memory tools were unavailable after the first turn. Missing template properties rendered as
NOT_FOUND. - 👥 Groups. Shared artifacts were not visible to other members,
create_sub_agentdid not inherit the parent's API key, and invalid group configurations are now rejected at save time. - ⚙️ Configuration & integrations. The MCP server root path and TLS compliance check read the wrong property keys, a failed properties migration could strand user data, A2A and Slack timeouts are now configurable, and the installers resolve port conflicts before starting containers.
🧪 Testing
20,500+ tests. The 90% instruction / 80% branch coverage gate now evaluates combined unit and integration coverage in CI. New structural test suites cover the Kubernetes and Helm manifests, documentation accuracy, configuration and metrics coverage, and CI workflow safety.
📦 Dependency Updates
| Dependency | 6.3.0 | 6.4.0 |
|---|---|---|
| ☕ Quarkus Platform | 3.38.3 | 3.39.3 |
| 🧠 langchain4j | 1.19.0 | 1.20.0 |
| 🧪 langchain4j-beta / community | 1.19.0-beta29 | 1.20.0-beta30 |
| 🔌 quarkus-mcp-server | 1.13.1 | 1.13.2 |
| 🔎 classgraph | 4.8.192 | 4.8.194 |
| 🥣 jsoup | 1.23.1 | 1.23.2 |
| 🍃 bson4jackson | 2.15.1 | 2.18.0 |
| 📏 json-schema-validator | 1.5.4 | 1.5.9 |
| 📗 jackson-dataformat-csv / -xml | platform-managed | 2.22.2 (pinned) |
| 🧹 quarkus-keycloak-authorization, jakarta.transaction-api | present | removed |
| 🐧 Base image | ubi9/openjdk-25-runtime:1.24 |
ubi10/openjdk-25-runtime:1.24 (digest-pinned) |
| ⎈ Helm chart | 1.0.1 | 2.0.0 |
🖥️ EDDI Manager (Admin Dashboard)
The Manager UI ships bundled with EDDI 6.4.0, with 440+ test files across 11 locales.
- 👥 Workspaces: space switcher, share dialog for any resource type, ownership badges, actions limited to the caller's access level, and administrator ownership transfer. The UI is unchanged against backends without workspaces.
- 🔌 Connections: list, creation wizard and detail editor, a linked-accounts panel for OAuth, and support for caller-supplied credentials.
- 💬 Groups & Workforce: votes, bids, negotiations, retros and decisions render as structured cards rather than raw JSON on every surface. Destructive actions now ask for confirmation, and several layout and crash issues on finished discussions are fixed.
- 🎨 Studio & chat: resizable Agent Studio panels with keyboard and RTL support, scrolling while a reply streams, and a rules editor that shows existing rules.
- 🔐 API alignment: discovery credentials are no longer sent in URLs, and partial GDPR operations and syncs are reported as such.
📋 Full Changelog
6.3.0...6.4.0. See docs/changelog.md and the monthly archives under docs/changelog/ for per-change detail and design decisions.
🐳 Docker Image
docker pull labsai/eddi:6.4.0Also available from Red Hat's certified catalog via registry.connect.redhat.com. From 6.4.0 the image requires an x86-64-v3 CPU.
Verify image signature
cosign verify \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
--certificate-identity-regexp '^https://github\.com/labsai/EDDI/\.github/workflows/ci\.yml@refs/(heads/main|tags/.+)$' \
labsai/eddi:6.4.0Note: EDDI is distributed exclusively as a Docker image. There are no binary downloads.
See the Docker Hub page for all available tags.
What's Changed
- fix(ci): bump preflight 1.17.1 -> 1.20.0, Red Hat rejects the old client by @ginccc in #705
- fix: address defects from the run-0820a by @ginccc in #707
- chore(ci): persist project metrics to a
metricsbranch by @ginccc in #708 - feat(ci): publish releases to Red Hat hosted registry alongside docker.io by @ginccc in #706
- fix(secrets): make DEK rotation survivable by giving the key generations by @ginccc in #709
- feat(security): close the outbound exposure gap and govern remote text by surface by @ginccc in #710
- feat(connections): one credential model for every outbound call by @ginccc in #711
- fix(api,docs): correct the quickstart, and the API behaviours it exposed by @ginccc in #717
- fix(tenancy): the quota counters were tested against the wall clock by @ginccc in #719
- Feature/grafana full metrics dashboard by @rolandpickl in #721
- feat(connections): a credential the caller hands over, so an agent cannot exceed its user's permissions by @ginccc in #718
- chore(ci): allowlist four gitleaks findings in the connector test fixtures by @ginccc in #713
- docs: repository-wide accuracy audit, with tests to keep it accurate by @ginccc in #722
- feat(security): per-user workspaces and resource sharing by @ginccc in #723
- fix(connections): reject code_verifier in extraAuthParams whatever its spelling by @ginccc in #720
- fix(review): close an SSRF gap, and two tests that passed for the wrong reason by @ginccc in #725
- refactor(style): make ImportStyleTest enforce the rule it documents by @ginccc in #726
- chore(deps): bump the quarkus group across 1 directory with 2 updates by @dependabot[bot] in #728
- fix(configs): keep v5 stored configurations loadable by @ginccc in #730
- fix(configs): make destructive configuration deletes safe, atomic and honest by @ginccc in #733
- fix(backup): repair agent export, import and sync by @ginccc in #731
- fix(schedule): correct fire bookkeeping, persistence and manual-fire claiming by @ginccc in #732
- fix(audit,gdpr,tenancy): repair ledger persistence, erasure reporting and quota windows by @ginccc in #734
- fix(deploy): repair Keycloak realm and secret generator, add manifest regression tests by @ginccc in #735
- fix(build): make the style, coverage and image gates able to fail by @ginccc in #736
- fix(docker): bump UBI9 base digest and repair the weekly base-image check by @ginccc in #737
- chore(docker): move the production base image to UBI 10 by @ginccc in #738
- test(caching): de-flake the cache-wide TTL expiry test by @ginccc in #739
- docs: add Javadoc to config store interfaces #545 by @AzazelSensei in #740
- fix(install): resolve every host port the compose files publish before docker refuses the bind by @ginccc in #714
- docs: correct 23 false documentation claims and pin them with a guard test by @ginccc in #745
- fix(config): repair fifteen configuration defects across scheduling, TLS, Slack and assets by @ginccc in #747
- feat(channels): implement observe mode for passive channel watching by @ginccc in #748
- chore(docker): bumped ollama version to 0.34.0 by @toporek3112 in #749
- docs(configs): add class Javadoc to config store interfaces by @alorentiar in #746
- fix: address the defects found by the 6.4.0 end-to-end run by @ginccc in #750
- fix(connections): address the connections code review — credentials, OAuth, validation, export by @ginccc in #751
- chore(release): EDDI 6.4.0, Quarkus 3.39.3, and every safe patch/minor by @ginccc in #727
New Contributors
- @AzazelSensei made their first contribution in #740
- @toporek3112 made their first contribution in #749
- @alorentiar made their first contribution in #746
Full Changelog: 6.3.0...6.4.0