Skip to the content.

Contributing to Embercore

This guide covers the development workflow for contributing to Embercore’s engine and web dashboard. For the project’s contribution policies, PR process, and code of conduct, see the root CONTRIBUTING.md.


Development Setup

Prerequisites

Tool Version Purpose
Go 1.23+ Engine development
Node.js 20+ (see .nvmrc) Web dashboard
pnpm 9+ Package manager
Anthropic API key Running E2E tests

Clone and install

git clone https://github.com/embercore-labs/embercore.git
cd embercore
pnpm install

Build the engine

cd packages/engine
make build       # produces ./embercore-engine
make test        # unit tests
make lint        # go vet + gofmt

Run the dashboard

pnpm dev         # http://localhost:3000

Full pipeline test

  1. Build the engine: cd packages/engine && make build
  2. Put embercore-engine on your PATH
  3. Start the dashboard: pnpm dev
  4. Open http://localhost:3000, paste your API key, drop a brief, and run

Code Structure Walkthrough

Engine (packages/engine/)

main.go                    MCP server entry point (registers all tools)
cmd/embercore/             CLI (plan, run, resume, status)
agents/
  athena/                  Planner — generates YAML plans from briefs
    plan.go                  PlanSpec types, ParsePlan, ValidatePlan, ToYAML
    athena.go                Athena struct, New(), GeneratePlan()
    prompt.go                System prompt builder
    extract.go               ExtractYAML() — pulls YAML from LLM response
  hermes/                  Executor — runs plans in topological order
    hermes.go                New(), ExecutePlan(), ResumePlan(), executeLayers()
    executor.go              executeStep(), buildPrompt()
    approval.go              CheckpointHandler type, WithCheckpointHandler()
  apollo/                  Copywriter — generates marketing content
    types.go                 ContentType, CopyRequest, CopyResult, Constraints, etc.
    apollo.go                New(), GenerateCopy(), Refine(), options
    prompt.go                buildSystemPrompt(), buildRefinePrompt()
    extract.go               parseResponse helpers, SEO scoring, reading level
  hephaestus/              Builder — assembles deliverables
    types.go                 OutputType, BuildRequest, BuildResult, BrandKit, etc.
    hephaestus.go            New(), Build(), BuildFromTemplate(), options
    assembler.go             assembleEmailHTML(), assembleBlogPost(), assembleSocialPack()
    templates.go             Built-in HTML/Markdown templates
  hestia/                  Memory — SQLite persistence wrapper
    hestia.go                New(), SavePlan(), StartRun(), RecordCheckpoint(), etc.
internal/
  provider/                LLM abstraction
    provider.go              Provider interface, Message, Options, Response types
    factory.go               NewFromEnv() — creates provider from env vars
    anthropic.go             Anthropic implementation
    openai.go                OpenAI implementation (supports custom base URL)
  store/                   SQLite storage layer
    store.go                 New(), Close() — opens DB, enables WAL, runs migrations
    models.go                Plan, Run, Checkpoint, Artifact, RunState structs
    migrations.go            Schema migrations (plans, runs, checkpoints, artifacts)
    plans.go                 CRUD for plans
    runs.go                  CRUD for runs
    checkpoints.go           CRUD for checkpoints
    artifacts.go             CRUD for artifacts
plan/                      Shared plan types (used by both Athena and Hermes)
  plan.go                    Spec, Step, Input, Parse(), Validate()
  toposort.go                TopoSort() — dependency-aware layer ordering
tools/                     MCP tool handlers
  prompts.go                 loadPrompt() helper
  pm_plan.go                 PMPlan() — reads brief, writes research brief
  run_research.go            RunResearch() — market research
  run_brand.go               RunBrand() — brand messaging
  run_ux.go                  RunUX() — wireframes and UX flows
  run_gtm.go                 RunGTM() — go-to-market (social + B2B in parallel)
  assemble_plan.go           AssemblePlan() — merges all outputs
  request_approval.go        RequestApproval() — checkpoint gate
prompts/                   Markdown prompt templates
  pm_agent.md                PM agent system prompt
  research_agent.md          Research output spec
  brand_agent.md             Brand messaging spec
  ux_agent.md                UX deliverable spec
  gtm_agent.md               GTM strategy spec

Dashboard (apps/web/)

src/app/
  page.tsx                 Main pipeline runner
  layout.tsx               Root layout
  agents/page.tsx          Agent directory
  plans/page.tsx           Plan browser
  plans/[id]/page.tsx      Plan detail view
  app/plan/page.tsx        Interactive pipeline runner
  api/run/route.ts         SSE pipeline execution endpoint
  api/brief/route.ts       Save product brief
  api/checkpoint/route.ts  Save checkpoint data
  api/artifact/route.ts    Fetch artifact content
src/components/
  app-shell.tsx            App shell layout
  sidebar.tsx              Navigation sidebar
src/lib/
  herald.ts                Engine subprocess client
  workspace.ts             File system workspace helpers
  mock-data.ts             Mock data for development

How to Add a New Agent

Follow these steps to add a new agent (e.g. “Artemis” for analytics):

1. Create the package

mkdir packages/engine/agents/artemis

2. Define types

Create packages/engine/agents/artemis/types.go:

package artemis

// ReportType identifies the kind of analytics report.
type ReportType string

const (
    TypeFunnel   ReportType = "funnel"
    TypeCohort   ReportType = "cohort"
)

type ReportRequest struct {
    Type  ReportType
    Brief string
    // ... your fields
}

type ReportResult struct {
    Content string
    Type    ReportType
    // ... your fields
}

3. Implement the agent

Create packages/engine/agents/artemis/artemis.go:

package artemis

import (
    "context"
    "fmt"

    "github.com/embercore-labs/embercore/packages/engine/agents/hestia"
    "github.com/embercore-labs/embercore/packages/engine/internal/provider"
)

type Artemis struct {
    provider provider.Provider
    hestia   *hestia.Hestia
    model    string
}

type Option func(*Artemis)

func WithProvider(p provider.Provider) Option {
    return func(a *Artemis) { a.provider = p }
}

func WithHestia(h *hestia.Hestia) Option {
    return func(a *Artemis) { a.hestia = h }
}

func WithModel(m string) Option {
    return func(a *Artemis) { a.model = m }
}

func New(opts ...Option) *Artemis {
    a := &Artemis{}
    for _, o := range opts {
        o(a)
    }
    return a
}

func (a *Artemis) Analyze(ctx context.Context, req ReportRequest) (*ReportResult, error) {
    if a.provider == nil {
        return nil, fmt.Errorf("artemis: provider must not be nil")
    }
    // Build prompt, call provider, parse response
    // ...
    return nil, nil
}

4. Register in KnownAgents

Edit packages/engine/agents/athena/plan.go:

var KnownAgents = map[string]bool{
    "Athena":     true,
    "Apollo":     true,
    "Hephaestus": true,
    "Hestia":     true,
    "Hermes":     true,
    "Artemis":    true, // ← add your agent
}

5. Add tests

Create packages/engine/agents/artemis/artemis_test.go:

package artemis_test

import (
    "context"
    "testing"

    "github.com/embercore-labs/embercore/packages/engine/agents/artemis"
    "github.com/stretchr/testify/require"
)

func TestAnalyze_RequiresProvider(t *testing.T) {
    a := artemis.New()
    _, err := a.Analyze(context.Background(), artemis.ReportRequest{})
    require.Error(t, err)
    require.Contains(t, err.Error(), "provider must not be nil")
}

6. (Optional) Add MCP tool

If you want the agent callable via MCP, add a handler in packages/engine/tools/ and register it in main.go’s buildServer().


How to Add a New Provider

1. Implement the interface

Create packages/engine/internal/provider/yourprovider.go:

package provider

import "context"

type YourProvider struct {
    apiKey string
    model  string
}

func NewYourProvider(apiKey string) (*YourProvider, error) {
    // validate, init client
    return &YourProvider{apiKey: apiKey}, nil
}

func (p *YourProvider) Complete(ctx context.Context, msgs []Message, opts Options) (*Response, error) {
    // Call your API, map response to Response type
    return &Response{
        Content: "...",
        Model:   p.model,
        Usage:   Usage{InputTokens: 0, OutputTokens: 0},
    }, nil
}

func (p *YourProvider) Name() string { return "yourprovider" }

2. Register in the factory

Edit packages/engine/internal/provider/factory.go, add a case in the switch:

case "yourprovider":
    key := os.Getenv("YOURPROVIDER_API_KEY")
    if key == "" {
        return nil, fmt.Errorf("provider: YOURPROVIDER_API_KEY is required")
    }
    p, err := NewYourProvider(key)
    if err != nil {
        return nil, err
    }
    if m := os.Getenv("EMBERCORE_MODEL"); m != "" {
        p.model = m
    }
    return p, nil

3. Add tests

Write unit tests that mock HTTP responses and verify correct message mapping.


Testing Guidelines

Unit tests

cd packages/engine
go test ./...            # all unit tests
go test ./agents/apollo/ # single package
go test -v -run TestGenerateCopy ./agents/apollo/  # single test

End-to-end tests

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

Web app

pnpm lint                # ESLint
pnpm build               # Type-check + build

Commit Conventions

Embercore uses Conventional Commits:

Prefix When
feat: New user-visible functionality
fix: Bug fix
docs: Documentation changes
chore: Tooling, deps, CI, config
refactor: Code restructure, no behaviour change
test: Adding or fixing tests
perf: Measurable performance improvement

Optional scope: feat(web): add cancel button on plan page

PRs are squash-merged — the PR title becomes the commit message.


PR Process

  1. Fork the repo, branch off main: <type>/<short-description> (e.g. feat/add-artemis-agent)
  2. Make small, focused changes — one concern per PR
  3. Write or update tests for code changes
  4. Run linters locally before pushing:
    pnpm lint                      # web
    cd packages/engine && make lint # engine
    
  5. Open a PR using the template — fill in What, Why, How Tested, and the checklist
  6. Wait for review (response within 3 working days)

What’s in scope

What’s out of scope

See CONTRIBUTING.md for full details.


See Also