10 KiB
adr_number, title, status_history, tier, authors, superseded_by, related_adrs, acceptance
| adr_number | title | status_history | tier | authors | superseded_by | related_adrs | acceptance | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | Layered Architecture |
|
1 |
|
null |
|
|
Context
CleverAgents is a complex AI-powered development assistant that must support two deployment modes (local CLI and multi-user server), multiple LLM providers, swappable storage backends, and a rich domain model spanning plans, decisions, actors, tools, and resources. Without a clear structural decomposition, these concerns would become entangled — making the system difficult to test, extend, and maintain.
The architecture must enforce strict boundaries so that domain logic remains independent of infrastructure choices (which database, which LLM provider, which vector store), and so that the presentation layer (CLI, TUI, A2A Server Endpoint) can evolve without affecting core business rules.
Decision Drivers
- Must support two deployment modes (local CLI and multi-user server) sharing the same core logic
- Domain logic must remain independent of infrastructure choices (database, LLM provider, vector store)
- Presentation surfaces (CLI, TUI, A2A Server Endpoint) must evolve without affecting business rules
- Need strict boundaries between layers to enable independent testing and swappable infrastructure
- System spans multiple complex subsystems (plans, decisions, actors, tools, resources) requiring clear structural decomposition
- Must support CQRS data flow and event-driven observability without entangling concerns
Decision
CleverAgents adopts a four-layer architecture — Presentation, Application, Domain, and Infrastructure — organized according to hexagonal architecture (ports and adapters) principles. The architecture incorporates Command Query Responsibility Segregation (CQRS) for data flow and an event-driven pattern for observability and decoupled side effects.
The strict dependency rule is: outer layers depend on inner layers, never the reverse. Cross-cutting concerns (logging, configuration, security) are handled through dependency injection and aspect-oriented patterns.
Design
Layer Definitions
Presentation Layer — The outermost layer, responsible for user interaction and external API surfaces. Contains the Typer CLI, Textual TUI, Textual Web interface, IDE plugin (embedded TUI), and A2A Server Endpoint (JSON-RPC 2.0). All presentation components call into the Application Layer through service facades. No business logic resides here.
Application Layer — Orchestrates use cases by coordinating domain objects and infrastructure services. Contains:
- Service Facades:
PlanService,ProjectService,ActorService,ContextService,ToolService,SkillService,ResourceService. Each facade provides a use-case-oriented API consumed by the Presentation Layer. - Workflow Engine:
PlanLifecycle,SessionWorkflow,CorrectionFlow,MergeWorkflow. These implement the multi-step orchestration logic for plan execution, correction, and merging. - Event Bus:
StructuredLog,EventEmitter,MetricsCollector. Receives domain events and routes them to observers. - DI Container: The
dependency-injectorDeclarativeContainerthat wires all components.
Domain Layer — The innermost layer, containing all business rules, domain models, domain services, and domain events. Contains:
- Domain Models:
Plan,Decision,Project,Resource,Actor,Action,Tool,Skill,Session,Invariant,AutomationProfile,Checkpoint,CorrectionAttempt. - Domain Services:
InvariantEnforcer,AutonomyController,ContextBuilder,MergeResolver. These services define repository interfaces asProtocolclasses (ports) — they never reference concrete implementations. - Domain Events:
PlanCreated,PhaseChanged,DecisionMade,ToolInvoked,ApplyCompleted,CorrectionRequested.
Infrastructure Layer — Provides concrete implementations of the ports defined in the Domain Layer. Contains:
- Database: SQLite/SQLAlchemy, Alembic migrations, Unit of Work.
- Indexing: Tantivy (full-text), FAISS (vector), Neo4j/rdflib (graph), Qdrant (alternative vector).
- Sandbox: Git worktree, filesystem copy, transaction rollback, no-op strategies.
- LLM/AI Runtime: LangChain, LangGraph
StateGraph, provider registry, RxPY bridge, MCP SDK. - External Integrations: MCP servers, Agent Skills Standard, REST/gRPC clients.
- LSP Runtime: Language Server Registry,
LSPToolAdapter, language server lifecycle management. LSP servers are attached to actors to provide language intelligence (diagnostics, type info, completions, references) — see ADR-027. - File System/OS: Watchdog, file I/O, Git CLI, subprocess management.
Hexagonal Architecture (Ports and Adapters)
The Domain Layer defines abstract repository interfaces (ports) using Python Protocol classes. The Infrastructure Layer provides concrete implementations (adapters). This indirection enables swapping SQLite for PostgreSQL, FAISS for Qdrant, or local execution for remote server execution without modifying domain logic.
CQRS
Write operations (plan creation, decision recording, resource modification) flow through command handlers that enforce invariants and emit domain events. Read operations (plan status queries, decision tree visualization, context queries) use optimized read paths that may bypass the ORM for performance.
Event-Driven Architecture
All significant state changes emit domain events through a structured event bus. Events flow through RxPY reactive streams for real-time processing and through the structured logging pipeline for persistence. This enables decoupled observability, audit logging, and future webhook/notification systems.
Deployment Modes
Both local and server modes share the same Domain and Application layers. The Infrastructure Layer provides swappable implementations for each deployment target:
- Local Mode: Single-process CLI, SQLite database, all components in-process.
- Server Mode: Multi-user service, CLI as thin client over HTTPS, shared storage and namespace resolution.
Constraints
- Outer layers depend on inner layers, never the reverse. No import from
infrastructureindomain. - The Domain Layer must have zero dependencies on infrastructure libraries (no SQLAlchemy imports, no LangChain imports, no HTTP client imports).
- Repository interfaces in the Domain Layer are defined as
Protocolclasses, not abstract base classes tied to any framework. - All service dependencies are wired through the DI container — no direct instantiation of infrastructure components in application or domain code.
- Presentation Layer components must not call Domain or Infrastructure layers directly; they must go through Application Layer service facades.
Consequences
Positive
- Infrastructure components (database, LLM provider, vector store, sandbox strategy) can be swapped without modifying domain logic.
- The domain model can be tested in complete isolation with mock implementations of all ports.
- Local and server deployment modes share the same core logic, reducing duplication.
- New presentation surfaces (TUI, web, IDE plugin) can be added by implementing against the existing service facade API.
Negative
- The port/adapter indirection adds boilerplate — every repository requires a protocol definition in the domain and a concrete implementation in infrastructure.
- Developers must understand the layer boundaries to place new code correctly.
- CQRS adds complexity for operations that are naturally both read and write (e.g., "read current state, decide, write").
Risks
- Layer violations may creep in without automated enforcement, especially under time pressure.
- The event bus could become a hidden coupling mechanism if events carry too much behavioral logic.
Alternatives Considered
None — specification-driven requirement. The four-layer hexagonal architecture with CQRS and event-driven patterns is prescribed by the specification as the foundational structural pattern.
Compliance
- Import linting: CI rules enforce that
domainmodules never import frominfrastructureorpresentation. Ruff custom rules or import-linter configuration checks are run on every commit. - Dependency injection audit: The DI container configuration is reviewed to ensure all cross-layer dependencies flow through declared providers, not direct imports.
- Architecture tests: Dedicated test suite verifying layer dependency rules using static analysis (e.g.,
import-linteror custom AST checks). - Code review: Pull requests that introduce new cross-layer dependencies require explicit justification.