Skip to content

Architecture

C4 Diagrams

See the C4 Diagrams page for all rendered diagrams.

C4 Containers

Clean Architecture

The API follows Clean Architecture — dependencies point inward only.

┌─────────────────────────────────────────────────────┐
│ ConductorE.Api (Frameworks & Drivers)               │
│                                                     │
│  Program.cs          DI wiring, endpoint routing    │
│  Adapters/                                          │
│    MartenEventStore  Implements IEventStore          │
│    MartenIssueQuery  Implements IIssueQuery          │
│    MartenAgentQuery  Implements IAgentQuery          │
│    MartenProjections Marten-specific projections     │
│                                                     │
│  ┌─────────────────────────────────────────────┐    │
│  │ ConductorE.Core (Domain + Use Cases)        │    │
│  │                                             │    │
│  │  Domain/                                    │    │
│  │    Events.cs      Pure event records        │    │
│  │    ReadModels.cs  Pure read model records   │    │
│  │                                             │    │
│  │  Ports/                                     │    │
│  │    IEventStore    Interface                  │    │
│  │    IIssueQuery    Interface                  │    │
│  │    IAgentQuery    Interface                  │    │
│  │                                             │    │
│  │  UseCases/                                  │    │
│  │    SubmitEvent    Maps request → event       │    │
│  │                                             │    │
│  │  ⚠ ZERO external dependencies              │    │
│  └─────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────┘

Core has zero NuGet packages. No Marten, no ASP.NET, no framework code. Domain events, read models, ports, and use cases are pure C#.

Marten is only in the Api project (adapters). If we ever swap PostgreSQL for another event store, only the adapters change — Core stays untouched.

Two-Component Design

Conductor-E runs as two components in the same Kubernetes namespace:

┌──────────────────── conductor-e namespace ────────────────────┐
│                                                               │
│  ┌──────────────────┐        ┌──────────────────┐            │
│  │ Conductor-E      │  HTTP  │ Conductor-E API  │            │
│  │ (Automate-E)     │───────▶│ (.NET 10)        │            │
│  │                  │        │                  │            │
│  │ Discord bot      │        │ Domain:          │            │
│  │ GitHub MCP tools │        │   Events         │            │
│  │ Claude Haiku     │        │   ReadModels     │            │
│  │ 1-year sub token │        │ Ports:           │            │
│  └──────────────────┘        │   IEventStore    │            │
│                              │   IIssueQuery    │            │
│                              │ Adapters:        │            │
│                              │   Marten → PG    │            │
│                              └────────┬─────────┘            │
│                                       │                      │
│                              ┌────────▼─────────┐            │
│                              │ PostgreSQL 16    │            │
│                              │ Marten schema    │            │
│                              └──────────────────┘            │
└───────────────────────────────────────────────────────────────┘

Event Flow

sequenceDiagram
    participant H as Human/Agent
    participant A as Automate-E (Discord)
    participant UC as SubmitEvent (Use Case)
    participant P as IEventStore (Port)
    participant M as MartenEventStore (Adapter)
    participant PG as PostgreSQL

    H->>A: Message in #conductor-e
    A->>A: Claude processes message
    A->>UC: SubmitEventRequest
    UC->>UC: MapToEvent (domain logic)
    UC->>P: AppendAsync(streamId, event)
    P->>M: Marten session.Events.Append
    M->>PG: INSERT + inline projection update
    PG-->>M: Stored
    M-->>UC: Done
    UC-->>A: SubmitEventResponse
    A-->>H: "Issue #547 queued"

Stream Identity

String-based (not Guid):

  • Issue streams: Stig-Johnny/star-rewards#547
  • Agent streams: dev-e-1

Projections

Marten inline projections (update synchronously with event append):

Projection Source Events Key Fields
IssueStatus All lifecycle events state, agentId, prNumber, priority, attempt
AgentStatus Heartbeat, IssueAssigned, IssueDone, AgentStuck status, currentIssue, completed/failed counts

Test Coverage

Suite Tests Line Branch
Core (unit) 28 70.3% 90%
API (integration, Testcontainers PostgreSQL) 11 32.9% 25.4%
Total 39

Core coverage gap is auto-generated record methods. API coverage gap is ASP.NET framework-generated code. All business logic is covered.