Files
cleveragents-core/docs/showcase/cli-tools/server-and-a2a-integration.md
HAL9000 ccfbf11009
ci.yml / docs: add showcase example for server and A2A integration (push) Failing after 0s
docs: add showcase example for server and A2A integration
Adds a complete end-to-end walkthrough of the server connection CLI
commands and A2A protocol facade, verified by the UAT system with real
command outputs. Covers:

- agents server --help, connect, status, serve commands
- All output formats (rich, json, yaml, plain)
- A2A JSON-RPC 2.0 wire format (A2aRequest, A2aResponse, A2aEvent)
- A2aLocalFacade: 42 supported operations, stub mode, service wiring
- get_facade() wired to real DI container
- A2aVersionNegotiator: version negotiation and mismatch handling
- ServerConnectionConfig: validation rules

Also updates examples.json index with the new entry.
2026-04-07 14:54:49 +00:00

31 KiB

Server Connection and A2A Protocol Integration

Overview

CleverAgents ships with a server connection subsystem and an Agent-to-Agent (A2A) protocol facade that together form the foundation for distributed, multi-agent deployments. This example walks through the complete workflow: configuring a server endpoint, inspecting connection status, understanding the A2A JSON-RPC 2.0 wire format, and using the local-mode facade to dispatch operations directly to application services.

Prerequisites

  • CleverAgents installed (pip install cleveragents)
  • Python 3.12 or higher

What You'll Learn

  • How to configure a server endpoint with agents server connect
  • How to inspect server connection status with agents server status
  • How to understand the A2A JSON-RPC 2.0 wire format (request/response/event models)
  • How to use the A2A local-mode facade to dispatch operations programmatically
  • How to wire real services into the facade via get_facade() and register_service()
  • How to handle version negotiation with A2aVersionNegotiator
  • How to validate server configuration with ServerConnectionConfig

Part 1: Server Connection CLI

Step 1: Explore the server subcommand

$ agents server --help

Output:

 Usage: agents server [OPTIONS] COMMAND [ARGS]...

 Server connection management (stub)

╭─ Options ────────────────────────────────────────────────────────────────────╮
│ --help          Show this message and exit.                                  │
╰──────────────────────────────────────────────────────────────────────────────╯
╭─ Commands ───────────────────────────────────────────────────────────────────╮
│ connect  Connect to a CleverAgents server (stub).                            │
│ status   Show current server connection status.                              │
│ serve    Run the CleverAgents ASGI server process.                           │
╰──────────────────────────────────────────────────────────────────────────────╯

What's Happening:

The server subcommand has three commands:

  • connect — registers a server URL in the configuration
  • status — shows the current server mode and configured URL
  • serve — launches the ASGI server process (for container/Kubernetes deployments)

Step 2: Check server status before configuration

$ agents server status

Output:

╭─────── Server Status ────────╮
│ Server Mode: disabled        │
│ Server URL: (not configured) │
│ Namespace: default           │
│ TLS Verify: True             │
╰──────────────────────────────╯

Output (JSON format):

$ agents server status --format json
{
  "command": "",
  "status": "ok",
  "exit_code": 0,
  "data": {
    "server_mode": "disabled",
    "server_url": null,
    "namespace": "default",
    "tls_verify": true
  },
  "timing": {
    "duration_ms": 0
  },
  "messages": [
    {
      "level": "ok",
      "text": "ok"
    }
  ]
}

What's Happening:

server_mode: "disabled" means no server URL has been configured. The system operates in local mode — all operations are handled by in-process services.


Step 3: Connect to a server

$ agents server connect https://agents.example.com

Output:

╭────────────────────────── Server Connection (Stub) ──────────────────────────╮
│ Server URL: https://agents.example.com                                       │
│ Namespace: default                                                           │
│ TLS Verify: True                                                             │
│ Status: stubbed                                                              │
│                                                                              │
│ Server connection is not yet implemented. The URL has been saved to          │
│ configuration but no connection will be made.                                │
╰──────────────────────────────────────────────────────────────────────────────╯

Output (JSON format):

$ agents server connect https://agents.example.com --format json
{
  "command": "",
  "status": "ok",
  "exit_code": 0,
  "data": {
    "server_url": "https://agents.example.com",
    "namespace": "default",
    "tls_verify": true,
    "status": "stubbed",
    "warning": "Server connection is not yet implemented. The URL has been saved to configuration but no connection will be made."
  },
  "timing": {
    "duration_ms": 0
  },
  "messages": [
    {
      "level": "ok",
      "text": "ok"
    }
  ]
}

What's Happening:

The connect command validates the URL (must start with http:// or https://), then persists three settings to ~/.cleveragents/config.toml:

  • server.url — the server endpoint
  • server.namespace — the namespace (default: "default")
  • server.tls-verify — whether to verify TLS certificates (default: true)

Each setting change emits a CONFIG_CHANGED audit event, ensuring a full audit trail for these security-relevant settings.


Step 4: Connect with a custom namespace

$ agents server connect https://agents.example.com --namespace my-team --format json

Output:

{
  "command": "",
  "status": "ok",
  "exit_code": 0,
  "data": {
    "server_url": "https://agents.example.com",
    "namespace": "my-team",
    "tls_verify": true,
    "status": "stubbed",
    "warning": "Server connection is not yet implemented. The URL has been saved to configuration but no connection will be made."
  },
  "timing": {
    "duration_ms": 0
  },
  "messages": [
    {
      "level": "ok",
      "text": "ok"
    }
  ]
}

What's Happening:

The --namespace (or -n) flag sets the namespace on the server. Namespaces allow multiple teams or projects to share a single CleverAgents server instance with isolated resources.


Step 5: Connect to a local dev server (no TLS)

$ agents server connect http://localhost:8080 --no-tls-verify --format json

Output:

{
  "command": "",
  "status": "ok",
  "exit_code": 0,
  "data": {
    "server_url": "http://localhost:8080",
    "namespace": "default",
    "tls_verify": false,
    "status": "stubbed",
    "warning": "Server connection is not yet implemented. The URL has been saved to configuration but no connection will be made."
  },
  "timing": {
    "duration_ms": 0
  },
  "messages": [
    {
      "level": "ok",
      "text": "ok"
    }
  ]
}

What's Happening:

--no-tls-verify disables certificate verification — useful for local development with self-signed certificates. Never use this in production.


Step 6: Check status after connecting

$ agents server status

Output:

╭──────────── Server Status ─────────────╮
│ Server Mode: stubbed                   │
│ Server URL: https://agents.example.com │
│ Namespace: default                     │
│ TLS Verify: True                       │
╰────────────────────────────────────────╯

Output (YAML format):

$ agents server status --format yaml
command: ''
status: ok
exit_code: 0
data:
  server_mode: stubbed
  server_url: https://agents.example.com
  namespace: default
  tls_verify: true
timing:
  duration_ms: 0
messages:
- level: ok
  text: ok

Output (plain format):

$ agents server status --format plain
server_mode: stubbed
server_url: https://agents.example.com
namespace: default
tls_verify: True

What's Happening:

server_mode: "stubbed" means a URL is configured but the actual network client is not yet implemented. The two possible modes are:

Mode Meaning
disabled No server URL configured — local mode only
stubbed URL configured, but network client not yet implemented

Step 7: Validation — invalid URL rejected

$ agents server connect not-a-url --format json

Output (exit code 1):

Invalid server configuration: 1 validation error for ServerConnectionConfig
server_url
  Value error, server_url must start with http:// or https://

What's Happening:

The connect command validates the URL using ServerConnectionConfig (a Pydantic model) before writing anything to disk. Invalid URLs are rejected immediately with a clear error message and exit code 1.


Step 8: Explore the serve command options

$ agents server serve --help

Output:

 Usage: agents server serve [OPTIONS]

 Run the CleverAgents ASGI server process.

 This command is used by container runtimes (Docker/Kubernetes) to launch
 the server endpoint using the project CLI entrypoint (`python -m
 cleveragents`).

╭─ Options ────────────────────────────────────────────────────────────────────╮
│ --host             TEXT                         Bind address for the ASGI    │
│                                                 server                       │
│                                                 [default: 0.0.0.0]           │
│ --port             INTEGER RANGE [1<=x<=65535]  Bind port for the ASGI       │
│                                                 server                       │
│                                                 [default: 8000]              │
│ --workers          INTEGER RANGE [x>=1]         Number of uvicorn worker     │
│                                                 processes                    │
│                                                 [default: 1]                 │
│ --log-level        TEXT                         Uvicorn log level            │
│                                                 [default: info]              │
│ --app              TEXT                         ASGI app import path         │
│                                                 (module:attribute)           │
│                                                 [default:                    │
│                                                 cleveragents.a2a.asgi:app]   │
│ --help                                          Show this message and exit.  │
╰──────────────────────────────────────────────────────────────────────────────╯

What's Happening:

The serve command launches the ASGI server (via uvicorn) for container deployments. The default app target is cleveragents.a2a.asgi:app, which serves health/readiness probes at /live, /ready, /health, and /.


Part 2: A2A Protocol Facade

The A2A (Agent-to-Agent) protocol is CleverAgents' internal communication protocol, based on JSON-RPC 2.0. In local mode, the A2aLocalFacade routes operations directly to application services — no network, no serialization overhead.

Step 9: Understand the wire format

from cleveragents.a2a.models import A2aRequest, A2aResponse, A2aErrorDetail, A2aEvent

# A2A request — JSON-RPC 2.0 envelope
req = A2aRequest(method="_cleveragents/health/check", params={"key": "value"})
print(req.model_dump())

Output:

{
  "jsonrpc": "2.0",
  "id": "01KNM6HBGXEZZ6RK37EXCB1AHM",
  "method": "_cleveragents/health/check",
  "params": {
    "key": "value"
  }
}
# Successful response
resp = A2aResponse(id=req.id, result={"status": "healthy", "services": {}})
print(resp.model_dump())

Output:

{
  "jsonrpc": "2.0",
  "id": "01KNM6HBGXEZZ6RK37EXCB1AHM",
  "result": {
    "status": "healthy",
    "services": {}
  },
  "error": null
}
# Error response
err_resp = A2aResponse(
    id=req.id,
    error=A2aErrorDetail(code=-32601, message="Method not found")
)
print(err_resp.model_dump())

Output:

{
  "jsonrpc": "2.0",
  "id": "01KNM6HBGXEZZ6RK37EXCB1AHM",
  "result": null,
  "error": {
    "code": -32601,
    "message": "Method not found",
    "data": {}
  }
}
# Server-sent event (for plan progress streaming)
event = A2aEvent(
    event_type="plan.progress",
    plan_id="plan-123",
    data={"phase": "running", "progress": 0.5}
)
print(event.model_dump())

Output:

{
  "event_id": "01KNM6HBGXEZZ6RK37EXCB1AHN",
  "event_type": "plan.progress",
  "plan_id": "plan-123",
  "data": {
    "phase": "running",
    "progress": 0.5
  },
  "timestamp": "2026-04-07T14:46:24.029773+00:00"
}

What's Happening:

The A2A wire format follows JSON-RPC 2.0:

  • jsonrpc: "2.0" — always present, validated on construction
  • id — auto-generated ULID if not provided (sortable, time-ordered)
  • method — the operation name (e.g. _cleveragents/plan/status)
  • params — operation-specific parameters dict
  • result XOR error — exactly one must be set in responses

A2aEvent is used for server-sent events (SSE) during plan execution streaming.


Step 10: List all supported A2A operations

from cleveragents.a2a.facade import A2aLocalFacade

facade = A2aLocalFacade()
ops = facade.list_operations()
print(f"Total operations: {len(ops)}")
for op in ops:
    print(f"  {op}")

Output:

Total operations: 42
  _cleveragents/plan/use
  _cleveragents/plan/execute
  _cleveragents/plan/apply
  _cleveragents/plan/cancel
  _cleveragents/plan/status
  _cleveragents/plan/tree
  _cleveragents/plan/explain
  _cleveragents/plan/correct
  _cleveragents/plan/diff
  _cleveragents/plan/artifacts
  _cleveragents/plan/prompt
  _cleveragents/plan/rollback
  _cleveragents/plan/list
  _cleveragents/registry/tool/list
  _cleveragents/registry/resource/list
  _cleveragents/registry/actor/list
  _cleveragents/registry/skill/list
  _cleveragents/registry/action/list
  _cleveragents/registry/project/list
  _cleveragents/context/show
  _cleveragents/context/inspect
  _cleveragents/context/simulate
  _cleveragents/context/set
  _cleveragents/health/check
  _cleveragents/diagnostics/run
  _cleveragents/sync/pull
  _cleveragents/sync/push
  _cleveragents/sync/status
  _cleveragents/namespace/list
  _cleveragents/namespace/show
  _cleveragents/namespace/members
  session.create
  session.close
  plan.create
  plan.execute
  plan.status
  plan.diff
  plan.apply
  registry.list_tools
  registry.list_resources
  context.get
  event.subscribe

What's Happening:

The 42 operations fall into two groups:

  • _cleveragents/ extension methods (31 ops) — spec-aligned ADR-047 names for the current API surface
  • Legacy proprietary names (11 ops) — deprecated names kept for backward compatibility (e.g. session.create, plan.execute)

The extension methods are organized into namespaces: plan/, registry/, context/, health/, diagnostics/, sync/, and namespace/.


Step 11: Dispatch operations in stub mode (no services)

from cleveragents.a2a.facade import A2aLocalFacade
from cleveragents.a2a.models import A2aRequest

facade = A2aLocalFacade()  # No services wired

def call(method, params=None):
    req = A2aRequest(method=method, params=params or {})
    return facade.dispatch(req)

# Health check
resp = call("_cleveragents/health/check")
print(resp.result)
# → {"status": "healthy", "services": {}}

# Plan list (stub — no service)
resp = call("_cleveragents/plan/list")
print(resp.result)
# → {"plans": [], "stub": true}

# Session create (stub — generates a ULID)
resp = call("session.create", {"actor_name": "test-actor"})
print(resp.result)
# → {"session_id": "01KNM6GE0WQ2TP4X3R0HAF03MJ", "status": "created"}

# Event subscribe (stub)
resp = call("event.subscribe")
print(resp.result)
# → {"subscription_id": "01KNM6GE0WQ2TP4X3R0HAF03MM", "status": "subscribed"}

What's Happening:

When no services are wired, the facade returns safe stub responses for every operation. This means:

  • Operations never crash due to missing dependencies
  • Tests can run without a full application container
  • The facade is safe to use in minimal environments

Step 12: Dispatch plan lifecycle operations

# Plan create (stub)
resp = call("_cleveragents/plan/use", {"action_name": "my-action"})
print(resp.result)
# → {"plan_id": "01KNM6GYV01AMVWFD1B9YD04MM", "status": "created"}

# Plan status (stub)
resp = call("_cleveragents/plan/status", {"plan_id": "test-plan-123"})
print(resp.result)
# → {"plan_id": "test-plan-123", "phase": "unknown"}

# Plan cancel (stub)
resp = call("_cleveragents/plan/cancel", {"plan_id": "test-plan-123"})
print(resp.result)
# → {"plan_id": "test-plan-123", "status": "cancelled", "stub": true}

# Plan prompt — inject guidance mid-execution
resp = call("_cleveragents/plan/prompt", {
    "plan_id": "test-plan-123",
    "guidance": "Focus on security"
})
print(resp.result)
# → {"plan_id": "test-plan-123", "guidance": "Focus on security",
#    "status": "guidance_injected", "stub": true}

# Plan rollback
resp = call("_cleveragents/plan/rollback", {"plan_id": "test-plan-123"})
print(resp.result)
# → {"plan_id": "test-plan-123", "status": "rolled_back", "stub": true}

What's Happening:

The plan lifecycle operations map to the full plan state machine: useexecuteapply (or cancel/rollback). The prompt operation allows injecting human guidance into a running plan mid-execution — a key feature of the supervised automation model.


Step 13: Register real services and dispatch

from cleveragents.a2a.facade import A2aLocalFacade
from cleveragents.a2a.models import A2aRequest

facade = A2aLocalFacade()

# Register a mock tool registry
class MockToolRegistry:
    def list_tools(self, namespace=None):
        class Tool:
            name = "bash"
            description = "Run shell commands"
        class Tool2:
            name = "file_read"
            description = "Read file contents"
        return [Tool(), Tool2()]

facade.register_service("tool_registry", MockToolRegistry())

# Now tool list returns real data
req = A2aRequest(method="_cleveragents/registry/tool/list")
resp = facade.dispatch(req)
print(resp.result)

Output:

{
  "tools": [
    {"name": "bash", "description": "Run shell commands"},
    {"name": "file_read", "description": "Read file contents"}
  ]
}
# Register a mock resource registry
class MockResourceRegistry:
    def list_resources(self, type_name=None):
        class R:
            resource_id = "res-001"
            name = "main-repo"
            resource_type_name = "git_repository"
        return [R()]

facade.register_service("resource_registry_service", MockResourceRegistry())

req2 = A2aRequest(method="_cleveragents/registry/resource/list")
resp2 = facade.dispatch(req2)
print(resp2.result)

Output:

{
  "resources": [
    {
      "resource_id": "res-001",
      "name": "main-repo",
      "type_name": "git_repository"
    }
  ]
}

What's Happening:

register_service() wires a named service into the facade. The handler map is rebuilt on the next dispatch (cached for performance). This pattern allows incremental wiring — start with no services and add them as they become available.


Step 14: Use the fully-wired facade via get_facade()

from cleveragents.a2a.cli_bootstrap import get_facade
from cleveragents.a2a.models import A2aRequest

# get_facade() wires the facade to the real DI container
facade = get_facade()
print(f"Services wired: {list(facade._services.keys())}")
# → Services wired: ['plan_lifecycle_service', 'session_service',
#                    'resource_registry_service']

# Health check
req = A2aRequest(method="_cleveragents/health/check")
resp = facade.dispatch(req)
print(resp.result)
# → {"status": "healthy", "services": {}}

# Plan list — returns real plans from the database
req2 = A2aRequest(method="_cleveragents/plan/list")
resp2 = facade.dispatch(req2)
print(resp2.result)

Output:

{
  "plans": [
    {"plan_id": "01KNKK0MQBWQ5NF0BZYKTF4WJP", "phase": "strategize"},
    {"plan_id": "01KNKK0MKCDXGP8Z3PZ88S5GGZ", "phase": "strategize"},
    {"plan_id": "01KNKJZ44ES00VT8JTRKX1HEQA", "phase": "strategize"},
    {"plan_id": "01KNKJZ424C48388RWHW3KY5QZ", "phase": "strategize"}
  ],
  "total": 4
}

What's Happening:

get_facade() is the production entry point. It:

  1. Calls get_container() to obtain the DI container
  2. Wires available services (suppressing errors for missing ones)
  3. Caches the facade for the process lifetime

The facade is process-scoped — multiple CLI invocations within the same Python process share the same wired instance. Call reset_facade() to force a rebuild (useful in tests).


Step 15: Handle unknown operations

from cleveragents.a2a.errors import A2aOperationNotFoundError
from cleveragents.a2a.models import A2aRequest

facade = A2aLocalFacade()

try:
    req = A2aRequest(method="unknown/operation")
    resp = facade.dispatch(req)
except A2aOperationNotFoundError as e:
    print(f"A2aOperationNotFoundError: {e}")
# → A2aOperationNotFoundError: Unknown A2A method: unknown/operation

What's Happening:

A2aOperationNotFoundError is raised (not returned as an error response) for truly unknown methods. This is intentional — the caller must handle routing errors explicitly, while domain errors (e.g. plan not found) are returned as A2aResponse.error payloads.


Part 3: Version Negotiation

Step 16: Negotiate A2A protocol versions

from cleveragents.a2a.versioning import A2aVersionNegotiator

neg = A2aVersionNegotiator()

print(f"Current version: {neg.get_current()}")
# → Current version: 2.0

print(f"Supported versions: {neg.SUPPORTED_VERSIONS}")
# → Supported versions: ('2.0',)

# Negotiate a supported version
result = neg.negotiate("2.0")
print(f"Negotiated: {result}")
# → Negotiated: 2.0

# Check support
print(f"2.0 supported: {neg.is_supported('2.0')}")   # → True
print(f"1.0 supported: {neg.is_supported('1.0')}")   # → False

# Unsupported version raises
try:
    neg.negotiate("1.0")
except Exception as e:
    print(f"Error: {type(e).__name__}: {e}")
# → Error: A2aVersionMismatchError: A2A version '1.0' is not supported

What's Happening:

A2aVersionNegotiator implements the version handshake for the A2A protocol. Currently only "2.0" (JSON-RPC 2.0) is supported. When a client connects with an incompatible version, A2aVersionMismatchError is raised with the requested version and the list of supported versions — giving the client enough information to downgrade gracefully.


Part 4: Server Configuration Model

Step 17: Validate server connection configuration

from cleveragents.a2a.server_config import ServerConnectionConfig

# Production HTTPS config
cfg = ServerConnectionConfig(
    server_url="https://agents.example.com",
    namespace="production"
)
print(cfg.model_dump())
# → {"server_url": "https://agents.example.com", "namespace": "production",
#    "auth_token_ref": null, "tls_verify": true}

# Local dev config (HTTP, no TLS verification)
cfg2 = ServerConnectionConfig(
    server_url="http://localhost:8080",
    namespace="dev",
    tls_verify=False
)
print(cfg2.model_dump())
# → {"server_url": "http://localhost:8080", "namespace": "dev",
#    "auth_token_ref": null, "tls_verify": false}

# With auth token reference (env var name or secret store key)
cfg3 = ServerConnectionConfig(
    server_url="https://agents.company.com",
    namespace="team-alpha",
    auth_token_ref="CLEVERAGENTS_AUTH_TOKEN",
    tls_verify=True
)
print(cfg3.model_dump())
# → {"server_url": "https://agents.company.com", "namespace": "team-alpha",
#    "auth_token_ref": "CLEVERAGENTS_AUTH_TOKEN", "tls_verify": true}

Validation errors:

# Empty URL
ServerConnectionConfig(server_url="")
# → ValueError: server_url must not be empty

# Missing scheme
ServerConnectionConfig(server_url="agents.example.com")
# → ValueError: server_url must start with http:// or https://

# Empty namespace
ServerConnectionConfig(server_url="https://agents.example.com", namespace="")
# → ValueError: namespace must not be empty

What's Happening:

ServerConnectionConfig is a frozen Pydantic model — immutable after creation. The auth_token_ref field stores a reference to an auth token (e.g. an environment variable name or secret store key), not the token itself. This keeps secrets out of the configuration file.


Complete Interaction Log

Click to see the full verified command sequence
# 1. Explore server subcommand
$ agents server --help
# → 3 commands: connect, status, serve

# 2. Check initial status (no config)
$ agents server status
# → Server Mode: disabled, Server URL: (not configured)

$ agents server status --format json
# → {"server_mode": "disabled", "server_url": null, "namespace": "default", "tls_verify": true}

# 3. Connect to a server
$ agents server connect https://agents.example.com
# → Panel: Server URL, Namespace: default, TLS Verify: True, Status: stubbed

$ agents server connect https://agents.example.com --format json
# → {"server_url": "https://agents.example.com", "namespace": "default",
#    "tls_verify": true, "status": "stubbed", "warning": "..."}

# 4. Connect with namespace
$ agents server connect https://agents.example.com --namespace my-team --format json
# → {"namespace": "my-team", ...}

# 5. Connect to local dev (no TLS)
$ agents server connect http://localhost:8080 --no-tls-verify --format json
# → {"server_url": "http://localhost:8080", "tls_verify": false, ...}

# 6. Check status after connecting
$ agents server status
# → Server Mode: stubbed, Server URL: https://agents.example.com

$ agents server status --format yaml
# → server_mode: stubbed, server_url: https://agents.example.com

$ agents server status --format plain
# → server_mode: stubbed

# 7. Validation: invalid URL
$ agents server connect not-a-url --format json
# → exit 1: "server_url must start with http:// or https://"

# 8. Explore serve command
$ agents server serve --help
# → --host, --port, --workers, --log-level, --app options

# Python A2A facade usage:
# 9. List 42 supported operations
# 10. Dispatch health check → {"status": "healthy", "services": {}}
# 11. Dispatch plan list (stub) → {"plans": [], "stub": true}
# 12. Dispatch session.create → {"session_id": "01K...", "status": "created"}
# 13. Dispatch plan/use → {"plan_id": "01K...", "status": "created"}
# 14. Dispatch plan/cancel → {"status": "cancelled", "stub": true}
# 15. Dispatch plan/prompt → {"status": "guidance_injected", "stub": true}
# 16. Register MockToolRegistry → tools: [bash, file_read]
# 17. Register MockResourceRegistry → resources: [main-repo]
# 18. get_facade() → wired to real container, plan list returns 4 real plans
# 19. Unknown operation → A2aOperationNotFoundError
# 20. Version negotiation: 2.0 → OK, 1.0 → A2aVersionMismatchError
# 21. ServerConnectionConfig validation: empty URL, no scheme, empty namespace

Key Takeaways

  • Two server modes: disabled (no URL configured) and stubbed (URL configured, network client pending). The mode is reflected in every server status response.
  • Config is persisted via audit trail: server connect uses set_value() (not write_config()) so every change emits a CONFIG_CHANGED event — important for security-relevant settings like server URL and TLS verification.
  • A2A uses JSON-RPC 2.0: The wire format is standard — jsonrpc: "2.0", auto-generated ULID id, method, params, and result XOR error.
  • 42 operations in two groups: _cleveragents/ extension methods (spec- aligned, ADR-047) and legacy proprietary names (deprecated, kept for backward compatibility).
  • Stub-safe facade: When services are absent, every operation returns a safe stub response. The facade never crashes due to missing wiring.
  • get_facade() is the production entry point: It wires the facade to the real DI container and caches it for the process lifetime. Use reset_facade() in tests.
  • A2aOperationNotFoundError is raised, not returned: Unknown methods raise an exception; domain errors (plan not found, etc.) are returned as A2aResponse.error payloads.
  • Version negotiation is strict: Only "2.0" is supported. Clients with incompatible versions receive A2aVersionMismatchError with enough context to downgrade.

Try It Yourself

# Check current server mode
$ agents server status --format json | jq '.data.server_mode'

# Connect to a server and immediately check status
$ agents server connect https://my-agents-server.com --namespace my-project
$ agents server status

# Reset server configuration
$ agents config set server.url "" --scope global

# List all A2A operations from Python
$ python -c "
from cleveragents.a2a.facade import A2aLocalFacade
f = A2aLocalFacade()
for op in f.list_operations():
    print(op)
"

# Run a health check via the wired facade
$ python -c "
from cleveragents.a2a.cli_bootstrap import get_facade
from cleveragents.a2a.models import A2aRequest
import json
facade = get_facade()
resp = facade.dispatch(A2aRequest(method='_cleveragents/health/check'))
print(json.dumps(resp.result, indent=2))
"

# Launch the ASGI server for local testing (requires uvicorn)
$ agents server serve --host 127.0.0.1 --port 8080 --log-level debug

This example was automatically generated and verified by the CleverAgents UAT system. Feature area: Server and A2A integration | Test cycle: 1 | Generated: 2026-04-07


Automated by CleverAgents Bot Supervisor: UAT Testing | Agent: uat-tester