Upgrading from EDDI 6.4
This guide is for an existing EDDI 6.4.x deployment that you want to run on 6.5. There is no migration to run by hand: the stores add their new indexes, collections and columns when 6.5 starts. What needs you is configuration. Several defaults now fail closed, a few identities are namespaced, and the shipped Keycloak realm and Helm chart changed in ways an existing install doesn't pick up by itself.
Read section 2 before you upgrade. Each item there can take a working 6.4 deployment down: every request answers 401 or 403, the pod doesn't start, or helm upgrade refuses to render. The later sections cover changes that a client or an agent author notices.
If this database first came from EDDI 5, also read section 9. For a 5.x database that has never run on 6.x, start with Upgrading from 5.x instead. Everything in this guide applies to it too.
1. Before you start
Take a backup (
mongodump, orpg_dumpfor PostgreSQL). 6.5 rebuilds an index on the deployment rows and adds collections, tables and columns at startup (section 8).Stop every 6.4 instance, then start 6.5. Don't run a rolling upgrade with both versions against one database, and don't plan on rolling back to 6.4 once 6.5 has served traffic:
a group discussion a human rejects is now stored as
REJECTED, a value 6.4 cannot read;conversation documents gain revision fields that 6.4 doesn't maintain, so a 6.4 write can silently undo a 6.5 write;
the deployment-row index is dropped and rebuilt when 6.5 starts.
To roll back, restore the backup.
Rehearse on a copy if you can: restore the backup into a throwaway database, boot 6.5 against it with your production configuration, and work through section 10.
Resolve pending Slack approvals (HITL) before you stop 6.4, or plan to decide them in the Manager or over REST. Buttons on cards posted by 6.4 stop working (section 2.7).
2. Changes that break a working deployment
2.1 A Keycloak realm imported from EDDI 6.1–6.4 answers 401 on every request
What changed. A token that names no user is now refused with 401. EDDI reads the user from quarkus.oidc.token.principal-claim or, when that is unset, from upn, preferred_username or sub. Up to 6.4 such a token was let in, and its conversations got a random anonymous-<hex> owner.
Who is affected. Every deployment whose Keycloak realm was imported from the realm file shipped with EDDI 6.1.0 to 6.4.0 (for Helm and Kustomize, 6.4.0 only). That file defined none of the basic, profile and email client scopes, so its tokens carry no sub and no preferred_username. Keycloak imports a realm only on its first boot, so the corrected realm file in 6.5 doesn't repair an existing realm. Without the repair, every request answers 401 after the upgrade. Each refusal logs a [SECURITY] warning, at most once every five minutes.
What to do. Repair the realm before you upgrade. The script is in Identity claims, and realms imported from EDDI 6.1.0–6.4.0. It creates the missing client scopes and attaches them to eddi-frontend. install.sh does the same when you re-run it. eddi update does not. With another identity provider, point QUARKUS_OIDC_TOKEN_PRINCIPAL_CLAIM at a claim its tokens do carry. Users sign in again afterwards. Conversations and memories written under the broken realm keep their anonymous-<hex> owner and are not re-attributed.
2.2 The token audience is enforced
What changed. quarkus.oidc.token.audience=eddi-backend is now set. 6.4 accepted a token minted for any client of the realm. A token without eddi-backend in its aud claim now gets 401.
Who is affected. Realms and identity providers whose clients don't add that audience: hand-built realms, other IdPs, service accounts, and tokens used for /v1 or MCP in OIDC mode. The realm EDDI ships has an eddi-backend-audience mapper on eddi-frontend (and on the new eddi-mcp client), so an unmodified shipped realm is not affected.
What to do. Give every client whose tokens EDDI must accept an audience mapper (oidc-audience-mapper with included.client.audience=eddi-backend). That is the fix, and EDDI should accept only tokens that carry eddi-backend in aud.
QUARKUS_OIDC_TOKEN_AUDIENCE=any turns the check off again (see security.md), but don't treat it as a fallback. EDDI reads roles from realm_access/roles, which doesn't depend on the client. With the check off, a token the realm issues to any of its clients on a user's behalf carries that user's full EDDI rights. That is the 6.4 exposure this release closes. Use it at most as a stopgap while the mappers are being added, and remove it afterwards.
2.3 Roles are read from realm_access/roles
What changed. 6.5 sets quarkus.oidc.roles.role-claim-path=realm_access/roles. The 6.4 image didn't set it, so Quarkus read roles from the groups claim whenever a token had one. That was a 6.4 bug: a user in any Keycloak group lost every EDDI role and got 403 everywhere, the seeded eddi administrator included.
Who is affected.
Keycloak users who belong to a group: the upgrade fixes them.
Deployments whose IdP delivers the EDDI roles in
groupsor another claim: those users lose their roles and get403.
What to do. In the second case, set QUARKUS_OIDC_ROLES_ROLE_CLAIM_PATH to the claim that carries the roles. It must not be the claim eddi.workspaces.groups-claim names (groups by default), which EDDI reads workspace membership from. If the two collide, or the path is missing, EDDI logs an ERROR at startup that names the problem. A 6.4 workaround of QUARKUS_OIDC_ROLES_ROLE_CLAIM_PATH=realm_access/roles can stay or go.
2.4 Helm with the in-chart MongoDB: the upgrade refuses to render
What changed. The chart's MongoDB now runs with authentication, and mongodb.rootPassword is required when mongodb.enabled=true. The mongo image creates the root user only on an empty volume, so a plain upgrade would leave MongoDB demanding a user that doesn't exist. A live helm upgrade detects a MongoDB that predates authentication and refuses to render. The credentialed connection string moves into the chart's Secret.
Who is affected. Helm releases with mongodb.enabled=true.
What to do. Create the root user in the still-unauthenticated database, then upgrade with the same password and mongodb.authMigrated=true. The render error prints the exact kubectl exec … mongosh command for your release, and Upgrading to an authenticated MongoDB has the full procedure, including the extra values an OIDC-off release needs.
The Kustomize mongodb overlay also runs MongoDB with authentication now, and has no render guard. Create the user in the running database first, then the mongodb-secrets Secret with the same password, then re-apply. The commands are in the header of k8s/overlays/mongodb/kustomization.yaml. Applied in the other order, MongoDB restarts with authentication and no user, and EDDI can't log in until you create one. The PostgreSQL overlay no longer ships a postgres-secrets Secret: an existing install keeps the one it has, because kubectl apply -k doesn't prune.
2.5 A weak vault master key stops a production boot
What changed. In production, EDDI refuses to start when EDDI_VAULT_MASTER_KEY is shorter than 16 characters, has fewer than 6 distinct characters, or is a known placeholder (changeme, password, secret, the demo key, …). Dev and test modes only warn. rotate-kek also refuses to rotate to a weak key.
Who is affected. Deployments with a weak master key.
What to do. Boot once with EDDI_VAULT_ALLOW_WEAK_MASTER_KEY=true, rotate with POST /secretstore/secrets/admin/rotate-kek (eddi-admin, over TLS, as described in KEK rotation), set the new EDDI_VAULT_MASTER_KEY, remove the flag and restart. Losing the master key makes every stored secret unrecoverable, so store the new one before you restart.
2.6 Credentials in HTTP calls must come from the configuration
What changed. An HTTP call resolves a credential reference (${vault:…}, ${eddivault:…}, ${connection:…}, ${caller:…}) only where its configuration wrote it: in the template of that URL, header, body or query parameter. A reference that reaches the request through conversation data (user input, a model reply, an API response, client context, or a {properties.x} that holds one) now refuses the call. The one exception is a property EDDI itself auto-vaulted: it carries a new autoVaulted marker, and only then is its ${vault:…} value resolved. The same rule applies to the parameters of an LLM task, a cascade step and a cascade judge. Two related changes:
A reference that can't be resolved (the secret doesn't exist, or the vault is off) now fails the call. 6.4 sent the literal
${vault:name}as the credential.A variable (
${vars:…}) whose value holds a credential reference is checked the same way.
Who is affected. Agents that pass a credential to an HTTP call through a property, context or model output. Conversations that auto-vaulted a secret under 6.4 (scope: "secret") and make the call after the upgrade: the 6.4 property carries no marker, so the call is refused.
What to do. Write credential references directly into the HTTP call's template. For an in-flight secret-scoped conversation, the user enters the secret again or starts a new conversation. See Where vault references work.
2.7 Slack users get a new identity
What changed. A Slack user is now slack:<team_id>:<user_id> in EDDI, not the raw Slack user id. The team comes only from the integration's platformConfig.teamId; without it the id is slack:<user_id>. Also:
An event whose team disagrees with the declared
teamIdgets403./integrations/slack/eventsrequires the signature of the integration that owns the channel.Channel-integration names must be unique per channel type. Updating one of two integrations with the same name fails validation, and a name may not contain
|.Approval buttons carry a card id, recorded in the new
slack_hitl_approval_recordsstore. A button without one, on a card posted by 6.4, is refused.
Who is affected. Every Slack deployment. Ongoing threads keep their conversation, and that conversation keeps its memories. A new conversation does not see long-term memories stored under the raw Slack id, and nothing moves them.
What to do.
Declare
platformConfig.teamIdon every Slack integration.Rename integrations whose names collide or contain
|.Decide pending Slack approvals before the upgrade, or afterwards in the Manager or over REST.
Decide whether memories stored under raw ids need to be moved by hand. For GDPR requests, address both ids until the old conversations have ended.
See Slack user identity.
2.8 /v1 users from the Open WebUI header get a new identity
What changed. On the OpenAI-compatible API, the user from X-OpenWebUI-User-Id: 42 is now openwebui:42. OIDC principals are unchanged, but a principal that itself starts with openwebui: gets 401.
Who is affected. Open WebUI and other /v1 clients that identify users through the header. Each existing chat starts a new EDDI conversation on its next message, without the long-term memories of the old one.
What to do. If /v1 never ran with http-policy=authenticated, set EDDI_OPENAI_COMPAT_ADOPT_LEGACY_HEADER_MAPPINGS=true to keep the existing chats on their conversations. Leave it off otherwise. GDPR requests for an adopted user must cover both openwebui:<id> and the raw <id>. See Identity resolution.
2.9 Templates: fetched data is no longer a template, and the engine is restricted
What changed.
A value read through
fromObjectPathis stored as found. This covers the property setter and thepostResponseproperty instructions of HTTP calls. An upstream valueHello {properties.name}used to be stored with the substitution applied; now the property holds that literal text. OnlyvalueStringis a template.Output from context or from
postResponseoutput-building is delivered verbatim: the context keys starting withoutputorquickReplies. Rendered output is also final, so a secondai.labs.templatingstep in a later workflow doesn't render it again.Runtime templates run in a restricted Qute engine.
Namespaces: only
vault,eddivault,connection,vars,caller,uuidUtils,json,encoder,strandtime.config:,inject:andcdi:render empty, andstr:evalis gone.Sections: only
if,for/each,let/set,withandwhen/switch. Any other, such as{#include}or{#eval}, fails the render.Only properties are read from objects (getters, record components, public fields). No method with arguments is called, apart from EDDI's string helpers such as
substring,replaceandtoLowerCase.A missing value always renders empty.
New bounds:
eddi.templating.max-output-chars(2,000,000) andeddi.templating.max-iterations(100,000).0disables either.
Who is affected. Configurations that relied on templates inside fetched data, on double rendering across workflows, or on the namespaces, sections and method calls listed above. None of this was documented behaviour.
What to do. Move templates into valueString or the output configuration. Check the templates you rely on with POST /administration/preview/template. See What a template can reach and Runtime template engine.
2.10 Docker Compose publishes on loopback
What changed. docker-compose.yml and docker-compose.postgres-only.yml publish EDDI's ports on 127.0.0.1 unless a bind-address variable says otherwise, and docker-compose.auth.yml does the same for Keycloak (${KEYCLOAK_BIND:-127.0.0.1}). Keycloak's bootstrap admin still defaults to admin/admin (KC_BOOTSTRAP_ADMIN_USERNAME, KC_BOOTSTRAP_ADMIN_PASSWORD).
Who is affected. Compose deployments reached from other hosts, or through a proxy on another host.
What to do. Set EDDI's bind address to 0.0.0.0 in .env (the comment above the ports: entry in docker-compose.yml names the variable), and KEYCLOAK_BIND for Keycloak. Keep authentication on, and change the Keycloak admin password first.
3. Authentication and OIDC: other changes
Token cache.
quarkus.oidc.token-cache.*is new (1,000 entries, TTL3M). Signature, expiry and audience are still checked on every request. What the cache defers is Keycloak's userinfo call, which is also its session-revocation check, so a logged-out session keeps working for up to three minutes. LowerQUARKUS_OIDC_TOKEN_CACHE_TIME_TO_LIVEif that matters./mcpis advertised as an OAuth protected resource (RFC 9728), so MCP clients can sign themselves in. The metadata document is served anonymously at/.well-known/oauth-protected-resource/mcp.On plain HTTP with authentication on, set
QUARKUS_OIDC_RESOURCE_METADATA_FORCE_HTTPS_SCHEME=false.The advertised authorization server is
quarkus.oidc.token.issuer, falling back toauth-server-url, which is usually an internal address. SetQUARKUS_OIDC_TOKEN_ISSUER(orQUARKUS_OIDC_RESOURCE_METADATA_AUTHORIZATION_SERVER) to the public issuer.Behind a proxy that doesn't pin
Host, setQUARKUS_OIDC_RESOURCE_METADATA_RESOURCEto the absolute public URL of/mcp.In Helm, these are
eddi.oidc.resourceMetadata.forceHttpsScheme(defaults to whetheringress.tlsis set),.authorizationServerand.resource.
The shipped realm has an
eddi-mcpclient, and theeddiadministrator also holdseddi-viewer. An existing realm is not re-imported. Only if MCP clients should sign in through Keycloak: createeddi-mcpas described in MCP server and security, and granteddi-viewerto administrators who use MCP. There is no role hierarchy: an administrator withouteddi-vieweris refused the read tools over MCP. (Releases after 6.5.0 admiteddi-adminandeddi-editorto most MCP read tools directly — see Upgrading from 6.5.)The shipped realm seeds no passwords and disables the password grant. New installs only: the
vieweranduserfixtures no longer ship asviewer/vieweranduser/user, andeddi-frontendno longer allows the direct-access (password) grant. An existing realm keeps what it has.A2A. The agent cards (
/.well-known/agent.json,/a2a/agents/{agentId}/agent.json) and/.well-known/capabilitiesare now anonymous. They answered401under OIDC in 6.4.GET /a2a/agentsstill needs a signed-in user.POST /a2a/agents/{agentId}(tasks/send) now needseddi-admin,eddi-editororeddi-user. The Agent Card's token endpoint comes fromeddi.keycloak.public.url; with another IdP, setEDDI_A2A_PUBLIC_TOKEN_ENDPOINT.
4. Other things that now fail closed or are refused
${caller:token} is also released to EDDI's own address: eddi.self.base-url, else http://127.0.0.1:${quarkus.http.port}. That address bypasses any reverse proxy.
Deployments that rely on proxy path rules to limit what agents may call on EDDI itself.
Set EDDI_CALLER_IDENTITY_SELF_RELEASE_ENABLED=false, or set EDDI_SELF_BASE_URL. See Calling as the signed-in user.
Engine-reserved context keys are dropped from client input: groupId, groupConversationId, groupDepth, groupTranscript, dynamicAgentConfig, dynamicCreatedAgentIds, delegationDepth, and any key that starts with one of them (groupIdLabel, for example). Removed silently, at REST conversation start and turn and at the MCP managed trigger. {context.groupId} renders empty for a client-started turn, and a groupId property no longer selects the group memory scope.
Clients that set these keys, or keys that start with them.
Rename your own keys. Only for trusted callers, permit them with EDDI_CONVERSATION_CLIENT_CONTEXT_PERMITTED_RESERVED_KEYS=<comma list>.
New role requirements. GET /conversationstore/conversations/active/{agentId} and POST /conversationstore/conversations/end need eddi-admin or eddi-editor, and so does /parser/*. Schedules are owner-scoped: a non-admin sees and changes only their own. A global variable whose value holds a ${vault:…}, ${eddivault:…} or ${connection:…} reference needs eddi-admin.
Users with only eddi-user, and scripts that call these endpoints.
Grant the role, or move the script to an editor or admin account.
User-memory writes are validated (PUT /usermemorystore/memories and the MCP memory tools). visibility is required (a missing one used to cause a 500); self and group need sourceAgentId, and group needs groupIds. Only the known categories are accepted. Keys are limited to 255 characters and values to 65,536 characters. _gdpr_* keys are refused with 400.
Clients that write user memories directly.
Send visibility and the ids its scope needs. See Write validation.
The model can write only self memories unless you allow more. The memory-tool guardrails gain allowedVisibilities (default ["self"], plus the configured defaultVisibility) and allowGlobalKeyOverwrite (default false). A model can no longer overwrite a global key another agent owns, or one with no recorded owner, which includes migrated data.
Agents with enableMemoryTools whose model is meant to write group or global memories.
Set both fields in the agent's userMemoryConfig.guardrails. See Guardrails.
Property visibility is honoured. An unknown visibility on a property instruction now fails the configuration; it used to become global. A long-term property without its own visibility gets userMemoryConfig.defaultVisibility, whether or not memory tools are enabled.
Rule-based agents that share long-term properties across agents and have a defaultVisibility other than global: other agents stop seeing those properties.
Set visibility on the properties that other agents need.
scope: "secret" works on every path, or fails the turn. Secret-scoped values from HTTP-call, MCP and LLM pre- and post-instructions used to be stored in plaintext; they are now vaulted, and without EDDI_VAULT_MASTER_KEY the turn fails. Saving a secret-scoped valueObject, valueInt or other non-string value, or a name containing /, {, } or $, is refused with 400.
Agents that use scope: "secret" outside the property setter, and deployments without a master key.
Set EDDI_VAULT_MASTER_KEY, or use scope: "conversation". See properties.
ZIP import limits: 10,000 entries, 32 MiB per entry and 256 MiB in total, uncompressed. Over a limit, every import, merge, upgrade and sync path answers 413. The request itself is also held to the body limit in section 6.
Very large agent archives.
Split the archive, or raise eddi.backup.import.max-entries, eddi.backup.import.max-entry-bytes or eddi.backup.import.max-uncompressed-bytes.
RAG ingestion crons are validated. A cron that can never fire (0 0 30 2 *) or that fires more often than eddi.schedule.min-interval-seconds (60) is refused when the knowledge base is saved, and on ZIP import. Six-field Quartz expressions are refused.
Knowledge bases with such a cron. A too-frequent one keeps firing until the knowledge base is next saved.
Use a five-field cron within the interval.
Agent Sync between instances refuses a source that is plain HTTP or a private address, with a 400 naming the setting (eddi.backup.sync.require-https, default true; eddi.backup.sync.allow-private-targets, default false).
Instances that sync from an internal or HTTP source.
List the source in eddi.backup.sync.allowed-sources. See Agent Sync.
5. Outbound HTTP
Test the integrations that depend on the first three points before you upgrade production.
HTTP calls no longer keep cookies between calls. The shared client used to be a cookie session; an API that relies on a session cookie set by an earlier call now sees none.
A request whose headers carry a resolved credential doesn't follow redirects (a
${vault:…},${connection:…}or${caller:…}reference). Credential headers are also stripped from any cross-origin redirect hop. Point credentialed calls at the final URL.More address ranges are blocked as private or internal: IPv4 embedded in IPv6 (mapped, NAT64, 6to4, Teredo), the NAT64 local-use prefix
64:ff9b:1::/48,198.18.0.0/15, and more cloud metadata endpoints such as168.63.129.16. An internal target in one of them is now refused. See SSRF protection.One deadline covers a whole exchange in the built-in tools' HTTP client, redirects and body included, so an endpoint that trickles its answer slowly now times out.
Size caps. An HTTP call's
maxLengthis enforced while the response streams in. New limits:eddi.tools.web-scraper.max-response-bytes(5 MiB),eddi.tools.pdf-reader.max-download-bytes(25 MiB) andeddi.attachments.extraction.max-pages(500).Fan-out cap. A fire-and-forget
batchRequestscall larger than itsmaxBatchSize(defaulteddi.httpcalls.batch.default-max-size, 100) fails the turn. Saving amaxBatchSizeaboveeddi.httpcalls.batch.max-size-ceiling(1,000) is refused with400.OpenAPI specs for MCP API tools may no longer use
file:or relative$refs.The web scraper tool returns Markdown instead of a plain-text dump.
6. Helm, Kubernetes, Compose and the image
Besides the MongoDB change in section 2.4:
Helm chart. 6.4.0 shipped chart 2.0.0; this release ships 2.3.0.
eddi.image.tagis empty by default, which means the chart'sappVersion. Seteddi.image.tag(and ideallyeddi.image.digest) to the release you are deploying. A chart taken from the repository names the previous image until the release's post-release update lands.With OIDC off,
eddi.security.allowUnauthenticatedMcpandeddi.security.allowUnauthenticatedSecretStoreno longer followeddi.oidc.enabled. Unset meansfalse, and the chart refuses to render until both are set.ingress.enabled=truewith OIDC off also needseddi.security.allowUnauthenticatedset explicitly. Enable OIDC or set the values.--reuse-valuescan't carry values the old chart never had, so put them on the upgrade command line.The startup probe checks
/q/health/liveinstead of readiness, so a long startup migration no longer gets the pod killed. Its limits areeddi.startupProbe.*.The EDDI and MongoDB pods no longer mount a service-account token (
serviceAccount.automountToken, defaultfalse). Set it totrueonly if you add something to the pod that calls the Kubernetes API.networkPolicy.enabled(defaultfalse) renders a policy for EDDI and isolates the in-chart datastores and Keycloak.New values:
eddi.chat.frameAncestors,eddi.metrics.httpPolicyandeddi.oidc.resourceMetadata.resource(section 3).
Kustomize.
The
k8s/baseDeployment names the placeholder imagelabsai/eddi:pinned-by-kustomization; the real tag comes fromimages:ink8s/base/kustomization.yaml. Always apply withkubectl apply -k, and setnewTagin your own overlay.The
mongodboverlay adds a NetworkPolicy that admits only EDDI pods to port 27017. Thepostgresoverlay adds the same for PostgreSQL. Allow backup or admin pods explicitly.The
monitoringoverlay needs agrafana-adminSecret, and Prometheus now uses a Role instead of a ClusterRole.kubectl apply -kdoesn't prune, so delete the old ClusterRole and ClusterRoleBinding by hand.As in the chart, pods no longer mount a service-account token, and the startup probe checks liveness.
See Kubernetes.
Image.
Application files belong to
root:0, so the process (UID 185) can write only under/deployments/tmpand/opt/eddi. Custom images or entrypoints that write elsewhere under/deploymentsneed changing.The image sets
JDK_JAVA_OPTIONS=--add-modules=jdk.incubator.vector, which the in-process Jlama provider needs. Append toJDK_JAVA_OPTIONSinstead of replacing it.
Request body size.
quarkus.http.limits.max-body-sizerises from25Mto60M, but only RAG file uploads can use it. Every other endpoint, ZIP import included, is held toeddi.http.limits.default-max-body-size(25M), raised automatically to fit an attachment ofeddi.attachments.max-size-bytes(about 27.7 MB by default). A larger body gets413.A chunked HTTP/1.1 body without
Content-Lengthgets411, unlesseddi.http.limits.refuse-unsized-bodies=false.Raise a proxy's body limit only if you want large RAG uploads.
Chat UI framing.
/chathas its own Content-Security-Policy, withframe-ancestorsset fromeddi.chat.frame-ancestors(default'none'). It couldn't be framed under 6.4 either. To embed it, seteddi.chat.frame-ancestors(EDDI_CHAT_FRAME_ANCESTORS; Helmeddi.chat.frameAncestors) to the embedding origins.Metrics.
/q/metricsstill needs authentication, now through its own setting,eddi.metrics.http-policy(Helmeddi.metrics.httpPolicy). Set it topermitonly where the path can't be reached from outside.Manager and Chat UI.
Both are now built from
ui/managerandui/chatin this repository and bundled into the image as before (/manage,/welcome,/workforce,/chat). The former separate repositories are archived, so stop deploying bundles built from them.The Chat UI now sends the route's environment, so
/chat/test/{agentId}really talks to thetestdeployment.It removes
?token=from the address bar after loading, and accepts only a same-origin path in?apiServer=.
Installers.
install.shandinstall.ps1 --with-authgenerate the Keycloak (and Grafana) admin passwords into.env. Plain Compose still defaults Keycloak toadmin/admin.
7. What a REST, MCP or SSE client notices
No REST path was removed between 6.4.0 and 6.5.
7.1 Responses and status codes
Failed turns say why. A turn in which a task failed carries a
taskErrorsentry in its output, in the default response and when streaming. Under 6.4 such a turn answered200withconversationState: "ERROR"and an output holding only actions.POST /conversationstore/conversations/endreads only conversation ids from the body and answers{ended, skipped, failed}:200when all ended,500if any end failed,400for a missing body. It accepts an optionalendReasonquery parameter; an unknown reason is a400. Undeploying with "end conversations" refuses to undeploy when an end failed.Group conversations.
A discussion a human rejects ends in the new terminal state
REJECTEDinstead ofFAILED. Thegroup_completeSSE event can carry it, and MCP clients that poll should treat it as terminal.New SSE events
cost_updatedandstance_updated;speaker_completegainsoutcome.Starting or continuing a group conversation needs USE access to the group. Non-admins page through their own group conversations only.
Agent visibility.
GET /administration/{environment}/deploymentstatusand the MCPlist_agentsanddiscover_agentstools list only the agents the caller may use.Memory reads show less. Detailed reads (
returnDetailed) dropaudit:*,*:trace:*and*Errorkeys and redact values under credential-named keys (apiKey,token,secret,password,authorization). A turn flagged as secret input reads back as<secret input>, older stored turns included. The stored documents are not rewritten.HITL. MCP
get_approval_statuswithdetail=fullno longer returnsargumentsRaw,chatTranscriptJson,traceSoFarorgatingAssistantMessageJson. A resume may carrypauseId; if the pause has changed, the answer is409 PAUSE_CHANGED. A turn resumed by someone other than the conversation owner runs without a caller identity, apart from the tool calls that person approved, so a later${caller:token}call fails.404instead of500for unknown ids on undo, redo,PATCH /agents/{agentId}/stateand/llm/tools/history/{id}. The managed-agent POST answers401instead of500for an anonymous caller.Schedules.
POST …/{id}/dismissanswers409unless the schedule is dead-lettered, and aPUTon a RAG-ingestion schedule answers409.Export downloads carry a token in their key (
<slug>--<id>-<version>-<token>.zip), so an export link made before the upgrade no longer downloads. Export again./v1model ids. Agents whose slugs collide are listed as<slug>-<agentId>.Audit ledger. New entries are signed in a new format (
v5:<keyId>:<hex>), and verification can reportUNKNOWN_KEY. An external verifier has to understand the new format. New optional settings:eddi.audit.hmac-keyandeddi.audit.hmac-previous-keys.MCP transport. The MCP server library moved to a new major version with a stricter
Origincheck against DNS rebinding. Retest MCP clients that send an unusualOrigin.
7.2 Agents and conversations
Conversations can follow a new agent version. A version saved with
compatible=true(PUT /agentstore/agents/{agentId}?compatible=true, MCPapply_agent_changes) lets running conversations move to it on their next turn. The default isfalse, and versions saved before 6.5 carry no compatibility marker, so the upgrade itself moves no conversation. Undeploying no longer ends conversations that can move to a compatible version. See Running conversations and new agent versions.Failed deployments are retried with a growing delay. Readiness stays UP and reports
agentsInErrorCount.NEGOTIATION groups get their arbitration prompt back. A stored NEGOTIATION group whose Arbitration phase had no prompt used the generic synthesis prompt in the backend. The moderator then summarised the deadlock instead of deciding it, and that summary was recorded as the verdict. Only the Manager repaired such a group, and only on its next save. 6.5 restores the arbitration prompt at run time and on save, so the outcome of those negotiations changes: a real verdict instead of a summary.
Soft delete ends the conversation. Ended, soft-deleted conversations are purged by the retention sweep (
eddi.conversations.deleteEndedConversationsOnceOlderThanDays, 365) counted from their last interaction.Undo reverts property changes too, and a new turn clears the redo stack. In recall and summaries the greeting is turn 0 and the first user message turn 1.
Tool results from HTTP, MCP and A2A tools are no longer cached unless the LLM task names the tool in
toolCacheScopes. Built-in tools are cached as before. See Tool cache scoping.RAG context is wrapped in a provenance envelope before it reaches the model (
markRagProvenance, defaulttrue). Aeddi://ai.labs.ragworkflow step now works, and a broken knowledge-base URI in it fails the deployment. Ingestion sources whose cron was never armed are armed at startup (eddi.rag.ingestion.schedule-repair.enabled, defaulttrue), so expect first runs, and their embedding cost, soon after boot.JSON mode falls back to plain output only on errors that can't be retried. A gateway that answers a JSON response format with a
5xxnow fails the turn. SetjsonResponseFormat: "off"for it.Gemini defaults
returnThinkingandsendThinkingtotrue, which Gemini 3.x needs for tool calls.includeFirstAgentMessageis deprecated.falsenow drops the first history message only when it is the agent's. An agent without a greeting keeps the user's first message.New LLM types for OpenAI-compatible providers (
xai,deepseek,moonshot,qwen,zhipu,minimax,openrouter,groq). Existingopenaiconfigurations with abaseUrlkeep working. See LLM configuration.LLM metrics. Every model call is measured:
eddi.llm.request.duration(per provider, model and outcome),eddi.llm.tokensandeddi.llm.request.errors. Watch the cardinality in Prometheus.Log lines escape CR and LF on the console, so a multi-line exception message becomes one line. Alerting rules that match across lines need updating.
NATS. With
eddi.messaging.type=nats, the conversation stream is updated in place to bounded retention (eddi.nats.stream-max-age=1h,stream-max-messages=100000,stream-max-bytes=256MiB).Platform Operator. An operator activated under 6.4 may carry a
targetServerUrltaken from the browser. Reconfigure it; the Manager now pre-fills the address. Check that a 6.4 reconfiguration didn't leave two operators deployed.Jlama. Agents created by the 6.4 wizard with a bare model name never loaded; fix the model name. Put Jlama's
modelCachePathon a volume.
8. What changes in the database automatically
None of this needs a command. Two of the steps delete duplicate documents, which is why section 1 asks for a backup.
Duplicate user memories are merged at startup (MongoDB). Before building the new unique indexes
idx_um_upsert_globalandidx_um_upsert_agentonusermemories, EDDI merges entries that share an identity. The one with the newestupdatedAtsurvives and takes the summedaccessCount; the others are deleted, values included. The log saysMerged duplicate usermemories identities: <n> redundant entries removed. If the merge or the index fails, EDDI logs an ERROR and starts without the guarantee.Deployment rows (MongoDB). Every index on
(environment, agentId, agentVersion)is replaced by one partial unique index. If duplicate rows block it, the newest row is kept and the others are deleted. Boot is never blocked.Resource indexes (MongoDB). An index on a store's own key that an earlier EDDI built with a different specification is rebuilt: the new one first, then the old one dropped. Custom indexes on those keys are replaced.
Conversation documents gain
_revand_histRev. A document without them counts as revision 0, so nothing is rewritten. Concurrent undo, redo or resume can now answer409; the metriceddi_conversation_store_conflict_countcounts the conflicts.New collections and tables:
rag_ingestion_documents,rag_ingestion_runs, therag_ingested_filesGridFS bucket (a table on PostgreSQL),slack_hitl_approval_records, and principal indexes on connection grants (idx_grant_principalon MongoDB,idx_cg_principalon PostgreSQL). GridFS attachments gain metadata indexes.PostgreSQL runs
ALTER TABLE … ADD COLUMN IF NOT EXISTSon the ingestion and Slack approval tables, so the database user needs DDL rights.Vault. Secret checksums become keyed HMACs, and new secrets are sealed with additional authenticated data. Existing secrets keep working and are migrated on their next write. Two new refusals:
a vault salt that can't be read stops startup, where 6.4 fell back to the legacy salt;
when the vault already holds keys and the master key changes, new keys are refused. Run
rotate-kek, or, if the old master key is lost,POST /secretstore/secrets/admin/adopt-master-key?confirm=true. See Lost master key.
9. If this deployment was migrated from 5.x
The 5.x migration in 6.4.0 had defects that could leave it half done while recording it as complete. 6.5 fixes the migration, but it doesn't re-run it on a database whose migrationlog already records it. Check before you upgrade:
db.migrationlog.find()containsv6-rename-migration-completeandv6-qute-migration-complete.If
v6-qute…is missing, start 6.5 once withEDDI_MIGRATION_V6_QUTE_ENABLED=trueand read the per-document messages.If
v6-rename…is missing, follow Upgrading from 5.x.
Nothing v5 is left: no collections
bots,packages,behaviorrulesets,httpcalls,langchainorregulardictionaries, and no configs referencingeddi://ai.labs.bot/,ai.labs.package/,ai.labs.behavior/,ai.labs.httpcalls/,ai.labs.langchain/orai.labs.regulardictionary/.Every agent that should be deployed has a row in
deployments. 6.4.0's first boot could delete deployment rows. Redeploy any agent that lost its row.No template still holds Thymeleaf syntax (
[[${,[(${,th:). Convert any that do by hand.
With EDDI_MIGRATION_V6_RENAME_ENABLED=true still set, every 6.5 boot also moves triggers an earlier 6.x left in bottriggers to agenttriggers, and brings triggers and user-conversation mappings to the v6 shape. With the flag off, that catch-up doesn't run.
The same flag enables two one-time catch-ups on the first 6.5 boot:
The step shape of stored conversations.
packagesbecomesworkflowsin every conversation that an earlier 6.x left with v5 steps. Those conversations loaded only through a read-time alias, which stays as a safety net.The field names of conversation descriptors.
botResource/botNamebecomeagentResource/agentName. 6.4 read a v5 descriptor without its agent and wrote it back that way on every turn and every idle end. So many descriptors have no agent at all; they get it back from their conversation. Until then,GET /conversationstore/conversations?agentId=<id>doesn't list those conversations.
Each logs Migrating … which an earlier 6.x left in place and, once it has succeeded, records v6-rename-step-shape-complete or v6-rename-descriptor-fields-complete in migrationlog. A catch-up that fails logs an ERROR and runs again on the next boot. Afterwards, both of these should be 0:
The idle limit's -1 now means "never". In 6.4, setting eddi.conversations.maximumLifeTimeOfIdleConversationsInDays to -1 made every conversation count as idle, so the daily sweep ended all of them. From 6.5, any value below 1 disables idle-ending, the same way -1 works for retention. If you set a very large number to work around that, you can switch to -1.
The retention hold described in Upgrading from 5.x applies only to a first boot from 5.x. A database that 6.4 already migrated keeps the retention sweep it had under 6.4.
10. Check the result
Sign in. Open the Manager as a normal user and as an administrator. A
401points to section 2.1 or 2.2; a403points to section 2.3.Startup log. Look for:
[SECURITY] Rejected an authenticated request (401)— tokens without a user name;an ERROR about
quarkus.oidc.roles.role-claim-path— a missing or clashing roles claim;[VAULT]lines — master key strength, salt and key checks;Merged duplicate usermemories identities— how many user memories were merged away.
Agents.
GET /administration/production/deploymentstatuslists every agent READY, and the readiness check reportsagentsInErrorCount: 0.HTTP calls. Run a conversation that uses each credentialed API. A refusal names the field and the reference (section 2.6).
Templates. Render the templates you rely on with
POST /administration/preview/template.
See also: configuration reference, security, secrets vault, Kubernetes, Slack integration, Open WebUI integration.
Last updated
Was this helpful?