14 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 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 48 | Server Application Architecture |
|
4 |
|
|
|
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 toSessionWorkflowand actor execution via Tasks - Extension methods:
_cleveragents/*— routed to Application-layer services (PlanService, RegistryServices, SyncService, etc.) - Multi-turn interaction: Tasks entering
input-requiredstate 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:
- Client discovers the server's Agent Card (via
/.well-known/agent.json) which declares supported auth schemes and_cleveragentsextensions - Client authenticates using the declared HTTP auth scheme (e.g., Bearer token via
Authorization: Bearer <server.token>) - Server validates the token against its user/token store
- All subsequent requests include the auth credentials; the
A2A-Versionheader 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 atserver.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
authenticateflow 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.