Architecture¶
C4 Diagrams¶
See the C4 Diagrams page for all rendered diagrams.
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.