Skip to content

6.4.0

Latest

Choose a tag to compare

@github-actions github-actions released this 15 Sep 08:24
· 2452 commits to main since this release
b2310a6

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=shared keeps 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, and GET /workspaces reports 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_USER requires 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.md documents every eddi.* 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.yml runs Ollama 0.34.0 alongside EDDI with the base URL pre-filled. Ollama also gains think and returnThinking.
  • 🗺️ 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

  • /mcp and /secretstore relied 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_agent did 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.0

Also 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.0

Note: 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 metrics branch 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

Full Changelog: 6.3.0...6.4.0