Configuration Reference
Every eddi.* property EDDI reads, with its default and the environment variable that sets it. This is the deployment/operator surface — agent behaviour is configured as JSON documents, not here. If you are trying to change what an agent says or does, you want Behavior Rules, LLM Integration or Properties instead.
How to set these
Three interchangeable mechanisms, in ascending order of precedence:
src/main/resources/application.properties
eddi.schedule.poll-interval=15s
Source builds and the shipped defaults
Environment variable
EDDI_SCHEDULE_POLL_INTERVAL=15s
Docker, Compose, Kubernetes — the normal path
JVM system property
-Deddi.schedule.poll-interval=15s
One-off local overrides
The environment-variable spelling is mechanical: uppercase the property name and replace every character that is not a letter or a digit with _. Both . and - are replaced — neither is dropped.
eddi.schedule.poll-interval → EDDI_SCHEDULE_POLL_INTERVAL
eddi.vault.master-key → EDDI_VAULT_MASTER_KEY
eddi.openai-compat.api-key → EDDI_OPENAI_COMPAT_API_KEY
eddi.tools.websearch.google.api-key → EDDI_TOOLS_WEBSEARCH_GOOGLE_API_KEYGetting this wrong fails silently. An unrecognised environment variable is not an error — the property simply keeps its default and the service starts normally.
EDDI_VAULT_MASTERKEY(dash deleted rather than replaced) leaveseddi.vault.master-keyempty, which means the vault is inactive, and ascope: "secret"property setter then fails the whole turn. Nothing in the startup log mentions the variable you set.To check what actually bound, read the value back from the Dev UI at
/q/dev, or compare against the spellings already used indocker-compose.yml,.env.exampleandk8s/.
Quarkus profiles prefix the key: %dev.eddi.usermemories.deleteOlderThanDays=-1 applies in dev mode only.
Empty is not the same as unset. Several properties ship deliberately empty (
eddi.vault.master-key,eddi.deployment.env). An empty value is a real value that disables or defaults the feature; it is not an error, and startup will not complain.
Data store & messaging
eddi.datastore.type
mongodb
mongodb or postgres. Selects the whole persistence layer — see Architecture → DB-agnostic design
eddi.messaging.type
in-memory
in-memory or nats. in-memory confines the conversation coordinator to a single instance; nats distributes it
NATS JetStream (only when eddi.messaging.type=nats)
eddi.nats.url
nats://localhost:4222
Server URL
eddi.nats.stream-name
EDDI_CONVERSATIONS
JetStream stream carrying conversation work
eddi.nats.dead-letter-stream-name
EDDI_DEAD_LETTERS
Stream that receives messages past max-retries
eddi.nats.max-retries
3
Redelivery attempts before dead-lettering
eddi.nats.ack-wait-seconds
60
How long JetStream waits for an ack before redelivering. Must exceed your slowest conversation turn, or slow turns are processed twice
Conversation lifecycle & retention
eddi.conversations.maximumLifeTimeOfIdleConversationsInDays
90
Idle conversations are closed after this many days
eddi.conversations.deleteEndedConversationsOnceOlderThanDays
365
Ended conversations are permanently deleted after this many days
eddi.conversations.max-input-chars
200000
Longest turn input, in characters, a caller may send to an existing conversation. Longer input is refused before anything reaches the model: 413 input_too_large on the REST and streaming conversation endpoints, 400 input_too_large on the OpenAI-compatible API, invalid params over A2A; other surfaces built on those entry points report the refusal as an error. 0 or negative disables the limit. Turns the engine drives itself for group members and sub-agents are exempt
eddi.usermemories.deleteOlderThanDays
-1
eddi.coordinator.max-active-conversations
10000
Ceiling on concurrently tracked conversations
eddi.coordinator.max-dead-letters
1000
Retained dead-letter entries. -1 unbounded, 0 retain none
Graceful shutdown
Tuned for Kubernetes rolling updates: EDDI reports itself not-ready first, waits for the load balancer to notice, then drains in-flight conversations.
eddi.shutdown.readiness-grace-seconds
3
Time between failing readiness and starting the drain. Must exceed your ingress's health-check interval, or requests keep arriving mid-drain
eddi.shutdown.drain-timeout-seconds
20
How long to wait for in-flight conversations before exiting anyway. Keep it below your orchestrator's terminationGracePeriodSeconds
eddi.shutdown.drain-poll-millis
100
How often the drain re-checks for completion
Streaming
eddi.streaming.cancel-on-client-disconnect
true
Abort the turn when an SSE client goes away. Set false to let the turn finish and persist, so a reconnecting client can read the result
eddi.llm.tool-loop.streaming.enabled
true
Stream tokens during tool-calling turns rather than falling back to a single chunk
Scheduling
Full narrative and metrics: scheduling.md → Deployment Configuration.
eddi.schedule.enabled
true
Master switch for the poller
eddi.schedule.poll-interval
15s
How often due schedules are looked for — the floor on firing punctuality
eddi.schedule.poll-batch-size
100
Schedules claimed per cycle
eddi.schedule.lease-timeout
5m
Claim lease before another instance may re-claim. Set above your slowest fire, or slow runs execute twice
eddi.schedule.max-retries
5
Attempts before DEAD_LETTERED
eddi.schedule.backoff-base-seconds
15
Retry delay = base × multiplier^(attempt-1)
eddi.schedule.backoff-multiplier
4
Defaults give 15s, 60s, 4m, 16m, 64m
eddi.schedule.min-interval-seconds
60
Smallest cron interval a schedule may request
eddi.schedule.instance-id
(hostname)
Cluster claim identity. Set explicitly where hostnames are recycled
eddi.schedule.default-timezone
UTC
IANA zone for schedules that name none
eddi.schedule.fire-timeout
5m
How long one conversation fire may run before it is abandoned as failed. Keep it at or below lease-timeout — past the lease another instance may reclaim the schedule regardless
eddi.schedule.fire-log-retention
90d
Fire logs older than this are deleted by a periodic sweep. 0 keeps everything — a 60-second heartbeat alone writes ~525,600 rows a year
eddi.schedule.fire-log-prune-interval
1h
How often that sweep runs. The DELETE is by timestamp and therefore idempotent, so it needs no cluster claim
Security & authentication
Authentication itself is Quarkus OIDC, not an
eddi.*property:quarkus.oidc.tenant-enabled(defaultfalse) is the master switch, andauthorization.enabledtracks it so@RolesAllowedis enforced exactly when OIDC is on. See security.md.
eddi.security.allow-unauthenticated
false
Permits running with OIDC disabled outside dev. AuthStartupGuard refuses a production boot without it
eddi.security.ssrf-protection.enabled
false
Opt-in. Validates the fully resolved target of httpCalls, MCP and A2A calls and stops following redirects. Off by default because configured targets legitimately reach internal hosts — turn it on if any outbound URL is influenced by conversation input. The cloud instance-metadata service (and the link-local range it lives in) is refused regardless of this setting. See security.md → SSRF Protection
eddi.mcp.allow-unauthenticated
false
Exposes the MCP server without auth. Needs its own opt-in on top of eddi.security.allow-unauthenticated — inheriting one flag must not be enough to open agent CRUD
eddi.secretstore.allow-unauthenticated
false
Same, for the secrets vault REST surface
eddi.caller-identity.enabled
true
Enables ${caller:token} / ${caller:userId} in httpCall headers. See httpcalls.md
eddi.keycloak.public.url
(empty)
Browser-facing Keycloak URL when it differs from the in-cluster one
Workspaces & resource sharing
Full guide: workspaces.md.
Enforcement and ownership are deliberately separate switches. Ownership is stamped on every new resource whenever
authorization.enabledis on, regardless ofeddi.workspaces.enabled, so a deployment can run a release with attribution recorded and nothing filtered, confirm the data looks right, and only then enforce. Enforcing before ownership has been stamped and backfilled is what would hide people's own work from them.
eddi.workspaces.enabled
false
Whether workspace access is actually enforced: listings filtered, and reads, writes, deletes and re-sharing each checked against the level the caller holds (VIEW / USE / EDIT / OWN). No effect while authorization.enabled=false — with no authenticated principal there is nothing to scope to, and startup warns rather than denying everyone everything
eddi.workspaces.groups-claim
groups
The JWT claim listing the caller's teams. Needs a group-membership protocol mapper on the eddi-backend client; without one every user simply gets a personal space and no teams, which is a correct answer rather than a failure
eddi.workspaces.legacy-visibility
shared
Who may see resources with no recorded owner: shared (everyone, so an upgrade hides nothing) or admin-only (only eddi-admin, so an operator migrates deliberately and then closes the door). Any other value fails startup rather than picking a policy by guessing
eddi.workspaces.default-space
(empty)
Where new resources are filed. Empty means the creator's personal space; a Keycloak group name gives a team-first deployment, where colleagues see each other's work by default and personal spaces are reached by explicitly moving a resource
Secrets vault
Full guide: secrets-vault.md.
eddi.vault.master-key
(empty)
KEK source. Empty means the vault is inactive. A scope: "secret" property setter then scrubs the plaintext, logs an ERROR and fails the turn with a LifecycleException naming EDDI_VAULT_MASTER_KEY — it never persists the value. (AgentSetupService's own vaultApiKey path is the exception and still degrades; see secrets-vault.md.)
eddi.vault.grant-enforcement
enforce
off, warn or enforce. An unrecognised value fails startup rather than silently disabling the check
eddi.vault.cache-ttl-minutes
5
Resolved-secret cache lifetime
eddi.vault.cache-max-size
1000
Resolved-secret cache entries
eddi.setup.vault-key-reuse
checksum
checksum reuses an existing vault entry when the value matches; never always writes a new one. A typo fails startup
eddi.setup.llm.log-conversation-content
false
Log conversation content during agent setup. Leave off outside debugging
Compliance gates
eddi.compliance.audit-signing-required
false
Refuse to start unless audit HMAC signing is active
eddi.compliance.database-encryption-acknowledged
false
Operator attestation that encryption-at-rest is configured — see hipaa-compliance.md
Audit ledger
Full guide: audit-ledger.md. Note there is no retention property — the ledger is append-only by design; see gdpr-compliance.md.
eddi.audit.enabled
true
Master switch
eddi.audit.flush-interval-seconds
3
Batch flush cadence
eddi.audit.max-queue-size
100000
In-memory queue. When full, entries are dropped and counted by eddi_audit_entries_dropped_total — alert on it, because a non-zero value means the trail has holes
eddi.audit.dead-letter-path
/opt/eddi/data/eddi-audit-deadletter.jsonl
Where undeliverable entries are written. Must be on a persistent volume, or dropped entries vanish with the container
eddi.audit.agent-signing-enabled
true
Sign agent configurations for provenance
eddi.audit.verify.recover-legacy
true
Accept pre-HMAC rows during chain verification
eddi.audit.verify.recover-legacy-max-rows
500
Cap on how many such rows are tolerated
GDPR / CCPA
Full guide: gdpr-compliance.md.
eddi.gdpr.restriction-cache-ttl-seconds
0
How long an Art. 18 restriction verdict may be reused without re-reading the store. 0 — the default — switches the cache off, so every check reads the store. The cache is node-local with no cross-node invalidation, so a cached "not restricted" on one node keeps a restricted user being processed for the length of the TTL after another node applies the restriction, and keeps answering from cache through a store outage instead of failing closed. Raise it only on a single-node deployment or one with conversation affinity
Human-in-the-Loop
Full guide: hitl.md.
eddi.hitl.tool.enabled
true
Per-tool-call approval gating
eddi.hitl.tool.task-approvals.mode
strict
strict unions requireApproval patterns, ignores task-level exempt, and demotes task-level AUTO_APPROVE — a task cannot loosen its agent's gate. replace restores pre-6.3.0 behaviour where a task config fully replaces the agent gate
eddi.hitl.tool.journal-retention
30d
How long tool-approval journal entries are kept
eddi.hitl.tool.transcript-max-bytes
2000000
Cap on a stored approval transcript
eddi.hitl.pending.max-age
(empty)
Auto-cancel pending approvals older than this ISO-8601 duration. Empty = never auto-cancel, so approvals wait indefinitely
eddi.hitl.pending.sweep-interval
6h
How often the auto-cancel sweep runs
eddi.hitl.crash-recovery.enabled
true
Restore paused conversations after a restart
eddi.hitl.crash-recovery.recover-in-progress
true
Also recover turns that were mid-execution
eddi.mcp.hitl.mutations.enabled
true
Allow approve/reject decisions through the MCP surface
Tools & outbound calls
eddi.tools.budget.enforce-by-default
false
Enforce per-conversation tool cost ceilings without a per-task enforceBudget flag. See langchain.md
eddi.tools.ratelimit.global.enabled
false
Deployment-wide tool rate limit, on top of per-tool limits. Both must admit a call
eddi.tools.ratelimit.global.limit
1000
Calls per minute when the above is on
eddi.tools.websearch.provider
duckduckgo
duckduckgo (no key) or google
eddi.tools.websearch.google.api-key
(empty)
Required for the google provider
eddi.tools.websearch.google.cx
(empty)
Google Programmable Search engine ID
eddi.tools.weather.openweathermap.api-key
(empty)
Required by the weather tool
eddi.httpcalls.default-timeout-millis
30000
Per-call timeout when the httpCall does not set one. Without it a call can occupy the conversation thread indefinitely
eddi.httpcalls.default-max-response-size-bytes
2000000
Response-body ceiling. Deliberately above the memory cap, so an over-long body is truncated into memory rather than failing the turn
eddi.mcpcalls.default-rate-limit
100
Default per-minute limit for MCP tool calls
eddi.ollama.default-base-url
http://localhost:11434
Used when an Ollama LLM config omits baseUrl
Attachments
Full guide: attachments-guide.md.
eddi.attachments.max-size-bytes
20971520 (20 MB)
Largest single upload
eddi.attachments.max-per-turn
5
Attachments per turn — per member turn in a group conversation
eddi.attachments.max-per-conversation
50
Attachments per conversation
eddi.attachments.max-total-bytes-per-conversation
104857600 (100 MB)
Aggregate bytes per conversation
eddi.attachments.max-forward-bytes
10485760 (10 MB)
Per-file ceiling on what is forwarded to the LLM, across every source
eddi.attachments.max-forward-aggregate-bytes
20971520 (20 MB)
Aggregate ceiling for one message
eddi.attachments.extraction.max-chars
50000
Cap on text extracted from a document
The upload cap and the forward cap are different numbers on purpose: a 20 MB PDF may be stored and read on demand via the
readAttachmenttool without being inlined into every prompt.
Backup, export & import
Full guide: import-export-an-agent.md.
eddi.backup.export.retention-minutes
60
How long a finished export archive stays downloadable
eddi.backup.export.sweep-interval
15m
How often the retention sweep runs on its own, independently of exports
POST /backup/export/{agentId}writes a ZIP undertmp/archives/and answers with aLocationheader the client then GETs, so the file has to outlive the request. Nothing else deletes it: the sweep runs before every export and on the interval above, so an instance that stops exporting still reclaims what it already wrote. It also removes the loosetmp/*.ziparchives earlier releases left behind, which are no longer downloadable. Raise the retention if a client may take longer than that between the POST and the GET; lower it to bound disk use on an instance that exports on a cron.
Protocols & integrations
MCP
eddi.mcp.tool-cache.ttl-ms
300000 (5 min)
How long a remote server's tool list is cached. Lower it while developing against a changing MCP server
eddi.mcp.tool-description.max-chars
1024
Truncation cap on imported tool descriptions, bounding prompt cost
A2A
eddi.a2a.enabled
true
Serve the A2A endpoints
eddi.a2a.base-url
http://localhost:7070
The URL advertised in Agent Cards. Wrong here means peers cannot reach you
eddi.a2a.capabilities.public
false
Serve /.well-known/capabilities unauthenticated
eddi.a2a.tool-description.max-chars
1024
Truncation cap on peer tool descriptions
eddi.a2a.signing.nonce.max-age-ms
300000 (5 min)
Replay window for signed requests
eddi.a2a.signing.nonce.clock-skew-ms
30000
Tolerated clock difference between peers
eddi.a2a.task-timeout-seconds
systemRuntime.agentTimeoutInSeconds
How long a peer's tasks/send may wait for the turn. Inherits the REST surface's budget, because an operator who raised that has already decided how long a turn may take
Slack
Full guide: slack-integration.md.
eddi.slack.request-timeout-seconds
60
How long a single agent turn may take before Slack is told it timed out. A turn that legitimately runs longer — a multi-step tool call, a slow provider, a cascade escalation — needs this raised, and answers with a timeout-specific notice naming the limit rather than a generic error
eddi.slack.group-completion-timeout-seconds
300
How long a whole group discussion may take before follow-up routing gives up
eddi.slack.api-max-retries
3
Attempts, including the first, for a Slack Web API call
eddi.slack.api-retry-base-ms
500
Base delay for the exponential backoff between those attempts
OpenAI-compatible API
Full guide: open-webui-integration.md.
eddi.openai-compat.enabled
false
Serve /v1
eddi.openai-compat.api-key
(empty)
Shared key clients present
eddi.openai-compat.http-policy
permit
permit accepts the shared key; authenticated has Quarkus OIDC validate per-user tokens instead
eddi.openai-compat.trust-user-headers
true
Believe X-OpenWebUI-User-Id as the EDDI userId. Safe only because the caller proved possession of the shared key — a leaked key therefore permits impersonating any user
eddi.openai-compat.allow-anonymous
false
Serve requests carrying no user identity
eddi.openai-compat.default-user
openai-anonymous
userId used when anonymous is allowed
eddi.openai-compat.environment
production
Deployment environment agents are resolved from
eddi.openai-compat.expose-stateless-variants
true
Also list …-stateless model ids
eddi.openai-compat.model-cache-seconds
30
How long /v1/models is cached
eddi.openai-compat.max-concurrent-requests
64
Concurrency ceiling for the adapter
eddi.openai-compat.request-timeout-seconds
120
Per-request timeout
Connections
Full guide: connections.md.
The first four are runtime settings: an administrator changes them without a restart through PUT /connectionstore/settings (or the Manager's Connections page). Setting the property pins the value instead — it wins over the stored one, and the endpoint refuses to change it (409). Unset and unstored, each takes the default below. See Enabling connections.
eddi.connections.enabled
false
Master switch for the connection credential model. Runtime setting enabled
eddi.connections.public-base-url
(none)
Externally reachable base URL for OAuth redirect URIs. Runtime setting publicBaseUrl. A pinned value that is not a bare https origin refuses the boot; without one, per-user account linking answers 400
eddi.connections.credential-endpoint-allowlist
(empty)
Origins that may receive the client secret — a connection's token and authorization endpoints, and only those (RFC 9728 resource-metadata discovery is not implemented). Not where the access token goes: that is each connection's own baseUrlAllowlist. Runtime setting credentialEndpointAllowlist
eddi.connections.allow-plaintext-remote-origins
false
Whether a connection's baseUrlAllowlist may send its credential over plaintext http:// to a non-loopback host. While false such an origin is refused at save time (400), refused per request (TARGET_NOT_ALLOWED) and reported at ERROR at boot; true accepts it with a WARN. Loopback http:// is always allowed. Runtime setting allowPlaintextRemoteOrigins. Upgrade note: existing connections with a remote http:// origin stop resolving until this is set
eddi.connections.settings.allow-unauthenticated-writes
false
Whether PUT /connectionstore/settings accepts a write with no verified identity. Outside dev and test, while authorization.enabled=false, such a write is refused (403) unless this is true — @RolesAllowed is a no-op without OIDC, and an anonymous caller must not be able to approve a credential endpoint. Not a runtime setting
eddi.connections.state-sweep-interval
1h
How often expired OAuth state entries are cleared
Upgrade note. Any of the first four properties that is set — even to its default, e.g.
EDDI_CONNECTIONS_ENABLED=falsecopied from an old example — now pins that value, and the settings page shows it read-only. Unset it to let administrators manage the value at runtime.
Groups, tenancy & variables
eddi.groups.max-depth
3
Nesting limit for groups-of-groups
eddi.groups.cadence.claim-ttl
PT24H
How long a standing-team cadence claim is held
eddi.tenant.default-id
default
Tenant assigned when a request names none
eddi.tenant.quota.enabled
false
Per-tenant quota enforcement
eddi.tenant.quota.max-conversations-per-day
-1
-1 = unlimited
eddi.tenant.quota.max-agents-per-tenant
-1
-1 = unlimited
eddi.tenant.quota.max-api-calls-per-minute
-1
-1 = unlimited
eddi.tenant.quota.max-monthly-cost-usd
-1
-1 = unlimited
eddi.deployment.env
development (when unset)
Value matched by the deploymentContext behavior-rule condition, letting one agent behave differently per environment
Logging & documentation surfaces
eddi.logs.buffer-size
10000
In-memory ring buffer backing /administration/logs
eddi.logs.db-enabled
true
Also persist logs to the database
eddi.logs.db-flush-interval-seconds
5
Persistence batch cadence
eddi.logs.db-persist-min-level
WARN
Minimum level persisted. Lowering this to DEBUG in production will fill the database quickly
eddi.docs.enabled
true
Serve EDDI's own docs at /administration/docs, as MCP resources (eddi://docs/*) and as the list_docs/read_docs MCP tools. One switch covers all three. The content is the public repository documentation, so this is an exposure policy, not a secrecy control
eddi.docs.path
docs
Directory the above is served from
Migration
These run once against an existing database and then stay off.
eddi.migration.v6-rename.enabled
false
Rewrite v5 resource URIs to v6 spellings
eddi.migration.v6-qute.enabled
false
Convert Thymeleaf templates to Qute
eddi.migration.backupBeforeWrite
true
Snapshot documents into .history collections before rewriting. Leave this on
eddi.migration.skipConversationMemories
false
Skip conversation memories, which are the bulk of the data and rarely need rewriting
See also
Metrics & Monitoring — what to watch once these are set
Security — the reasoning behind the security defaults
Kubernetes — how these map onto Helm values and ConfigMaps
.env.example— the Docker Compose subset, ready to copysrc/main/resources/application.properties— the shipped defaults, with inline commentary
Last updated
Was this helpful?