For the complete documentation index, see llms.txt. This page is also available as Markdown.

Developer Quickstart Guide

Version

This guide helps developers quickly understand EDDI's architecture and start building agents.

Understanding EDDI in 5 Minutes

What EDDI Is

EDDI is middleware for conversational AI—it sits between your app and AI services (OpenAI, Claude, etc.), providing:

  • Orchestration: Control when and how LLMs are called

  • Business Logic: IF-THEN rules for decision-making

  • State Management: Maintain conversation history and context

  • API Integration: Call external REST APIs from agent logic

Key Concept: The Lifecycle Pipeline

Every user message goes through a pipeline of tasks:

Input → Parser → Rules → API/LLM → Output

Each task transforms the Conversation Memory (a state object containing everything about the conversation).

Agent Composition

Agents aren't code—they're JSON configurations:

Each step points at a stored configuration document by eddi:// URI, so the same rule set or output set can be reused across workflows and agents.

Quick Setup

Prerequisites

  • Java 25 (the version in pom.xml is authoritative)

  • Maven — not needed separately; the repo ships the ./mvnw wrapper (.\mvnw.cmd on Windows)

  • MongoDB 7 (or PostgreSQL — see Architecture)

  • Docker — optional for running from source, required for the quick start below

Run with Docker (Easiest)

Optional overlays stack on top of the base file — for example, a local LLM on the same Docker network:

Run from Source

💡 Secrets Vault: If you plan to store API keys through the Manager UI or use ${vault:...} references, set the vault master key first:

Without this, the vault is disabled and secret endpoints return HTTP 503. Any passphrase works for local dev. See Secrets Vault for full details.

Configuring AI Tools

If you plan to use the Web Search or Weather tools in your agents, you need to set up API keys in your environment or application.properties.

Web Search (Google):

  • eddi.tools.websearch.provider=google

  • eddi.tools.websearch.google.api-key=...

  • eddi.tools.websearch.google.cx=...

Weather (OpenWeatherMap):

  • eddi.tools.weather.openweathermap.api-key=...

See LangChain Documentation for details.

Your First Agent (via API)

The walkthrough below is a complete, copy-pasteable session against a stock docker compose up -d instance on http://localhost:7070. Every request and every response shape here is the one the running server actually produces.

Three things to know before you start, because they surprise everybody once:

  1. A successful create returns 201 with an empty body. The id is in the Location (and X-Resource-URI) header, as an eddi:// URI — not an HTTP URL you can fetch. Take the last path segment as the id: eddi://ai.labs.dictionary/dictionarystore/dictionaries/68a1…?version=1 → id 68a1…, version 1.

  2. Every store has a descriptors listingGET /<store>/<resource>/descriptors. That is how you find what you created. There is also a cross-type listing, GET /descriptorstore/descriptors?type=<type>, which is handy when you want everything of one kind at once.

  3. Unknown fields are rejected, not ignored. A 400 naming the offending key means the payload used an older field name — the message lists the legal ones. This is deliberate: a silently dropped key looks like a successful save and changes nothing.

jq is used below only to pretty-print; it is not required.

1. Create a Dictionary

Dictionaries map what users type to expressions, which behavior rules then match on:

Response: 201 Created, with

Keep <DICTIONARY_ID>. To list every dictionary, or read one back:

Naming a resource. A resource created over the API has an empty name, so the Manager UI lists it as "Unnamed …". Names live on the descriptor, not on the configuration document — set one with a PATCH, and the same call works for every resource type in this guide:

2. Create Behavior Rules

Rules decide which actions a turn emits:

Response: 201, Location: eddi://ai.labs.rules/rulestore/rulesets/<RULESET_ID>?version=1

behaviorRules is the canonical name: it is what a read returns, and what the shipped reference config and the ZIP fixtures use. rules is still accepted on write for older clients. Before 6.4.0 a read answered rules regardless of what you posted, which is why a rule set created over the API showed up empty in the Manager.

3. Create Output Templates

Output maps an action to what the user sees. valueAlternatives is a list of typed output items, not a list of strings — a bare string is rejected:

Response: 201, Location: eddi://ai.labs.output/outputstore/outputsets/<OUTPUT_ID>?version=1

Besides text, an output item may be image, quickReply, inputField, applicationLink, button, agentFace or other. See Output Configuration.

4. Create a Workflow

A workflow is the ordered list of pipeline steps — the workflowSteps array. Each step names a lifecycle task by eddi:// URI; a step that needs a configuration document points at it through config.uri, and the parser takes its dictionaries through extensions instead:

Response: 201, Location: eddi://ai.labs.workflow/workflowstore/workflows/<WORKFLOW_ID>?version=1

Step order is execution order. The available step types:

Step type

Purpose

Configuration

eddi://ai.labs.parser

Input → expressions

dictionaries/corrections via extensions

eddi://ai.labs.rules

Behavior rules → actions

config.uri → rule set

eddi://ai.labs.property

Slot-filling / properties

config.uri → property setter

eddi://ai.labs.apicalls

Outbound HTTP calls

config.uri → API calls

eddi://ai.labs.mcpcalls

MCP tool calls

config.uri → MCP calls

eddi://ai.labs.llm

LLM interaction

config.uri → LLM config

eddi://ai.labs.output

Actions → user-visible output

config.uri → output set

eddi://ai.labs.templating

Resolves {…} in the output

none

Add eddi://ai.labs.templating last whenever an output or system prompt contains {properties.x}-style placeholders. Without it the expressions are never resolved. It is harmless when there is nothing to resolve, so the examples keep it.

eddi://ai.labs.behavior and eddi://ai.labs.httpcalls are accepted as aliases of ai.labs.rules and ai.labs.apicalls; new configurations should use the names in the table.

5. Create an Agent

An agent is a list of workflows — the field is workflows:

Response: 201, Location: eddi://ai.labs.agent/agentstore/agents/<AGENT_ID>?version=1

Give it a name, or the Manager will list it as "Unnamed Agent":

packages is still accepted as an alias for workflows, so a payload using the old name is stored rather than rejected. It is deprecated — write workflows.

6. Deploy the Agent

An agent must be deployed to an environment before it can hold a conversation. Pass waitForCompletion=true and the call returns the final status instead of a bare 202 Accepted:

Any status other than READY — typically ERROR — means the agent's workflow references something that does not resolve. Check the server log, then re-deploy. The status can be re-read at any time:

7. Chat with Your Agent

Starting a conversation and sending a message are two separate calls. The start call takes no message body; it creates the conversation and returns its id in the Location header:

Then talk to the conversation, not the agent. Plain text is the simplest form:

The same endpoint accepts JSON when you want to pass context alongside the message:

To start a conversation with an initial context, POST that same context map — and only a context map — to the start endpoint:

Adding an LLM (Ollama Example)

Ollama is used here because it needs no API key. Everything below is identical for the other providers except type and the credential — see LLM Integration for the full list.

If EDDI runs in Docker and Ollama runs on your host, localhost inside the container is the container. Either use http://host.docker.internal:11434, or run Ollama as a compose service with docker compose -f docker-compose.yml -f docker-compose.ollama.yml up -d, which puts it on the same network as http://ollama:11434.

1. Create the LLM Configuration

Response: 201, Location: eddi://ai.labs.llm/llmstore/llms/<LLM_ID>?version=1

parameters is a free-form map, but a key no provider reads is logged as a warning at build time rather than applied. systemMessage and addToOutput are read by the pipeline; model, baseUrl, temperature, maxTokens, topP and topK are read by the Ollama builder.

For a hosted provider, put the credential in the vault rather than in the configuration document:

2. Add the LLM Step to the Workflow

Insert an eddi://ai.labs.llm step into workflowSteps, before eddi://ai.labs.output:

Update the workflow in place (this creates version 2):

Then point the agent at the new workflow version and deploy again — a deployed agent version is immutable, so a config change always means a re-deploy.

3. Trigger the LLM from a Behavior Rule

The LLM task runs when an action it listens for is emitted. Add a rule whose actions contains send_to_ai:

Now anything the dictionary does not recognise is handed to the model, while greeting(*) still takes the cheap rule-based path.

Understanding the Flow

Let's trace what happens when a user says "hello":

1. API Request

2. RestAgentEngine

  • Validates agent ID

  • Creates/loads conversation memory

  • Submits to ConversationCoordinator

3. ConversationCoordinator

  • Ensures sequential processing (no race conditions)

  • Queues message for this conversation

4. LifecycleManager Executes Pipeline

Parser Task:

Behavior Rules Task:

Output Task:

5. Save & Return

  • Memory saved to MongoDB

  • Response returned to user

Key Architectural Components

IConversationMemory

The state object passed through the pipeline:

ILifecycleTask

Interface all tasks implement:

ConversationCoordinator

Ensures messages are processed in order:

Common Patterns

Pattern 1: Conditional LLM Invocation

Only call LLM for complex queries:

Pattern 2: API Call Before LLM

Fetch data, then ask LLM to format it:

The LLM receives the API response in memory and can format it naturally.

Pattern 3: Context-Aware Responses

Use context passed from your app:

The start endpoint's JSON body is the context map — there is no input field on it, because starting a conversation and sending a message are separate calls:

Access in an output template — and remember the eddi://ai.labs.templating step, or the placeholder is never resolved:

Next Steps

Learn More

Use the Dashboard

Visit http://localhost:7070/manage to:

  • Create agents visually

  • Test conversations interactively

  • Browse configurations

  • Monitor deployments

Explore a Worked Configuration

docs/agent-configs/rule-based-reference/ is a complete, working rule-based agent — a conversational wizard that provisions another agent over EDDI's own REST API. It is the canonical reference for behavior-rule patterns, property setters capturing free text, HTTP call templates and quick replies. Two unit tests sweep it, so the config documents it supplies keep parsing and saving — note that descriptors and unmapped filenames are counted as skipped rather than checked, so a green sweep is not a claim about every file in the directory.

Build Your Own Task

Create a custom lifecycle task:

Register it in CDI and it becomes available as an extension!

Troubleshooting

A request failed and I have no idea why

  • 400 with a message naming a field — the payload used a key the model does not declare, or put the wrong kind of value in one that it does. The message names the JSON path and the legal fields.

  • 404 on deploy — the agent id or version does not exist.

  • 201 but nothing appears in the Manager — the resource was created with an empty name. Set one via PATCH /descriptorstore/descriptors/{id}?version=1.

Agent doesn't respond

  1. Deployment status: GET /administration/production/deploymentstatus/{agentId}?version=1 — anything other than READY means the workflow did not load.

  2. Conversation state: GET /agents/{conversationId}/status

  3. Full memory snapshot (what each pipeline step actually produced): GET /agents/{conversationId}?returnDetailed=true

  4. Server log: docker compose logs -f eddi, or GET /administration/logs

Rules not matching

  • Confirm the parser produced the expression you are matching on — the expressions:parsed entry of the detailed snapshot above shows exactly what it emitted, and behavior_rules:success / behavior_rules:fail show which rules matched.

  • The dictionary has to be wired into the parser step's extensions, not referenced as a step of its own.

  • actionmatcher with a comma-separated list means AND (a contiguous sublist), not OR. Use a connector with "operator": "OR" for alternatives.

  • occurrence: "anyStep" matches anywhere in the conversation.

LLM not being called

  • The behavior rule has to emit the action the LLM task lists in its actions.

  • The eddi://ai.labs.llm step has to be in the workflow the deployed agent version points at — adding it to a newer workflow version does nothing until the agent is re-pointed and re-deployed.

  • Check the credential resolves. A ${vault:...} reference needs EDDI_VAULT_MASTER_KEY set, or the vault is disabled.

Ollama-backed agent times out or answers nothing

  • From inside the eddi container, localhost is the container. Use http://host.docker.internal:11434, or the docker-compose.ollama.yml overlay and http://ollama:11434.

  • Reasoning models (gemma3n, deepseek-r1, qwen3 …) think before answering, and the reasoning is not part of the streamed content — the window stays silent for as long as that takes. Set "think": "false" in the task's parameters to turn it off, or "returnThinking": "true" to surface it.

Memory not persisting

  • Ensure MongoDB is running and the connection string is right.

  • scope: "step" is cleared at the end of the turn, conversation lives for the session, longTerm survives across conversations.

Getting Help

  • Documentation: https://github.com/labsai/EDDI/tree/main/docs

  • GitHub: https://github.com/labsai/EDDI

  • Issues: https://github.com/labsai/EDDI/issues

Summary

EDDI's power comes from its configurable pipeline architecture:

  • Agents are JSON configurations, not code

  • Everything flows through Conversation Memory

  • Tasks are pluggable and reusable

  • LLMs are orchestrated, not just proxied

Start simple, then add complexity as needed. The architecture scales from basic agents to sophisticated multi-API workflows.

Last updated

Was this helpful?