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#issueNumberfor issues,agentIdfor 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.
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:
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 testbefore 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.