Developer Quickstart Guide
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 → OutputEach 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.xmlis authoritative)Maven — not needed separately; the repo ships the
./mvnwwrapper (.\mvnw.cmdon 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=googleeddi.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:
A successful create returns
201with an empty body. The id is in theLocation(andX-Resource-URI) header, as aneddi://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→ id68a1…, version1.Every store has a
descriptorslisting —GET /<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.Unknown fields are rejected, not ignored. A
400naming 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
behaviorRulesis the canonical name: it is what a read returns, and what the shipped reference config and the ZIP fixtures use.rulesis still accepted on write for older clients. Before 6.4.0 a read answeredrulesregardless 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.templatinglast 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.behaviorandeddi://ai.labs.httpcallsare accepted as aliases ofai.labs.rulesandai.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":
packagesis still accepted as an alias forworkflows, so a payload using the old name is stored rather than rejected. It is deprecated — writeworkflows.
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
parametersis a free-form map, but a key no provider reads is logged as a warning at build time rather than applied.systemMessageandaddToOutputare read by the pipeline;model,baseUrl,temperature,maxTokens,topPandtopKare 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
Architecture Overview - Deep dive into design
Behavior Rules - Master decision logic
HTTP Calls - Integrate external APIs
LLM Integration - Configure LLMs (12 providers)
Output Configuration - Message and quick-reply types
Human-in-the-Loop - Gate an agent's writes on human approval
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
400with 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.404on deploy — the agent id or version does not exist.201but nothing appears in the Manager — the resource was created with an empty name. Set one viaPATCH /descriptorstore/descriptors/{id}?version=1.
Agent doesn't respond
Deployment status:
GET /administration/production/deploymentstatus/{agentId}?version=1— anything other thanREADYmeans the workflow did not load.Conversation state:
GET /agents/{conversationId}/statusFull memory snapshot (what each pipeline step actually produced):
GET /agents/{conversationId}?returnDetailed=trueServer log:
docker compose logs -f eddi, orGET /administration/logs
Rules not matching
Confirm the parser produced the expression you are matching on — the
expressions:parsedentry of the detailed snapshot above shows exactly what it emitted, andbehavior_rules:success/behavior_rules:failshow which rules matched.The dictionary has to be wired into the parser step's
extensions, not referenced as a step of its own.actionmatcherwith a comma-separated list means AND (a contiguous sublist), not OR. Use aconnectorwith"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.llmstep 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 needsEDDI_VAULT_MASTER_KEYset, or the vault is disabled.
Ollama-backed agent times out or answers nothing
From inside the
eddicontainer,localhostis the container. Usehttp://host.docker.internal:11434, or thedocker-compose.ollama.ymloverlay andhttp://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'sparametersto 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,conversationlives for the session,longTermsurvives 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?