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
- Build the engine:
cd packages/engine && make build - Put
embercore-engineon your PATH - Start the dashboard:
pnpm dev - 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
- Use
testify/requirefor assertions - Place tests in
_test.gofiles next to the code - Mock the
Providerinterface — don’t call real APIs in unit tests
End-to-end tests
cd packages/engine
go test -tags e2e ./e2e/
- Requires
ANTHROPIC_API_KEYset in the environment - Uses YAML fixtures in
e2e/(e.g.newsletter.yaml,launch-campaign.yaml) - Tests the full pipeline: plan → execute → artifact
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
- Fork the repo, branch off
main:<type>/<short-description>(e.g.feat/add-artemis-agent) - Make small, focused changes — one concern per PR
- Write or update tests for code changes
- Run linters locally before pushing:
pnpm lint # web cd packages/engine && make lint # engine - Open a PR using the template — fill in What, Why, How Tested, and the checklist
- Wait for review (response within 3 working days)
What’s in scope
- Documentation, accessibility, engine resilience, MCP client recipes, prompt improvements, examples
What’s out of scope
- Auto-posting to social platforms
- Paid tiers / billing / accounts
- Chat-as-front-door UX
- New LLM providers before v0.5
See CONTRIBUTING.md for full details.
See Also
- Getting Started — set up your dev environment
- Architecture Guide — understand the system design
- Agent Reference — API details for each agent
- MCP Integration — MCP tool reference