Files
cleveragents-core/docs/adr/ADR-001-layered-architecture.md
freemo d5b122d4a3
CI / lint (push) Successful in 14s
CI / quality (push) Successful in 21s
CI / security (push) Successful in 36s
CI / build (push) Successful in 36s
CI / typecheck (push) Successful in 39s
CI / benchmark-regression (push) Has been skipped
CI / unit_tests (push) Successful in 2m57s
CI / integration_tests (push) Successful in 3m23s
CI / docker (push) Successful in 53s
CI / coverage (push) Successful in 5m58s
CI / benchmark-publish (push) Successful in 19m27s
Docs: Updated to A2A and integrating rest standard
2026-03-11 13:19:55 -04:00

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
2026-02-16
Draft
Jeffrey Phillips Freeman
2026-02-16
Proposed
Jeffrey Phillips Freeman
2026-02-16
Accepted
Jeffrey Phillips Freeman
1
Jeffrey Phillips Freeman
null
number title relationship
3 Dependency Injection Provides the composition root that wires components across all four layers
number title relationship
5 Technical Stack Defines the concrete technologies used in each architectural layer
number title relationship
19 Storage and Persistence Implements the Infrastructure Layer's persistence adapters
number title relationship
22 LangChain/LangGraph Integration Implements the Infrastructure Layer's LLM runtime adapters
number title relationship
26 Agent-to-Agent Protocol (A2A) Defines the versioned protocol boundary between the Presentation and Application layers; all client-to-backend communication flows through A2A
number title relationship
47 A2A Standard Adoption Adopts the external A2A standard (JSON-RPC 2.0) as the sole client-server protocol, replacing the previous REST/FastAPI approach
number title relationship
48 Server Application Architecture Defines the server application that implements the A2A endpoint, sharing Domain and Application layers with the client
votes_for votes_against abstentions
voter comment
Jeffrey Phillips Freeman <Jeffrey.Freeman@CleverThis.com> Four-layer separation is the right foundation to keep domain logic portable across deployment modes

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-injector DeclarativeContainer that 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 as Protocol classes (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 infrastructure in domain.
  • 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 Protocol classes, 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 domain modules never import from infrastructure or presentation. 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-linter or custom AST checks).
  • Code review: Pull requests that introduce new cross-layer dependencies require explicit justification.