A production-oriented Go engine for AI-assisted decision planning, dynamic replanning, and versioned decision state.
Dynamic Decision Engine helps systems generate, rank, version, and update structured action plans based on goals, context, constraints, signals, and outcomes.
It is built for use cases where a system should not simply ask an LLM “what should we do?”, but instead maintain a traceable, versioned decision process:
- What goal are we optimizing for?
- What context and constraints were known at the time?
- Which possible moves were considered?
- Why was one action path ranked higher than another?
- What changed later?
- Did the plan get updated?
- What outcome did the previous decision produce?
The project focuses on decision infrastructure, not chatbot coaching.
How it works at a glance:
flowchart TD
G["🎯 Goal<br/>context · assets · constraints"] --> E["Decision Engine"]
E --> P["Ranked action plan<br/>moves · experiments · fallbacks · provenance"]
P --> V[("Immutable plan versions<br/>v1 → v2 → …")]
S["📡 Signal<br/>new information"] --> M{"material?"}
M -- "yes — replan" --> E
M -- "no — version stands, audited" --> V
O["✅ Outcome<br/>recorded result"] --> V
V -. "world changes" .-> S
Most LLM applications treat a recommendation as the end of the conversation: you ask "what should we do?", get a paragraph of text, and the system forgets it ever answered.
That breaks down the moment a decision matters:
- No accountability. When the plan turns out wrong, there is no record of what was known at decision time, which options were considered, or why one was ranked above another.
- No reaction to change. The world moves — a customer replies, an experiment fails, a price drops — but the one-off answer doesn't, and stale advice keeps getting followed.
- No learning. Outcomes are never recorded against the recommendation that produced them, so confidence is never tested against reality.
A real decision system treats a recommendation as a versioned, auditable object: ranked moves with explicit confidence and rationale, experiments with success signals and kill criteria, fallback options, immutable plan versions with full provenance — and replanning when a material signal arrives.
This project builds exactly that, as a clean, production-oriented backend component.
If you're building agents, decision-support tools, or anything where "why did it recommend that?" must have an answer, this is the missing layer — see Use cases for what that means concretely.
Instead of returning a single next_best_action, the engine returns ranked action paths with rationale, experiments, fallbacks, and provenance — and keeps them versioned as the world changes (see the flow above).
{
"plan_id": "plan_7K2M9Q",
"version": 1,
"summary": "Test a narrow market segment before expanding positioning.",
"ranked_moves": [
{
"rank": 1,
"title": "Test AI automation agencies as the first segment",
"confidence": 0.82,
"expected_impact": "high",
"effort": "medium",
"risk": "low",
"rationale": "This segment already understands AI agents, has short feedback cycles, and can validate the decision-infrastructure use case quickly.",
"experiment": {
"title": "Contact 30 AI automation agency founders in 7 days",
"duration_days": 7,
"success_signals": [
"5+ qualified replies",
"2+ discovery calls",
"1 paid pilot conversation"
],
"kill_criteria": [
"No qualified replies after 50 targeted contacts"
]
},
"fallback_moves": [
"Test SaaS support teams with an AI-assisted refund decision workflow",
"Reposition around approval-controlled AI agent actions"
]
}
],
"provenance": {
"planner": "mock",
"model": "deterministic",
"prompt_version": "mock-v1",
"input_snapshot_id": "snap_F3A91P",
"reasoning_summary": "The top move was selected because it maximizes learning speed while keeping execution cost low."
}
}A plan is not overwritten when new information arrives.
New signals create new immutable plan versions.
plan v1 → signal arrives → plan v2
This makes the decision process auditable and explainable.
The engine is designed to update recommendations when relevant signals arrive.
Examples:
- a customer replies
- an experiment fails
- a new market signal appears
- a constraint changes
- a metric improves or deteriorates
- a user reports a real-world outcome
The default planner is deterministic and runs without API keys.
That makes the system:
- testable
- reproducible
- usable offline
- suitable for CI
- safe to run out of the box
- private by default — in the default configuration the engine and all decision data stay on your own hardware; nothing leaves the machine, which makes it a good fit for a self-hosted home-server deployment (see Quick start)
Want real AI planning? Bring your own key. Real planners ship behind the same
interface for Anthropic Claude, OpenAI, and DeepSeek — select one with
DDE_PLANNER=anthropic|openai|deepseek (BYOK) and the matching API key
(ANTHROPIC_API_KEY / OPENAI_API_KEY / DEEPSEEK_API_KEY). Each elicits the
structured plan via a forced tool/function call and records token usage and latency
as provenance. (DeepSeek is served through the OpenAI-compatible adapter.) Note that
enabling a cloud provider sends your inputs to that provider's API; the
deterministic mock remains the default so the system runs fully local, offline, and
in CI with no key.
Local AI inference is planned. A self-hosted LLM planner (via an
OpenAI-compatible endpoint — specific provider to be decided) is on the
roadmap, so AI-assisted planning will eventually run entirely on your
own hardware with no cloud dependency. (The OpenAI-compatible adapter already
accepts a custom endpoint via DDE_LLM_BASE_URL.)
The same BYOK posture applies to market data for the investing domain: the
offline fixture provider is the default, and real vendors are one env switch
away — DDE_MARKETDATA_PROVIDER=http DDE_MARKETDATA_VENDOR=fmp,stooq chains
Financial Modeling Prep (quotes, fundamentals, EOD bars; DDE_MARKETDATA_API_KEY)
with keyless Stooq (EOD bars, availability subject to its anti-bot protection) as
fallback, behind a TTL cache and per-vendor rate limiting. A vendor outage never
kills a decision — the chain falls through and the planner degrades. Free tiers
are for dev/demo (typically non-commercial); bring your own paid key for
production use. Backtests always run on offline fixtures so they
stay reproducible and lookahead-free — see
docs/domains.md.
Multi-model planning (DDE_PLANNER=multi) composes models by role, each
recorded in provenance for auditability:
- verify — one provider proposes, a different provider critiques (drops weak moves, re-calibrates confidence). Best for calibration + auditability.
- route — a cheap model handles the common case; escalates to a stronger model on low confidence or when a material signal arrives. A cost lever.
- ensemble — several providers run in parallel; agreement on the top move scales its confidence (divergence lowers it). An uncertainty signal.
# verify: Claude proposes, GPT reviews
DDE_PLANNER=multi DDE_MULTI_MODE=verify DDE_MULTI_PROVIDERS=anthropic,openai dde serve
# keyless offline demo (ensemble of two mock planners)
DDE_PLANNER=multi DDE_MULTI_MODE=ensemble DDE_MULTI_PROVIDERS=mock,mock \
dde evaluate --input examples/founder-growth.jsonA goal's context goes stale the moment it's written. The engine can fetch fresh state (prices, availability, balances, research) right before planning and fold it into the goal context, so the planner reasons over the world now — not a snapshot from goal-creation time.
Sources sit behind one mechanism-agnostic interface: HTTP/REST, MCP servers, autonomous AI agents, and in-process read-models all look the same to the engine. A domain declares which sources it consults; non-deterministic sources (agents, live APIs) keep their non-determinism sealed inside the fetch, and every source consulted is recorded in provenance.
- Deterministic & auditable — fetched data lands in the input snapshot, so the same world-state always reproduces the same decision; raw payloads, fetch times and source identity are kept for audit.
- Opt-in & safe — off by default (
DDE_SOURCES_ENABLED), so the offline mock path stays byte-for-byte reproducible. - Never blocks — a source outage degrades to a stale contribution (optionally serving a last-good cached value); the decision is still produced.
DDE_DOMAINS=examples/domains.yaml \
DDE_SOURCES_ENABLED=true DDE_SOURCES=examples/sources.yaml \
dde evaluate --input examples/purchasing-goal.json📖 Wiring sources, the source interface, and the agentic-planner roadmap:
docs/domains.md
The project is designed as a backend system, not a prompt collection.
Implemented architecture principles:
- Go-based core
- clean domain model
- context propagation
- structured logging
- request IDs
- immutable plan versions
- decision provenance
- transactional persistence
- Postgres-backed horizontal scalability
- in-memory mode for tests and local development
- deterministic planner for reproducible tests
- REST API and CLI interface
The person, team, company, or system pursuing a goal.
The objective the system is planning around.
Examples:
- find first paying customers
- choose a technical architecture
- reduce churn
- evaluate a market entry path
- improve career leverage
- prioritize product experiments
The current known situation: facts, assets, constraints, assumptions, and available options.
A possible strategic action.
Moves are ranked by expected impact, effort, risk, confidence, and rationale.
A concrete test attached to a move.
Each experiment can define:
- duration
- success signals
- kill criteria
- expected learning
A new piece of information that may change the plan.
An external provider the engine consults before planning to enrich the goal context with live state (prices, availability, research). Where a Signal is pushed in to say "something changed, reconsider", a Data Source is pulled at decision time to answer "what is true right now". Sources are pluggable — HTTP/REST, MCP servers, AI agents, or in-process read-models — behind one interface, and each is recorded in provenance.
A real-world result produced by an executed move. It references the move by its
stable address in the immutable plan — (plan_version, move_rank) — and the move
title is snapshotted server-side, so an outcome always points at the exact move
that was acted on even after later replans regenerate the move set.
An immutable snapshot of a recommendation at a point in time.
Metadata explaining how a plan was generated, from which input snapshot, with which planner, prompt version, and model.
Three kinds of builders hit the problems this engine solves. Here is what it provides for each.
An agent that acts autonomously needs more than a chat reply — it needs a plan it can execute mechanically, abort safely, and answer for later.
- An MCP server built in —
dde mcp(stdio) or/mcp(streamable HTTP): agents create goals, fetch ranked plans, submit signals and record outcomes with zero integration code. - Machine-actionable moves — every move carries confidence, expected impact, effort, risk, explicit dependencies (a DAG, so the agent knows what can run in parallel), fallback moves, and an experiment with success signals and kill criteria it can check mechanically.
- Signal-driven replanning — when the world changes, the agent submits a signal; the engine decides whether it is material and versions the plan, instead of the agent re-prompting from scratch and losing history.
- Webhooks —
replan.completed,outcome.recordedand friends trigger downstream automation without polling.
A product that recommends actions to humans — growth experiments, operations, investing, sales — must explain its ranking, stay current as data changes, and not spam users with churn.
- Ranked alternatives, not one answer — each move carries a rationale and expected impact / effort / risk, so the UI can show why #1 beat #2.
- Domain packs — pure-data descriptors supply the domain's vocabulary,
prompt guidance, scoring and validation; four ship today (
generic,investingwith a numeric deterministic scorer over point-in-time market data,growth,career), and new domains load from a config file. - Live context — external sources (HTTP, MCP, AI agents) are fetched at decision time and folded into the input snapshot, so plans reason over the world now, not goal-creation time.
- Materiality thresholds — per-domain confidence deltas decide which signals justify a new plan version, so users see meaningful updates only.
- A measurable learning loop — outcomes are recorded against the exact
move acted on;
dde calibratefits stated confidence to observed reality, and the backtest harness scores decision quality (Brier score, kill precision/recall, noise robustness) before a scoring change ships.
After the fact — an incident review, a compliance question, a postmortem — you must reconstruct what was known, what was considered, and why one path ranked above another.
- Input snapshots — every decision records a fingerprint of exactly what the planner saw (goal, context, fetched data, signal), so the decision state at reasoning time is reproducible.
- Full provenance — planner, model, prompt version, domain-pack version, token usage, every multi-model contributor and its role, and every data source consulted, with raw payload and fetch time.
- Immutable history — plans are never overwritten; every revision is an append-only version, and immaterial signals are recorded with the reason no new version was created.
- Outcome addressing — results reference
(plan_version, move_rank), so an outcome always points at the exact move that was acted on, even after later replans regenerate the move set.
📖 Domains, the investing pack, external data sources, policy, async replanning and backtesting:
docs/domains.md
Requirements: Go 1.25+. No database or API keys required — the default store is in-memory and the default planner is deterministic.
go run ./cmd/dde evaluate --input examples/founder-growth.jsongo run ./cmd/dde servego test ./... -racedocker compose -f docker-compose.dev.yml up -d postgres
DATABASE_URL=postgres://dde:dde@localhost:5432/dde?sslmode=disable \
go run ./cmd/dde serveThis is also the self-hosting path: anyone can run the whole stack on a home server, NAS, or mini-PC that has Docker. In the default configuration all goals, plans, versions, and history stay on that machine — your data never leaves your hardware.
docker compose -f docker-compose.dev.yml up --buildA Caddy reverse proxy fronts the stack as a single
origin. Open the app at http://localhost — the browser talks to the API on
the same origin (relative /v1/*), routed by Caddy. The individual service ports
below are also published locally for debugging, but open the UI via
http://localhost (not :3000): browser write actions issue same-origin
requests and only reach the API through the proxy.
| Service | URL | Notes |
|---|---|---|
| App (use this) | http://localhost | Admin UI + API behind one origin (Caddy) |
| API | http://localhost:8080 | REST API direct (/health, /metrics) — for curl/debugging |
| Admin UI | http://localhost:3000 | Next.js server direct — SSR/debug only; browser writes need the proxy |
| Prometheus | http://localhost:9090 | Scrapes the API's /metrics |
| Grafana | http://localhost/grafana | Provisioned dashboard (admin / admin), via the proxy |
| Postgres | localhost:5432 | dde / dde |
A separate compose file runs the stack as a public, HTTPS demo where visitors bring their own LLM key. See docs/deploy.md for the full guide. In short:
cp .env.prod.example .env.prod # set DDE_DOMAIN + strong passwords
docker compose -f docker-compose.prod.yml --env-file .env.prod up -d --builddde evaluate --input examples/investing-thesis.json
dde signal --input examples/signal-update.json
dde backtest --input internal/backtest/testdata/scenario.json
dde migrate
dde serve
dde mcp
dde version📖 Full API reference — request/response payloads for every endpoint:
docs/api.md
POST /v1/evaluateGenerates a ranked action plan without persisting state.
POST /v1/goalsPOST /v1/goals/{id}/plansPOST /v1/signalsStores a signal and triggers replanning if the signal is material.
GET /v1/plans/{id}/versionsThe engine is also an MCP server: every use-case above is exposed as a Model Context Protocol tool, so MCP-capable agents (Claude Code, Claude Desktop, custom runtimes) can create goals, generate ranked plans, submit signals, record outcomes and audit the version history with zero integration code.
dde mcp # stdio — point Claude Code/Desktop at {"command": "dde", "args": ["mcp"]}
dde serve # also mounts streamable HTTP MCP at http://localhost:8080/mcpBoth transports share the engine's semantics with REST; the /mcp endpoint
shares the live service with the API.
📖 Tool reference, transports and agent-loop guidance:
docs/mcp.md
The engine can push domain events (goal.created, plan.created,
signal.received, replan.completed, outcome.recorded,
goal.status_changed) to an HTTP endpoint as they happen — Slack bridges,
n8n/Zapier flows and agent triggers react to decisions without polling.
Off by default; enable with:
DDE_WEBHOOK_URL=https://example.com/hook \
DDE_WEBHOOK_SECRET=s3cret \
dde serveDeliveries are best-effort with retries and an HMAC-SHA256 signature header
(X-DDE-Signature) for receiver-side verification.
📖 Event types, envelope format, signature verification and delivery semantics:
docs/api.md
cmd/dde
↓
internal/api
↓
internal/engine
↓
internal/llm
↓
internal/storage
↓
internal/domain
The domain layer is pure and does not depend on transport, storage, or LLM providers.
The LLM/planner layer is a replaceable boundary.
Storage supports both in-memory mode and Postgres.
For the full layering, replanning loop, and monitoring topology, see docs/architecture.md and docs/concepts.md.
Active development.
The initial core is implemented and tested:
- deterministic planner
- domain model
- CLI
- REST API
- immutable plan versions
- signal-driven replanning
- provenance
- outcome tracking
- table-driven tests with
-race - Postgres persistence with ordered migrations
- Prometheus metrics + Grafana dashboards
- a minimal Next.js admin UI
- webhook event notifications
- an MCP server (stdio + streamable HTTP)
More LLM providers, local / self-hosted inference, authentication, richer scoring, OpenTelemetry, and deeper admin workflows are planned next.
- Core domain model
- Deterministic mock planner
- CLI evaluation flow
- In-memory repository
- Postgres repository
- Ordered migrations
- Immutable plan versions
- Signal-driven replanning
- Outcome tracking
- REST API
- Table-driven tests
- GitHub Actions CI
- Prometheus metrics + Grafana dashboards
- Minimal admin UI
- Application/use-case layer with concurrency-safe replanning
- Pluggable LLM planners: Anthropic Claude, OpenAI, and DeepSeek (structured output via tool/function calls)
- Multi-model strategies: cross-model verify, cost routing, agreement ensemble (provenance-tracked)
- Multi-domain support (per-goal domain + pack registry + planner router)
- Domain packs and examples (generic, investing, growth, career)
- Investing pack: numeric scoring planner + point-in-time market data + backtesting
- Config-as-data policy (per-domain scoring/materiality tunables)
- Async, FIFO-per-plan replanning + durable signal status + plan cache (TTL for finance)
- Per-domain metrics
- Pluggable external data sources (HTTP/MCP/AI-agent/read-model; deterministic pre-fetch, provenance-tracked)
- Webhook event notifications (best-effort, HMAC-signed, retried)
- MCP server (stdio via
dde mcp+ streamable HTTP at/mcp) - Local / self-hosted LLM inference (OpenAI-compatible endpoint, provider TBD)
- Real market-data vendors (FMP + keyless Stooq, vendor chain with fallback, TTL cache, rate limiting; BYOK)
- OpenTelemetry tracing
- Authentication / authorization
- Deterministic by default
- LLMs behind interfaces
- No hidden state
- Immutable decision history
- Explicit provenance
- Structured outputs over free-form text
- Small core, extensible edges
- Production-oriented Go code
- Testable without external services
This software is provided "as is", without warranty of any kind. The Dynamic Decision Engine generates plans, rankings, and recommendations — including output produced by large language models — that may be incomplete, biased, or incorrect. It is a decision-support tool, not a substitute for human judgment.
You are solely responsible for any decision made or action taken based on its output. Use it at your own risk. The authors and contributors accept no liability for any loss or damage arising from its use. See the LICENSE for the full warranty and liability terms.
GNU Affero General Public License v3.0 (AGPL-3.0) — see LICENSE.
The AGPL is chosen deliberately: if you run a modified version of this engine as a network service, you must make your modified source available to its users.