Files
cleveragents-core/docs/adr/ADR-048-server-application-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

229 lines
14 KiB
Markdown

---
adr_number: 48
title: "Server Application Architecture"
status_history:
- ["2026-03-11", "Draft", "Jeffrey Phillips Freeman"]
- ["2026-03-11", "Proposed", "Jeffrey Phillips Freeman"]
- ["2026-03-11", "Accepted", "Jeffrey Phillips Freeman"]
tier: 4
authors: ["Jeffrey Phillips Freeman"]
superseded_by:
related_adrs:
- number: 1
title: "Layered Architecture"
relationship: "The server shares the same Domain and Application layers as the client; only Infrastructure and Presentation layers differ"
- number: 22
title: "LangChain/LangGraph Integration"
relationship: "The server deploys actor StateGraphs to LangGraph Platform and invokes them via RemoteGraph"
- number: 23
title: "Server Mode"
relationship: "Refines ADR-023's server mode design with concrete architecture — LangGraph Platform for execution, A2A standard for protocol, PostgreSQL for persistence"
- number: 26
title: "Agent-to-Agent Protocol (A2A)"
relationship: "The server implements the A2A standard endpoint; ADR-026 defines the protocol boundary role"
- number: 47
title: "A2A Standard Adoption"
relationship: "ADR-047 defines the protocol the server speaks (A2A standard + extensions); this ADR defines the server that implements it"
acceptance:
votes_for:
- voter: "Jeffrey Phillips Freeman <Jeffrey.Freeman@CleverThis.com>"
comment: "A separate server application sharing domain/application layers with the client, backed by LangGraph Platform for remote execution, provides clean separation of concerns while maximizing code reuse"
votes_against: []
abstentions: []
---
## Context
ADR-023 established that CleverAgents supports local and server deployment modes sharing Domain and Application layers, differing only in Infrastructure and Presentation. ADR-047 adopted the A2A standard as the sole client-server protocol. What remains unspecified is the concrete architecture of the server application itself — how it is structured, how it executes actor graphs remotely, how it manages multi-user state, and how it integrates with the shared layers.
## Decision Drivers
- The server must share the same Domain and Application layers as the client to avoid behavioral drift (ADR-001, ADR-023)
- Remote actor graph execution must not limit plan capabilities — different actors for different plan phases (strategy, execution) must each deploy independently
- The server must speak the A2A standard protocol (ADR-047) as its sole client-facing interface
- Multi-user persistence requires PostgreSQL (not SQLite) with proper isolation and namespace-scoped authorization
- The server must be deployable to Kubernetes via Helm charts for production environments
- LangGraph Platform is the established graph execution engine (ADR-022) and provides RemoteGraph for remote invocation
## Decision
The CleverAgents server is a **separate application** (distinct repository/deployment unit) that shares the same Domain and Application layers as the client. It implements the A2A standard endpoint (JSON-RPC 2.0 over HTTP) for all client communication, delegates actor graph execution to **LangGraph Platform** via **RemoteGraph**, and uses PostgreSQL for multi-user persistence.
## Design
### Server Application Structure
The server is organized following the same four-layer architecture as the client (ADR-001), sharing two layers and providing its own implementations for the other two:
| Layer | Client | Server | Shared? |
|-------|--------|--------|---------|
| **Domain** | Business rules, domain models, domain events | Same | Yes — identical package |
| **Application** | Service facades, workflows, event bus | Same | Yes — identical package |
| **Infrastructure** | SQLite, local sandbox, local file access, `A2aLocalFacade` | PostgreSQL, LangGraph Platform RemoteGraph, server-side sandbox, A2A SDK server | No — different implementations |
| **Presentation** | CLI (Typer), TUI (Textual), IDE plugin | A2A JSON-RPC 2.0 endpoint | No — different entry points |
The shared Domain and Application layers are consumed as a Python package dependency. This ensures zero behavioral drift between local and server modes.
### Presentation Layer: A2A Endpoint
The server's Presentation layer is an **A2A JSON-RPC 2.0 endpoint** implemented using the A2A Python SDK's server-side connection handler. This is the **only** client-facing interface — there is no REST API, no GraphQL, no separate admin endpoint.
The endpoint handles:
- **Standard A2A methods**: `message/send`, `message/stream` — routed to `SessionWorkflow` and actor execution via Tasks
- **Extension methods**: `_cleveragents/*` — routed to Application-layer services (PlanService, RegistryServices, SyncService, etc.)
- **Multi-turn interaction**: Tasks entering `input-required` state prompt the connected client for decisions or permissions
### Infrastructure Layer: LangGraph Platform
Actor graphs (StateGraphs defined in YAML) are **deployed to LangGraph Platform** as separate deployments. The server invokes them via **RemoteGraph**:
```
Client ──A2A──→ Server A2A Endpoint
├── SessionWorkflow (Application Layer)
│ │
│ └── RemoteGraph.invoke(actor_graph, state)
│ │
│ └── LangGraph Platform (remote actor execution)
│ ├── Strategy Actor Graph
│ ├── Execution Actor Graph
│ └── Estimation Actor Graph
└── Extension Method Handlers
├── PlanService
├── RegistryServices
├── SyncService
└── NamespaceService
```
This does **not** limit plan execution. Different actors for different plan phases (strategy vs. execution vs. estimation) each deploy as **separate RemoteGraphs**. The `SessionWorkflow` orchestrates them the same way it orchestrates local actor graphs, but through the RemoteGraph interface instead of direct in-process invocation.
### Infrastructure Layer: Persistence
| Component | Technology | Notes |
|-----------|-----------|-------|
| Database | PostgreSQL via SQLAlchemy | Same ORM as client (SQLite), different dialect |
| Migrations | Alembic | Schema migrations for server-specific tables (users, tokens, namespace ACLs) |
| Session store | PostgreSQL | Multi-user session persistence with namespace isolation |
| Plan store | PostgreSQL | Plan lifecycle records, decision trees, artifacts |
The SQLAlchemy repository implementations are shared between client and server where the schema is identical. Server-specific tables (authentication, namespace ACLs, user management) are additive.
### Infrastructure Layer: Additional Components
| Component | Technology | Purpose |
|-----------|-----------|---------|
| Logging | structlog | Structured logging consistent with client (ADR-025) |
| Caching | Redis (optional) | Multi-instance session affinity, rate limiting. Single-instance deployments can use in-memory caching. |
| Task queue | None (synchronous) | Plans execute synchronously via RemoteGraph. No background job queue — the A2A connection stays open during execution with streaming notifications. |
### Security Architecture
**Authentication flow:**
1. Client discovers the server's Agent Card (via `/.well-known/agent.json`) which declares supported auth schemes and `_cleveragents` extensions
2. Client authenticates using the declared HTTP auth scheme (e.g., Bearer token via `Authorization: Bearer <server.token>`)
3. Server validates the token against its user/token store
4. All subsequent requests include the auth credentials; the `A2A-Version` header identifies the protocol version
**Authorization:**
- Namespace-scoped: users can only access entities in namespaces they have been granted access to
- The `local/` namespace is never accessible via server — it exists only on the client
- Server namespaces (`<username>/`, `<orgname>/`) have explicit ACLs
**Transport security:**
- HTTPS required for all production deployments (TLS termination at ingress)
- The A2A SDK HTTP transport supports standard TLS
### Deployment Architecture
| Component | Technology | Notes |
|-----------|-----------|-------|
| Container | Docker | Server packaged as container image |
| Orchestration | Kubernetes | Production deployment target |
| Configuration | Helm chart (`k8s/`) | Configurable replicas, resource limits, ingress |
| Ingress | Kubernetes Ingress | TLS termination, routing |
| Database | PostgreSQL (managed or self-hosted) | External to the application container |
| LangGraph Platform | Managed or self-hosted | External service for graph execution |
### Sync Protocol
Entity synchronization between client and server uses A2A extension methods:
| Method | Purpose |
|--------|---------|
| `_cleveragents/sync/pull` | Download server namespace entities to local cache |
| `_cleveragents/sync/push` | Upload local entity definitions to a server namespace |
| `_cleveragents/sync/status` | Compare local and server entity versions |
Sync behavior:
- **Auto-sync** (`server.sync.auto`): Entities sync on connection and at `server.sync.interval` (default: 300s)
- **Direction**: Server → local for server namespaces; local → server only on explicit push
- **Conflict resolution**: Server version wins by default; conflicts are surfaced to the user via `sync/status`
- The `local/` namespace is **never** synced
### Technology Stack
| Component | Version | Purpose |
|-----------|---------|---------|
| Python | >= 3.13 | Runtime |
| A2A Python SDK (`a2a-sdk`) | Latest | A2A JSON-RPC 2.0 server implementation |
| LangGraph Platform | Latest | Remote actor graph execution |
| PostgreSQL | >= 15 | Multi-user persistence |
| SQLAlchemy | >= 2.0 | ORM (shared with client) |
| Alembic | >= 1.13 | Database migrations |
| structlog | >= 24.1 | Structured logging |
| Redis | >= 7.0 (optional) | Caching, session affinity for multi-instance |
| Helm | >= 3.0 | Kubernetes deployment |
## Constraints
- The server **must not** contain any Domain or Application layer code that is not shared with the client. All mode differences must be in Infrastructure and Presentation layers.
- The server's **only** client-facing interface is the A2A JSON-RPC 2.0 endpoint. No REST API, no GraphQL, no admin endpoints.
- Actor graph execution **must** go through LangGraph Platform RemoteGraph — the server does not execute actor graphs in-process.
- The `local/` namespace must never be accessible or syncable via the server.
- All A2A connections require authentication (via HTTP auth schemes declared in the Agent Card) before any task operations.
## Consequences
### Positive
- Maximum code reuse — Domain and Application layers are identical, eliminating behavioral drift between local and server modes
- LangGraph Platform provides production-grade graph execution with built-in scaling, persistence, and monitoring
- The A2A-only interface ensures all clients (CLI, TUI, IDE, third-party) interact identically with the server
- RemoteGraph per actor enables independent scaling of different actor types (strategy actors may need different resources than execution actors)
- PostgreSQL provides proven multi-user persistence with proper transaction isolation
### Negative
- Dependency on LangGraph Platform — the server cannot execute actors without it
- Operating a server requires PostgreSQL, Kubernetes, and optionally Redis — significantly more infrastructure than local mode
- The A2A Python SDK is relatively new; production edge cases may require SDK contributions or workarounds
### Risks
- LangGraph Platform availability directly affects server plan execution. Mitigation: health checks and graceful degradation (report unavailability, don't silently fail)
- Network latency between server and LangGraph Platform adds overhead to every actor invocation. Mitigation: co-locate server and LangGraph Platform in the same region/cluster
- The shared Domain/Application package must be versioned carefully to avoid incompatibilities between client and server releases. Mitigation: semantic versioning with backward compatibility guarantees
## Alternatives Considered
**Single monolithic server (no LangGraph Platform)** — Execute actor graphs in the server process directly. Simpler deployment but loses LangGraph Platform's scaling, persistence, and monitoring. Doesn't align with ADR-022's adoption of LangGraph. Rejected.
**Server in the same repository as the client** — Simpler development workflow but conflates deployment units and makes it harder to version the shared layers independently. A separate application with shared package dependencies is cleaner. Rejected.
**Multiple API surfaces (A2A + REST admin)** — An admin REST API alongside A2A for server management. Adds a second protocol surface, complicates authentication, and violates the single-protocol principle. Admin operations are handled via `_cleveragents/` extension methods. Rejected.
## Compliance
- **Shared layer tests**: Tests verify that Domain and Application layers imported by the server are byte-identical to those used by the client (same package version).
- **A2A conformance tests**: The server passes the A2A standard test suite for all standard methods.
- **Extension method tests**: All `_cleveragents/` methods are tested against documented parameter and response schemas.
- **RemoteGraph integration tests**: Tests verify actor graph deployment to and invocation via LangGraph Platform.
- **Authentication tests**: Tests verify the `authenticate` flow and namespace-scoped authorization.
- **Sync tests**: Integration tests verify pull/push/status sync operations including conflict detection.
- **Deployment tests**: Helm chart renders correctly and the container starts with valid configuration.