Skip to content

vingrad/dynamic-decision-engine

Repository files navigation

Dynamic Decision Engine

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
Loading

Why this exists

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.


Core idea

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).


Example output

{
  "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."
  }
}

What makes it different

1. Plans are versioned

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.


2. Replanning is signal-driven

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

3. LLMs are a boundary, not the architecture

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.json

4. Decisions can pull live data

A 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


5. Built as infrastructure

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

Core concepts

Player

The person, team, company, or system pursuing a goal.

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

Context

The current known situation: facts, assets, constraints, assumptions, and available options.

Move

A possible strategic action.

Moves are ranked by expected impact, effort, risk, confidence, and rationale.

Experiment

A concrete test attached to a move.

Each experiment can define:

  • duration
  • success signals
  • kill criteria
  • expected learning

Signal

A new piece of information that may change the plan.

Data Source

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.

Outcome

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.

Plan Version

An immutable snapshot of a recommendation at a point in time.

Provenance

Metadata explaining how a plan was generated, from which input snapshot, with which planner, prompt version, and model.


Use cases

Three kinds of builders hit the problems this engine solves. Here is what it provides for each.

AI agents that act

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 indde 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.
  • Webhooksreplan.completed, outcome.recorded and friends trigger downstream automation without polling.

Decision-support products

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, investing with 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 calibrate fits stated confidence to observed reality, and the backtest harness scores decision quality (Brier score, kill precision/recall, noise robustness) before a scoring change ships.

Anything that must answer "why did it recommend that?"

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


Quick start

Requirements: Go 1.25+. No database or API keys required — the default store is in-memory and the default planner is deterministic.

Run a local evaluation

go run ./cmd/dde evaluate --input examples/founder-growth.json

Start the API

go run ./cmd/dde serve

Run tests

go test ./... -race

Run with Postgres

docker compose -f docker-compose.dev.yml up -d postgres

DATABASE_URL=postgres://dde:dde@localhost:5432/dde?sslmode=disable \
  go run ./cmd/dde serve

Run the full stack (API + admin UI + observability)

This 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 --build

A 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

Publish a public demo (Hetzner VPS, BYOK)

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 --build

CLI

dde 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

API overview

📖 Full API reference — request/response payloads for every endpoint: docs/api.md

Stateless evaluation

POST /v1/evaluate

Generates a ranked action plan without persisting state.

Create a goal

POST /v1/goals

Generate an initial plan

POST /v1/goals/{id}/plans

Submit a signal

POST /v1/signals

Stores a signal and triggers replanning if the signal is material.

List plan versions

GET /v1/plans/{id}/versions

MCP server

The 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/mcp

Both 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


Webhooks

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 serve

Deliveries 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


Architecture

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.


Project status

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.


Roadmap

  • 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

Design principles

  • 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

Disclaimer

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.

License

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.

About

Go engine for AI decision-making: ranked action plans that version, audit, and re-plan as new signals arrive.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages