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:
internal/packages are not importable from outside the engine module.plan/contains shared types used by both Athena and Hermes.- Agents depend on
providerandstorebut never on each other (except through Hestia). tools/(MCP handlers) depend on agents and prompts.
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
- stdio (default): Reads from stdin, writes to stdout. Works with Claude Desktop, Cursor, VS Code.
- Streamable HTTP (
-httpflag): Serves at<addr>/mcp. Compatible with Claude.ai remote MCP.
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
- Next.js 15 with App Router
- React 19 + TypeScript
- Tailwind CSS 4 for styling
- Communicates with the engine binary via subprocess (
herald.ts)
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
- Local-first: All data stays on your machine in SQLite. No cloud services, no telemetry.
- BYOK: API keys are never stored by Embercore — they come from env vars at runtime.
- Human-in-the-loop: Every plan execution pauses at checkpoints for approval. The
--auto-approveflag is opt-in. - Deterministic fallbacks: Hephaestus can assemble deliverables without an LLM using built-in templates and assembler functions.
- MCP-native: The engine is an MCP server first, CLI second. This makes it composable with any MCP-compatible AI tool.
- Pure-Go SQLite: Uses
modernc.org/sqliteto avoid CGo — the binary is fully static and cross-compilable.
See Also
- Getting Started — install and first run
- Agent Reference — API details for each agent
- MCP Integration — client configuration
- Contributing — development workflow