9.4 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 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 23 | Server Mode |
|
4 |
|
null |
|
|
Context
CleverAgents starts as a single-user local CLI tool, but the specification envisions a collaborative multi-user deployment where teams share entity definitions, execute plans on shared infrastructure, and collaborate on projects. The system needs a server deployment mode that enables this without requiring a separate codebase — the same domain and application layers must support both local and server modes.
Decision Drivers
- The same domain and application logic must run identically in both single-user local and multi-user server deployments
- Teams need to share entity definitions, execute plans on shared infrastructure, and collaborate without maintaining separate codebases
- Local mode must work fully offline with zero server dependency
- Client and server must share namespaces and resolve names identically regardless of deployment mode
- Infrastructure concerns (database, sandbox, transport) must be swappable without touching domain or application code
Decision
CleverAgents supports two deployment modes that share the same Domain and Application layers. Local mode is a single-process CLI application with SQLite storage and A2A over stdio. Server mode is a multi-user service where the CLI acts as a thin client communicating with a remote server via A2A over HTTP (JSON-RPC 2.0). The Infrastructure Layer provides swappable implementations for each mode, enabled by the hexagonal architecture (ADR-001).
Design
Local Mode
- Single-process CLI application.
- All components run in the same Python process.
- SQLite database for persistence (WAL mode for concurrent reads).
- All resources are local to the machine.
- A2A flows over stdio — the agent runs as a subprocess, JSON-RPC messages over stdin/stdout.
- Extension methods (
_cleveragents/*) resolved in-process byA2aLocalFacade. - The
local/namespace is always available. - No server dependency — works fully offline.
Server Mode
- The CLI operates as a thin client.
- Communication with the server via A2A over HTTP (JSON-RPC 2.0) using
server.urlandserver.token. - The server hosts shared storage (PostgreSQL), namespace resolution, and remote plan execution.
- Server namespaces (
<username>/,<orgname>/) are available when connected. - Entity definitions (actors, actions, skills, tools) can be synced between local and server via
_cleveragents/sync/*extension methods. - Plans can execute on the server, with actor graphs running on LangGraph Platform via RemoteGraph.
Server Infrastructure
- A2A endpoint: A2A JSON-RPC 2.0 server implemented using the A2A Python SDK's server-side connection handler. This is the sole client-facing interface.
- Database: PostgreSQL via SQLAlchemy (same ORM, different dialect from local SQLite).
- Actor execution: LangGraph Platform via RemoteGraph — each actor graph deploys as a separate RemoteGraph.
- Deployment: Helm chart in
k8s/directory for Kubernetes deployment. - Authentication: Token-based authentication via HTTP auth schemes declared in the server's Agent Card.
Sync Mechanism
Entity synchronization between client and server uses A2A extension methods (_cleveragents/sync/*):
- Auto-sync: Controlled by
server.sync.auto(default:true). When enabled, entities are synced on startup and after registration changes. - Sync interval: Background syncs at
server.sync.interval(default: 300 seconds). - Manual sync: Can be triggered explicitly when auto-sync is disabled.
- Direction: Server namespaces are downloaded to local cache; local namespace entities are never uploaded automatically.
Namespace Resolution in Server Mode
local/always resolves locally, regardless of server connection.<username>/and<orgname>/resolve against the server.- Server-qualified names (
dev:freemo/actor) resolve against a specific server when multiple servers are connected. - The default namespace can be configured to a server namespace via
core.namespace.
Project Types and Execution Location
| Project Type | Definition | Plan Execution |
|---|---|---|
| Local | Contains at least one local-only resource | Client only |
| Remote | All resources are remotely accessible | Client or Server |
Remote projects can have their plans executed on the server, enabling shared infrastructure and centralized execution. When server-hosted agents need to access client-local resources, _cleveragents/ extension method callbacks (fs/*, terminal/*) forward the requests to the connected client.
Shared Architecture
The key architectural enabler is the hexagonal pattern (ADR-001):
- Domain Layer: Identical between modes. All business rules, domain models, and domain events are shared.
- Application Layer: Identical between modes. Service facades, workflow engine, and event bus are shared.
- Infrastructure Layer: Swappable implementations per mode:
- SQLite ↔ PostgreSQL (database)
- Local file access ↔ A2A callbacks (resource access)
- Local sandbox ↔ LangGraph Platform RemoteGraph (execution environment)
- A2aLocalFacade (stdio) ↔ A2A SDK server endpoint (HTTP) (transport)
Constraints
- The
local/namespace must never be synced to a server. - Server mode requires
server.urlandserver.tokento be configured. - The Domain and Application layers must have zero mode-specific code. All mode differences must be in the Infrastructure Layer.
- Token authentication is required for all server A2A method calls (enforced via HTTP auth schemes declared in the Agent Card).
- Local-only projects (containing local-only resources) cannot execute plans on the server.
- The CLI must function fully offline in local mode, with no server dependency.
Consequences
Positive
- A single codebase supports both local-first development and collaborative server deployment.
- The hexagonal architecture ensures mode switching is purely an infrastructure concern.
- Local mode has zero configuration requirements — no server needed.
- Teams can share entity definitions and execute plans on shared infrastructure without each member maintaining local copies.
Negative
- Server mode requires operating a server (PostgreSQL, Kubernetes, LangGraph Platform, Helm, authentication).
- The sync mechanism adds complexity and potential for stale or conflicting entity definitions.
- Network latency affects all operations in server mode.
Risks
- Sync conflicts between local modifications and server-side changes could produce inconsistent entity states.
- Server downtime renders server-namespaced entities unavailable.
- Security of the authentication token and HTTPS transport must be ensured.
Alternatives Considered
Separate server codebase — Would require maintaining two implementations of the same domain logic, leading to divergence and doubled maintenance. The shared-layer approach avoids this.
Peer-to-peer sync (no central server) — More resilient but significantly more complex for conflict resolution, namespace management, and access control. A central server provides simpler semantics for organizational namespaces and shared execution.
Compliance
- Mode isolation tests: Tests verify that Domain and Application layer code has no mode-specific imports or branching.
- Infrastructure swappability tests: Tests verify that swapping SQLite for PostgreSQL, local sandbox for RemoteGraph, etc., produces correct behavior.
- Offline functionality tests: Tests verify that local mode works completely without network access.
- Sync tests: Integration tests verify entity sync behavior including auto-sync, manual sync, and conflict handling via
_cleveragents/sync/*methods. - Authentication tests: Tests verify that the A2A authentication flow works correctly and that unauthenticated requests are rejected.