Skip to content

Patterns & Principles

Architecture Patterns

1. Clean Architecture

Dependencies point inward only. The Core project has zero external dependencies — no Marten, no ASP.NET, no framework code.

Layer Project Depends On
Domain ConductorE.Core/Domain/ Nothing
Ports ConductorE.Core/Ports/ Domain
Use Cases ConductorE.Core/UseCases/ Domain + Ports
Adapters ConductorE.Api/Adapters/ Ports + Marten
Controllers ConductorE.Api/Program.cs Use Cases + Ports

If we swap PostgreSQL for another store, only the adapters change. Core stays untouched.

2. Event Sourcing

Every action emits an immutable event. Events are the source of truth — current state is derived from replaying events.

  • Append-only: events are never modified or deleted
  • Inline projections: read models (IssueStatus, AgentStatus) update synchronously with event appends
  • String streams: repo#issueNumber for issues, agentId for agents
  • Full audit trail: replay events to see exactly what happened, when, and by whom

We use Marten on PostgreSQL, same pattern as the Tablez platform.

3. Ports & Adapters

Use cases depend on interfaces (ports), not implementations. Adapters implement ports and live in the outer layer.

Use Case → IEventStore (port) → MartenEventStore (adapter) → PostgreSQL

This enables:

  • Unit testing with FakeEventStore (no database needed)
  • Swapping infrastructure without changing business logic
  • Clear boundaries between domain and framework code

4. Adapter Pattern

Platform-specific concerns are abstracted behind unified interfaces. Applied in Automate-E for multi-platform messaging:

Discord Adapter ─┐
                  ├─→ Message Handler (platform-agnostic) → Agent Loop
Slack Adapter ───┘

Same agent code works on Discord or Slack — change messaging.platform in config, no code changes.

5. Configuration over Code

Agents are defined by character.json configuration, not by writing new code. Automate-E is the shared runtime — one Docker image serves all agents.

{
  "name": "Conductor-E",
  "messaging": { "platform": "discord" },
  "llm": { "model": "claude-haiku-4-5-20251001" },
  "tools": [...]
}

New agent = new config file + optional backend API. No code changes to the runtime.

Design Principles

1. SOLID

Principle Application
Single Responsibility Each use case does one thing. SubmitEvent maps and appends — nothing else.
Open/Closed Add new event types by adding records to Domain, no existing code modified.
Liskov Substitution FakeEventStore substitutes MartenEventStore in tests.
Interface Segregation IEventStore, IIssueQuery, IAgentQuery — three focused interfaces, not one mega-interface.
Dependency Inversion Use cases depend on IEventStore (abstraction), not MartenEventStore (implementation).

2. YAGNI

Build only what's needed now. Don't add abstractions, features, or configurability for hypothetical future requirements.

  • Three lines of similar code is better than a premature abstraction
  • No feature flags or backwards-compatibility shims
  • If it's not in the current issue, it doesn't go in the PR

3. TDD

Write tests before or alongside implementation. Every use case has test coverage. Target:

  • Unit tests for all domain logic and use cases (Core project)
  • Integration tests for API endpoints with real PostgreSQL (Testcontainers)
  • Run dotnet test before every push

4. Separation of Concerns

The agent that produces a thing cannot approve that thing. This is structural, not cultural.

Agent Can Do Cannot Do
Dev-E Write code, create PRs Approve its own PRs
Review-E Review code, approve/reject Write implementation code
Conductor-E Assign work, escalate Write code or review code

Operational Rules

1. Fix Forward

When production breaks, fix forward. Never auto-rollback.

Production breaks → Agent attempts fix → If failed, reassign
  → If failed again → Escalate to CTO
  → CTO decides: fix forward or rollback (human decision only)

Rollback is never automatic. That's always a CTO decision.

2. Two Strikes Then Human

If an agent fails the same issue twice (two different attempts), escalate to human. No third automatic attempt.

Strike Action
1st failure AGENT_STUCK → reassign to different agent, fresh branch
2nd failure ESCALATED → post to Discord #admin, wait for human

3. Diagram-First

Create C4 diagrams before coding complex systems. If the diagram is complex, the code will be complex — simplify the diagram first.

Level When to Create
L1 Context Before starting a new system
L2 Containers Before adding services or databases
L3 Components Before refactoring internal architecture
L4 Flow Before implementing complex interactions

Use PlantUML for C4 diagrams. Mermaid for everything else (flows, state machines, timelines).

4. Event-Driven Coordination

Agents communicate through events, not direct calls. The event store is the shared nervous system.

Dev-E emits WORK_STARTED → Event Store → Conductor-E reads → assigns next
Dev-E emits PR_CREATED → Event Store → Conductor-E reads → monitors review
Review-E approves → GitHub → Conductor-E reads → auto-merge

No agent calls another agent directly. All coordination flows through events.