Repository navigation
EDDI 6.7.0 is about teams of agents that work like teams, humans who stay in control of them, and knowing what it all costs.
- 👥 Group collaboration. Standing teams get budgets, file work into each other's backlogs with a native cross-team handoff, track bets and outcomes, and share lessons across teams. People can break tied votes and execute task-force tasks.
- 🙋 Human-in-the-loop, grown up. Approvers can edit an answer while approving it, see the draft and its sources on the approval card, and get approval analytics with sampled audit and a safe path to earned autonomy. Tool calls are screened by tool-call guardrails before and after the gate.
- 💰 Cost you can trust. Provider usage is normalised the way each provider bills it, every call site is priced through one resolver backed by a managed model-price catalog, and new caps arrive:
toolQuotas, per-run quotas and an opt-in hard tenant ceiling. - ✉️ New channels and feedback. An email channel (IMAP trigger mode and threaded conversation mode, with DKIM/DMARC checks), conversation ratings, and alert rules for bad ratings, failed turns and output matches.
- 🧠 Better answers from the same model.
judgeandexpressionguardrails, structured answers through a terminal reply tool, near-miss JSON rescued without a second call, lazy loading for HTTP, MCP and A2A tools, and self-continuing long jobs. - 🏷️ White-label. Run every EDDI surface under your own name, logo and colours.
- 🛰️ A hardened cluster. NATS host-name verification, a signed control plane, fencing, fail-loud readiness, and a multi-process E2E suite that proves it.
- 🔒 Hardening. A review sweep across secrets, SSRF, GDPR, ingestion, backup, HITL and the UIs. Many fixes apply to code that shipped in 6.6.0 or earlier, so upgrading is recommended regardless of the new features.
6.7 is mostly hardening and opt-in features: stored agent, group and workflow configs keep working unchanged. Please read the Breaking Changes before upgrading.
🚀 What's New
👥 Group Collaboration
- 💶 Team budgets.
teamBudgeton a group meters every root discussion's spend to the team. New work is refused with409once the team (or tenant) is spent, and scheduled cadence fires are skipped. Admins can set this month's cap and transfer budget between teams (REST, Manager); templates can readcontext.teamBudget. - 🔀 Native cross-team handoff. A team or a standalone agent can file a task into another team's backlog under two-sided consent enforced in code: the source lists the target (
teamHandoff.targets/handoffConfig.targets), the target lists the source (handoffConfig.accept.from). - 🎯 Team learning loop. Backlog tasks can carry a bet (
hypothesis,metric,target,reviewDate) with a due-review list, and outcome events record what actually happened against a task or an approval decision. RETRO quotes recent outcomes. - 📚 Team lessons.
lessonConfigshares retro lessons across teams (again two-sided:shareWith+readFrom), and a scheduled, cost-capped consolidation job merges duplicates and retires lessons a later outcome contradicted. - ⚖️ Humans in the loop of a team.
tiePolicy: HUMAN_DECIDESlets a person break a tied vote, and a HUMAN member can be a task-force executor. - 🧑⚖️ Model-backed convergence judge.
convergence.judge: SERVICEchecks convergence with one direct model call instead of a member turn. - 🛠️ Agent operations tools (each off unless whitelisted):
read_team_scorecard, budget raise/transfer proposals that apply only after aneddi-adminapproves the call, andschedule_followupso an agent can book its own check-back.
🙋 Human-in-the-Loop
- ✏️ Edit-on-approve. A resume can carry
editedAnswer, areasonCodeand per-calltaskDecisions; group task results and the synthesis can be edited on approval too. The decision audit contract v2 records whether the reviewer changed anything and why. Available in the Manager, Slack, email, REST and MCP. - 🗂️ Approval card context.
hitlConfig.approvalContextrenders short labelled blocks (the draft, its sources, the devil's advocate's objection) onto the card when the pause commits. - 📊 Approval analytics and earned autonomy.
hitlConfig.oversightadds approval statistics per tool pattern, sampled audit of what the gate lets through, and a governed path from "always ask" to "this pattern has earned autonomy". - 💬 A rejection note asks the model to revise instead of ending the attempt; humans and groups can produce verdicts that rework within a run.
- 📧 Email notifications when a conversation pauses for approval.
🚧 Guardrails & Answer Quality
- 🛡️ Tool-call guardrails.
toolCallGuardrailsscreen tool-call arguments before the approval gate, and re-screen approved and amended calls before they execute. A newdisclosureguardrail type is included, and an agent may not write its owntoolCallGuardrails. - 🧑🏫
judgeguardrail. A model grades the answer against issue codes you declare, andreprompttells it what to fix.onExhausted: deliver-bestdelivers the best attempt with a warning instead of blocking. - 🧮
expressionguardrail. A sandboxed, deterministic check over a JSON answer (sum(lines[*].amount) == total) with an author-written fix hint. - 📤
answerMode: "tool". Structured answers through a terminalreplytool whose parameters are theresponseSchema, so tool-using agents on Gemini, Anthropic and Bedrock get enforced JSON too. - 🩹 Near-miss JSON rescued locally.
onInvalidJson: "wrap"and relatedresponseValidationfields repair plain-text, double-escaped, misnamed and empty replies without a second model call. - 📐 Argument schemas for HTTP tools.
argumentSchema(generated from OpenAPI bysetup-api) validates tool arguments before the approval gate; generated parameters are now typed. - 🧑⚖️ The cascade judge sees the request, not just the answer.
💰 Cost & Quotas
- 🧾 Cost accounting. Usage is normalised the way each provider bills it (Anthropic/Bedrock prompt-cache reads and writes, Gemini thinking tokens), every call site is priced at its own model through one
ModelPriceResolver, and a model-price catalog (/administration/model-prices, Manager → Model Prices) replaces the hard-coded table. Unpriced models are visible, and a strict mode can refuse them. - 🎟️
toolQuotas. Per-subject, per-window caps on tool calls counted across conversations and nodes ("100 improvements per user per day"), with shared pools and aRUNwindow for "20 searches per scout per run". - 🧱 Hard tenant ceiling (opt-in).
eddi.tenant.quota.cost-reservation.enabled=truereserves spend before each model phase and priced tool call, so concurrent conversations can no longer all pass on the same remaining budget. - ✅ Every paid attempt is checked against the cost ceiling and the tenant budget, with per-conversation reservations.
✉️ Channels, Feedback & Alerts
- 📬 Email channel.
channelType: "email"in trigger mode polls an IMAP folder and starts an agent turn or group discussion per mail (never replies). Conversation mode replies in the thread over SMTP, only to an authenticated sender, with per-thread rate limits. Inbound mail is checked for DKIM and DMARC alignment; XOAUTH2 is supported. - ⭐ Conversation ratings. Users rate a conversation or single replies in the Chat UI; maintainers list, summarise, export and triage them on the Manager's Feedback page or over MCP.
- 🚨 Alert rules for low ratings, turn errors and output matches, evaluated outside the pipeline with throttling, digests and an inbox (Manager → Alerts).
🔧 Engine, Tools & Integration
- 🔁 Self-continuation. The reserved
CONTINUE_CONVERSATIONaction queues the next turn of the same conversation, so a long job becomes many ordinary turns (maxContinuations,continuationInput). - 🔂
forEachon httpcalls. A call runs once per array element and collects every result, in source order, with bounded concurrency. - 💤 Lazy external tools.
lazySourcesextendstoolLoadingStrategy: LAZYto HTTP, MCP and A2A tools, with groupeddiscover_toolsand optionallazyRetention: conversation. - 📡 Streaming.
POST /agents/{conversationId}/resume/streamstreams a resumed turn,tool_resultevents close each tool call, call identity is stored in the trace,toolDisplaygives plain-language activity lines, and a keep-alive comment holds a silent stream open. - 🧰
setup-api. Per-turn auth from secret context (Bearer ${context:userToken}, sent only to the spec's own origin), a dry run, and re-sync of an agent generated from a newer spec. - 🔔 New-release check, on by default. The backend checks once a day and raises one admin notification per release (see Breaking Changes for the opt-out).
🏷️ White-Label Branding
Admin → Branding sets the product name, logo, favicon and colours with a live preview. The Manager, Chat UI, Platform Operator, Swagger/OpenAPI, A2A cards and the Keycloak theme all follow the brand, delivered same-origin. With no brand set, nothing changes.
🛰️ Cluster
Shared state, events and deployments across nodes, node identity with duplicate detection, a signed control plane (eddi.cluster.signing.key, HMAC-SHA256), fence-first leases that never lower the fence, lost-reply adoption, NATS TLS host-name verification and fail-loud readiness. A new multi-process E2E suite (three EDDI nodes, an R3 NATS cluster, Toxiproxy fault injection: partitions, half-open links, SIGSTOP, leader step-downs, kill -9 mid-turn) runs nightly on MongoDB and PostgreSQL, with soak and scale harnesses.
⚠️ Breaking Changes & Upgrade Notes
Full details: Upgrading from 6.5 → Changes in 6.7.
| Change | Impact |
|---|---|
| 🔔 Release check on by default | The backend calls api.github.com once a day. Set eddi.update-check.enabled=false (EDDI_UPDATE_CHECK_ENABLED=false) before first boot to turn it off; this also locks the Manager switch. Air-gapped deployments should disable it. |
| 💹 Cost figures rise for some workloads | inputTokens and spend now include prompt-cache reads/writes and thinking tokens. Ceilings tuned on the old, understated numbers may trigger sooner. eddi.llm.pricing.provider-normalization=false restores the old counting. unpricedPolicy defaults to ALLOW. |
| 💶 Team budgets are admin-edit only | Editors can no longer change teamBudget (eddi.groups.team-budget.admin-only-edits=false restores it). A spent or unreadable budget refuses new root discussions with 409 (fail closed). |
| 🧑⚖️ Cascade judge input | With strategy: judge_model the judge now also receives the last 8,000 characters of the user message, so escalation rates can shift. |
🙋 HumanInputRequest |
content is optional on the DTO and choice is new (MCP submit_group_human_input too). A missing content is still 400 except for a tie-break. |
| 🚪 More paths fail closed | Ingestion refuses unverifiable ownership, a resume re-checks USE, Slack approval cards need a known pause, every bot-token reference is checked against allowedHosts, scheme-relative references are vetted, and workspace endpoints return 403 instead of 500. Check your automation and Slack/email integrations. |
| 🛰️ Cluster readiness is loud | A node that never connected to NATS, or hit an auth/TLS error, reports not-ready, so a wrong credential takes every node out of rotation. tls:///wss:// are host-name verified and opentls:// fails boot. |
| ✍️ Cluster signing | Opt-in via eddi.cluster.signing.key. Keep eddi.cluster.signing.required=false during a rolling upgrade until every node has the key. |
| ☸️ Helm & Kustomize | Chart 2.14.0 or later. The Kustomize nats component needs a nats-auth Secret created beforehand; nats.antiAffinity: required needs three nodes. |
| 🔄 Rolling upgrades | Group conversation schema goes to version 5. Finish the rollout before enabling HUMAN_DECIDES or allowResultEdits on a mixed-version cluster. |
Take a backup before upgrading.
🔒 Security
- Secrets: the grant check reads what the runtime reads;
allowedHostsis enforced on transport, connections, Slack tokens andauthHeaderRef; linear-time reference scanning; secrets kept out of errors; tool results redacted before they reach the model; vault and connection races closed. - Outbound requests: the DNS-rebinding gap between the SSRF check and the connection is closed, and destinations are re-checked per request.
- Authorization: config references are authorized, a resume re-checks USE, schedule fires carry their own authority and
maxCostPerFire, group approver scope is enforced, and compiled prompts are withheld. - GDPR & audit: an erasure barrier and deep scrub for the audit ledger, dead letters fenced behind erasure, and snapshot ordering fixed.
- Hostile input: regex work in seven matchers, ingestion (HTML conversion, crawling, PDF decoding), NLP, template formatting and patch/scan work are all bounded.
- Integrations: email Message-ID is bound to its sender, the webhook replay cache refuses rather than evicts and is shared in a cluster, and long-lived MCP logins work with https token endpoints behind TLS.
- Deployment: installer and provisioning credentials are scoped and kept out of argv;
SHA256SUMSnow carries SLSA provenance.
⚠️ Known issue: the Platform Operator cannot be activated or upgraded
In 6.7.0, activating the Platform Operator, and upgrading an existing one, fails with System prompt is too long (… characters, maximum 100000) (HTTP 400). The Operator's instructions grew past the limit setup-api enforces on system prompts.
- An Operator set up on an earlier release keeps working on its previous instructions. It shows an "upgrade available" notice; do not upgrade it until the fix is released, because the upgrade replaces it and fails.
- On a fresh 6.7.0 install the Operator cannot be activated yet.
- The fix (#1290) raises the limit and adds a test that keeps the Operator's prompt well below it. It ships in the next release.
Two smaller issues are fixed in the same PR: after an Operator was edited and redeployed in place, the Operator screen still showed and checked the version first activated; and an unbranded install shows the old EDDI logo as its browser-tab icon.
⚠️ Known issue: GHSA-ccrj-gw8r-854m (OpenTelemetry)
EDDI 6.7.0 ships opentelemetry-api 1.62.0, which is affected by GHSA-ccrj-gw8r-854m (medium): an unauthenticated request with a crafted W3C tracestate header (long runs of spaces or tabs) costs quadratic CPU while the trace context is extracted, and that can stall other connections served by the same thread. The version is managed by Quarkus 3.40.1.1, and the fixed 1.67.0 cannot be forced underneath it — EDDI then fails at startup. It will be picked up as soon as a Quarkus platform manages OpenTelemetry 1.67.0 or later.
Mitigation: drop or truncate the tracestate header at your reverse proxy or ingress (the W3C spec allows at most 512 characters, so nothing legitimate is lost). Lowering header-size limits (at the proxy, or quarkus.http.limits.max-header-size in EDDI) reduces the cost per request but does not remove it.
🐛 Bug Fixes
- A resumed HITL conversation keeps its secret flag, so secret context can be re-supplied.
- Scheduling keeps claiming due schedules while long fires run.
\'inside JSON strings in a model reply no longer breaks parsing; a guardrail-refused message is never sent to a model on a later turn.- Group prompts fence peer text, tool-result tails are kept, and identifiers survive summaries.
- An untyped context entry is a
400, not a500. - The Workforce dashboard shows every agent, ordered by last use.
- Log capture starts at boot and buffers early records.
📦 Dependency Updates
langchain4j 1.22.0 → 1.23.0, Jackson 2.22.3 → 2.22.4, Guava → 33.7.2-jre (GHSA-xxph-c9ww-hj94), minimatch → 11.0.4 in the Manager and Chat UI, Node (UI build) 24.22.0 → 26.13.0 LTS, Surefire/Failsafe 3.6.0 → 3.6.1. New: Jakarta Mail / Angus Mail and dnsjava for the email channel. Base images refreshed.
🖥️ EDDI Manager
New pages for Branding, Model Prices, Feedback and Alerts; a tool-call guardrails editor; team budget cards, quota tables and transfer dialogs; bets, due reviews and outcomes on the standing-team workspace; and edit-on-approve with approval-card context.
📚 Documentation
The documentation in docs/ can now be built as a site with Astro + Starlight (docs-site/), in preparation for moving it from docs.labs.ai to docs.eddi.technology; until that switch, docs.labs.ai stays the place to read it.
📋 Full Changelog
6.6.0...6.7.0. See docs/changelog.md and docs/changelog/2026-10.md for per-change detail and design decisions.
🐳 Docker Image
docker pull labsai/eddi:6.7.0cosign 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/[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?)$' \
labsai/eddi:6.7.0EDDI is distributed exclusively as a Docker image. There are no binary downloads.