Skip to the content.

Architecture Guide

This document describes how Embercore is structured, how data flows through the system, and how key subsystems work.


High-Level Overview

┌─────────────────────────────────────────────────────────────────────┐
│                         Entry Points                                │
│                                                                     │
│   CLI (cobra)              MCP Server (stdio/HTTP)     Web Dashboard│
│   embercore plan/run       embercore-engine             Next.js     │
│   └──────────┬──────────┘  └──────────┬───────────┘    └─────┬─────┘
│              │                        │                      │      │
│              ▼                        ▼                      ▼      │
│   ┌─────────────────────────────────────────────────────────────┐   │
│   │                      Agent Layer                            │   │
│   │                                                             │   │
│   │   Athena ──▶ Hermes ──▶ Apollo / Hephaestus                │   │
│   │                ▲                    │                        │   │
│   │                └────── Hestia ◄─────┘                       │   │
│   └─────────────────────────────────────────────────────────────┘   │
│                               │                                     │
│                               ▼                                     │
│   ┌─────────────────────────────────────────────────────────────┐   │
│   │                    Infrastructure                           │   │
│   │                                                             │   │
│   │   Provider (Anthropic/OpenAI)    Store (SQLite + WAL)      │   │
│   └─────────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────────┘

Package Layout

embercore/
├── apps/
│   └── web/                    # Next.js dashboard (TypeScript)
│       ├── src/app/            # App Router pages
│       │   ├── page.tsx        # Pipeline runner (main UI)
│       │   ├── agents/         # Agent directory
│       │   ├── plans/          # Plan browser + detail view
│       │   ├── app/plan/       # Interactive pipeline runner
│       │   └── api/            # API routes (run, brief, checkpoint, artifact)
│       ├── src/components/     # Shared UI components (AppShell, Sidebar)
│       └── src/lib/            # Utilities (workspace, herald client, mock data)
│
├── packages/
│   └── engine/                 # Go engine (core)
│       ├── main.go             # MCP server entry point (stdio + HTTP)
│       ├── cmd/embercore/      # CLI commands (plan, run, resume, status)
│       ├── agents/
│       │   ├── athena/         # Planner agent
│       │   ├── hermes/         # Execution orchestrator
│       │   ├── apollo/         # Copy/creative writing agent
│       │   ├── hephaestus/     # Builder/assembly agent
│       │   └── hestia/         # Memory/persistence agent
│       ├── internal/
│       │   ├── provider/       # LLM provider abstraction (Anthropic, OpenAI)
│       │   ├── store/          # SQLite storage layer
│       │   └── logger/         # Structured logging
│       ├── plan/               # Shared plan types (Spec, Step, TopoSort)
│       ├── tools/              # MCP tool handlers
│       ├── prompts/            # Agent prompt templates (Markdown)
│       └── e2e/                # End-to-end tests + fixtures
│
└── docs/                       # Documentation (you are here)

Dependency graph

cmd/embercore ──▶ agents/* ──▶ internal/provider
                     │              │
                     ▼              │
                  plan/ ◄───────────┘
                     │
                     ▼
               internal/store

tools/ ──▶ agents/* ──▶ internal/provider
  │            │
  ▼            ▼
prompts/   internal/store

Key rules:


Data Flow

Full pipeline

1. User provides a Brief (text or file)
           │
           ▼
2. Athena.GeneratePlan(brief, opts)
   ├── Calls LLM with planning prompt
   ├── Parses YAML response
   ├── Validates (IDs, deps, cycles, known agents)
   └── Returns PlanSpec
           │
           ▼
3. Hestia.SavePlan(name, yaml, brief)
   └── Persists to SQLite → returns plan ID
           │
           ▼
4. Hermes.ExecutePlan(spec, opts)
   ├── Hestia.StartRun(planID) → run ID
   ├── plan.TopoSort(checkpoints) → layers
   └── For each layer:
       └── For each step:
           ├── executeStep(step) → calls LLM
           ├── CheckpointHandler(step, output)
           │   ├── approved → continue
           │   └── rejected → pause run
           └── Hestia.RecordCheckpoint(...)
           │
           ▼
5. Apollo.GenerateCopy / Hephaestus.Build
   └── Produces content artifacts
           │
           ▼
6. Hestia.CreateArtifact(...)
   └── Persists final deliverables

Plan YAML structure

name: 'product-launch'
version: '1'
description: 'Full product launch campaign'
agents_used:
  - Athena
  - Apollo
  - Hephaestus
inputs:
  - name: product_brief
    type: string
    required: true
    description: 'Product description and goals'
checkpoints:
  - id: research
    agent: Apollo
    action: 'Market research and competitor analysis'
    outputs: [research_report.md]
  - id: brand-copy
    agent: Apollo
    action: 'Generate brand messaging and positioning'
    depends_on: [research]
    outputs: [brand_guide.md]
  - id: email-campaign
    agent: Hephaestus
    action: 'Build HTML email sequence'
    depends_on: [brand-copy]
    outputs: [emails.html]

Topological execution

Steps are sorted into layers based on depends_on. Steps within the same layer have no mutual dependencies and could run in parallel (currently executed sequentially).

Layer 0: [research]              ← no dependencies
Layer 1: [brand-copy]            ← depends on research
Layer 2: [email-campaign]        ← depends on brand-copy

Storage

Database

Embercore uses SQLite with WAL mode for concurrent read performance. The database is created automatically at ~/.embercore/data.db (overridable with --db).

The storage layer (internal/store) uses the pure-Go SQLite driver modernc.org/sqlite — no CGo required.

Schema

Four core tables, managed by auto-running migrations:

plans

Column Type Description
id TEXT PRIMARY KEY UUID
name TEXT Plan name
description TEXT Optional description
source_brief TEXT Original brief text
yaml_content TEXT Full YAML plan definition
status TEXT "draft", "active", "completed", "failed"
created_at DATETIME Creation timestamp
updated_at DATETIME Last update timestamp

runs

Column Type Description
id TEXT PRIMARY KEY UUID
plan_id TEXT FK to plans.id
status TEXT "running", "paused", "completed", "failed"
current_step TEXT ID of the step currently executing
started_at DATETIME When the run started
completed_at DATETIME When the run finished
error TEXT Error message if failed

checkpoints

Column Type Description
id TEXT PRIMARY KEY UUID
plan_id TEXT FK to plans.id
step_id TEXT Step ID from the plan
agent TEXT Agent that executed this step
status TEXT "pending", "completed", "approved"
input_data TEXT Input passed to the step
output_data TEXT Output produced by the step
approved_by TEXT Who approved this checkpoint
approved_at DATETIME When it was approved
created_at DATETIME Creation timestamp

artifacts

Column Type Description
id TEXT PRIMARY KEY UUID
run_id TEXT FK to runs.id
checkpoint_id TEXT FK to checkpoints.id
type TEXT Artifact type (e.g. "email-html", "blog-post")
name TEXT Human-readable name
content TEXT Full artifact content
created_at DATETIME Creation timestamp

Models

The Go models map directly to these tables. See internal/store/models.go:

type Plan struct {
    ID, Name, Description, SourceBrief, YAMLContent, Status string
    CreatedAt, UpdatedAt time.Time
}

type Run struct {
    ID, PlanID, Status, CurrentStep, Error string
    StartedAt, CompletedAt *time.Time
}

type Checkpoint struct {
    ID, PlanID, StepID, Agent, Status, InputData, OutputData, ApprovedBy string
    ApprovedAt *time.Time
    CreatedAt  time.Time
}

type Artifact struct {
    ID, RunID, CheckpointID, Type, Name, Content string
    CreatedAt time.Time
}

// Composite view for run state
type RunState struct {
    Run         Run
    Plan        Plan
    Checkpoints []Checkpoint
    Artifacts   []Artifact
}

Provider Abstraction

Embercore uses a BYOK (Bring Your Own Key) pattern. You supply API keys via environment variables; the engine creates the appropriate provider at startup.

Provider interface

type Provider interface {
    Complete(ctx context.Context, messages []Message, opts Options) (*Response, error)
    Name() string
}

Supported providers

Provider Env Vars Default Model
Anthropic ANTHROPIC_API_KEY Claude Sonnet
OpenAI OPENAI_API_KEY, optional OPENAI_BASE_URL GPT-4o

The OPENAI_BASE_URL variable enables any OpenAI-compatible API (Ollama, Together, Groq, etc.).

Factory

// NewFromEnv creates a Provider based on environment variables.
// EMBERCORE_PROVIDER = "anthropic" (default) | "openai"
// EMBERCORE_MODEL    = optional model override
p, err := provider.NewFromEnv()

Message types

type Message struct {
    Role    string // "user", "assistant", "system"
    Content string
}

type Options struct {
    Model       string
    MaxTokens   int
    Temperature float64
    System      string
    StopSeqs    []string
}

type Response struct {
    Content    string
    Model      string
    Usage      Usage  // InputTokens, OutputTokens
    StopReason string
}

MCP Integration

Embercore’s primary interface is the Model Context Protocol (MCP). The engine binary (main.go) registers 8 tools on an MCP server:

Tool Description
run_workflow Full multi-agent pipeline (PM → Research → Brand → UX → GTM → Assemble)
pm_plan PM Agent: reads product brief, writes focused brief for research
run_research Market research, ICP classification, competitor analysis
run_brand Positioning, brand voice, messaging pillars
run_ux Wireframe briefs, screen lists, user flows
run_gtm Social media + B2B outreach (runs in parallel)
request_approval Human-in-the-loop checkpoint gate
assemble_plan Assembles all stage outputs into final_product_plan.md

Transports

See MCP Integration Guide for full configuration examples.


Web Dashboard

The Next.js dashboard (apps/web/) provides a visual interface for Embercore:

Pages

Route Purpose
/ Interactive pipeline runner — run stages, review checkpoints
/plans Browse all saved plans with status badges
/plans/[id] Plan detail with step graph, status, and checkpoint history
/agents Agent directory
/app/plan Alternative pipeline runner with stage-by-stage controls

API Routes

Route Method Purpose
/api/run POST SSE endpoint — streams pipeline execution events
/api/brief POST Saves product brief to workspace
/api/checkpoint POST Saves checkpoint artifacts
/api/artifact GET Fetches artifact content

Stack


Testing Strategy

Unit tests

cd packages/engine
make test          # runs go test ./...

Unit tests live next to the code they test (_test.go convention). The engine uses github.com/stretchr/testify for assertions.

End-to-end tests

cd packages/engine
go test -tags e2e ./e2e/

E2E tests require a live API key (ANTHROPIC_API_KEY). They use YAML fixture plans in e2e/ (e.g. newsletter.yaml, launch-campaign.yaml) and exercise the full pipeline.

Linting

make lint          # go vet + gofmt check
pnpm lint          # web app ESLint

Key Design Decisions

  1. Local-first: All data stays on your machine in SQLite. No cloud services, no telemetry.
  2. BYOK: API keys are never stored by Embercore — they come from env vars at runtime.
  3. Human-in-the-loop: Every plan execution pauses at checkpoints for approval. The --auto-approve flag is opt-in.
  4. Deterministic fallbacks: Hephaestus can assemble deliverables without an LLM using built-in templates and assembler functions.
  5. MCP-native: The engine is an MCP server first, CLI second. This makes it composable with any MCP-compatible AI tool.
  6. Pure-Go SQLite: Uses modernc.org/sqlite to avoid CGo — the binary is fully static and cross-compilable.

See Also