Skip to content

Commit 3fbb1e8

Browse files
authored
feat(a2a): implement M12 A2A server (#98)
* feat(a2a): implement M12 A2A server with JSON-RPC routing, task management, and SSE streaming Add axum-based HTTP server to zeph-a2a crate behind feature-gated server module. Implements core A2A protocol server-side operations: JSON-RPC 2.0 dispatch for message/send, tasks/get, tasks/cancel; in-memory TaskManager with full lifecycle; SSE streaming with JSON-RPC response envelope; bearer token auth with constant-time comparison (subtle); per-IP rate limiting; 1 MiB body size limit. Refactor Part from flat struct to tagged enum with kind discriminator and rename TaskState::Pending to Submitted with explicit per-variant serde renames for spec-compliant kebab-case wire format. Resolves #83, #84, #85 * chore: bump version to 0.7.0, update README and Docker configs for A2A server - Bump workspace version 0.6.0 -> 0.7.0 - Add A2A Server section to README with endpoints and env vars - Add A2A environment variables to docker-compose.yml and docker-compose.dev.yml - Expose A2A port 8080 in Docker Compose services - Add zeph-a2a Cargo.toml to Dockerfile.dev dependency cache layer - Update CHANGELOG with 0.7.0 release date
1 parent 4e7bc77 commit 3fbb1e8

File tree

19 files changed

+2086
-46
lines changed

19 files changed

+2086
-46
lines changed

CHANGELOG.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,26 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
66

77
## [Unreleased]
88

9+
## [0.7.0] - 2026-02-08
10+
11+
### Added
12+
- A2A Server: axum-based HTTP server with JSON-RPC 2.0 routing for `message/send`, `tasks/get`, `tasks/cancel` (Issue #83)
13+
- In-memory `TaskManager` with full task lifecycle: create, get, update status, add artifacts, append history, cancel (Issue #83)
14+
- SSE streaming endpoint (`/a2a/stream`) with JSON-RPC response envelope wrapping per A2A spec (Issue #84)
15+
- Bearer token authentication middleware with constant-time comparison via `subtle::ConstantTimeEq` (Issue #85)
16+
- Per-IP rate limiting middleware with configurable 60-second sliding window (Issue #85)
17+
- Request body size limit (1 MiB) via `tower-http::limit::RequestBodyLimitLayer` (Issue #85)
18+
- `A2aServerConfig` with env var overrides: `ZEPH_A2A_ENABLED`, `ZEPH_A2A_HOST`, `ZEPH_A2A_PORT`, `ZEPH_A2A_PUBLIC_URL`, `ZEPH_A2A_AUTH_TOKEN`, `ZEPH_A2A_RATE_LIMIT`
19+
- Agent card served at `/.well-known/agent-card.json` (public, no auth required)
20+
- Graceful shutdown integration via tokio watch channel
21+
- Server module gated behind `server` feature flag on `zeph-a2a` crate
22+
23+
### Changed
24+
- `Part` type refactored from flat struct to tagged enum with `kind` discriminator (`text`, `file`, `data`) per A2A spec
25+
- `TaskState::Pending` renamed to `TaskState::Submitted` with explicit per-variant `#[serde(rename)]` for kebab-case wire format
26+
- Added `AuthRequired` and `Unknown` variants to `TaskState`
27+
- `TaskStatusUpdateEvent` and `TaskArtifactUpdateEvent` gained `kind` field (`status-update`, `artifact-update`)
28+
929
## [0.6.0] - 2026-02-08
1030

1131
### Added

Cargo.lock

Lines changed: 48 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,26 @@ resolver = "3"
55
[workspace.package]
66
edition = "2024"
77
rust-version = "1.88"
8-
version = "0.6.0"
8+
version = "0.7.0"
99
authors = ["bug-ops"]
1010
license = "MIT"
1111
repository = "https://github.com/bug-ops/zeph"
1212

1313
[workspace.dependencies]
1414
anyhow = "1.0"
15+
axum = "0.8"
1516
criterion = "0.8"
17+
futures = "0.3"
1618
eventsource-stream = "0.2"
19+
http-body-util = "0.1"
1720
futures-core = "0.3"
1821
notify = "8"
1922
notify-debouncer-mini = "0.7"
2023
ollama-rs = { version = "0.3", default-features = false, features = ["rustls", "stream"] }
2124
pulldown-cmark = "0.13"
2225
qdrant-client = { version = "1.16", default-features = false }
2326
reqwest = { version = "0.13", default-features = false }
27+
subtle = "2.6"
2428
serde = "1.0"
2529
serde_json = "1.0"
2630
serial_test = "3.3"
@@ -32,16 +36,18 @@ thiserror = "2.0"
3236
tokio = "1"
3337
tokio-stream = "0.1"
3438
toml = "0.9"
39+
tower = "0.5"
40+
tower-http = "0.6"
3541
tracing = "0.1"
3642
tracing-subscriber = "0.3"
3743
uuid = "1.20"
38-
zeph-a2a = { path = "crates/zeph-a2a", version = "0.6.0" }
39-
zeph-channels = { path = "crates/zeph-channels", version = "0.6.0" }
40-
zeph-core = { path = "crates/zeph-core", version = "0.6.0" }
41-
zeph-llm = { path = "crates/zeph-llm", version = "0.6.0" }
42-
zeph-memory = { path = "crates/zeph-memory", version = "0.6.0" }
43-
zeph-skills = { path = "crates/zeph-skills", version = "0.6.0" }
44-
zeph-tools = { path = "crates/zeph-tools", version = "0.6.0" }
44+
zeph-a2a = { path = "crates/zeph-a2a", version = "0.7.0" }
45+
zeph-channels = { path = "crates/zeph-channels", version = "0.7.0" }
46+
zeph-core = { path = "crates/zeph-core", version = "0.7.0" }
47+
zeph-llm = { path = "crates/zeph-llm", version = "0.7.0" }
48+
zeph-memory = { path = "crates/zeph-memory", version = "0.7.0" }
49+
zeph-skills = { path = "crates/zeph-skills", version = "0.7.0" }
50+
zeph-tools = { path = "crates/zeph-tools", version = "0.7.0" }
4551

4652
[workspace.lints.clippy]
4753
all = "warn"
@@ -57,7 +63,7 @@ repository.workspace = true
5763

5864
[features]
5965
default = ["a2a"]
60-
a2a = ["dep:zeph-a2a"]
66+
a2a = ["dep:zeph-a2a", "zeph-a2a?/server"]
6167

6268
[dependencies]
6369
anyhow.workspace = true

Dockerfile.dev

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ COPY crates/zeph-skills/Cargo.toml crates/zeph-skills/Cargo.toml
1414
COPY crates/zeph-memory/Cargo.toml crates/zeph-memory/Cargo.toml
1515
COPY crates/zeph-channels/Cargo.toml crates/zeph-channels/Cargo.toml
1616
COPY crates/zeph-tools/Cargo.toml crates/zeph-tools/Cargo.toml
17+
COPY crates/zeph-a2a/Cargo.toml crates/zeph-a2a/Cargo.toml
1718
RUN mkdir -p src && echo 'fn main() {}' > src/main.rs && \
1819
for d in crates/*/; do mkdir -p "$d/src" && echo '' > "$d/src/lib.rs"; done && \
1920
cargo build --release 2>/dev/null || true

README.md

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ docker pull ghcr.io/bug-ops/zeph:latest
4545
Or use a specific version:
4646

4747
```bash
48-
docker pull ghcr.io/bug-ops/zeph:v0.6.0
48+
docker pull ghcr.io/bug-ops/zeph:v0.7.0
4949
```
5050

5151
**Security:** Images are scanned with [Trivy](https://trivy.dev/) in CI/CD and use Oracle Linux 9 Slim base with **0 HIGH/CRITICAL CVEs**. Multi-platform: linux/amd64, linux/arm64.
@@ -128,6 +128,14 @@ enabled = true
128128
[tools.shell]
129129
timeout = 30
130130
blocked_commands = [] # Additional patterns beyond defaults
131+
132+
[a2a]
133+
enabled = false
134+
host = "0.0.0.0"
135+
port = 8080
136+
# public_url = "https://agent.example.com"
137+
# auth_token = "secret"
138+
rate_limit = 60
131139
```
132140

133141
</details>
@@ -152,6 +160,12 @@ blocked_commands = [] # Additional patterns beyond defaults
152160
| `ZEPH_MEMORY_CONTEXT_BUDGET_TOKENS` | Context budget for proportional token allocation (default: 0 = unlimited) |
153161
| `ZEPH_SKILLS_MAX_ACTIVE` | Max skills per query via embedding match (default: 5) |
154162
| `ZEPH_TOOLS_TIMEOUT` | Shell command timeout in seconds (default: 30) |
163+
| `ZEPH_A2A_ENABLED` | Enable A2A server (default: false) |
164+
| `ZEPH_A2A_HOST` | A2A server bind address (default: `0.0.0.0`) |
165+
| `ZEPH_A2A_PORT` | A2A server port (default: `8080`) |
166+
| `ZEPH_A2A_PUBLIC_URL` | Public URL for agent card discovery |
167+
| `ZEPH_A2A_AUTH_TOKEN` | Bearer token for A2A server authentication |
168+
| `ZEPH_A2A_RATE_LIMIT` | Max requests per IP per minute (default: 60) |
155169

156170
</details>
157171

@@ -261,7 +275,7 @@ context_budget_tokens = 8000 # Set to LLM context window size (0 = unlimited)
261275

262276
## Docker
263277

264-
**Note:** Docker Compose automatically pulls the latest image from GitHub Container Registry. To use a specific version, set `ZEPH_IMAGE=ghcr.io/bug-ops/zeph:v0.6.0`.
278+
**Note:** Docker Compose automatically pulls the latest image from GitHub Container Registry. To use a specific version, set `ZEPH_IMAGE=ghcr.io/bug-ops/zeph:v0.7.0`.
265279

266280
<details>
267281
<summary><b>🐳 Docker Deployment Options</b> (click to expand)</summary>
@@ -304,7 +318,7 @@ docker compose --profile gpu -f docker-compose.yml -f docker-compose.gpu.yml up
304318

305319
```bash
306320
# Use a specific release version
307-
ZEPH_IMAGE=ghcr.io/bug-ops/zeph:v0.6.0 docker compose up
321+
ZEPH_IMAGE=ghcr.io/bug-ops/zeph:v0.7.0 docker compose up
308322

309323
# Always pull latest
310324
docker compose pull && docker compose up
@@ -408,11 +422,27 @@ Found a vulnerability? See [SECURITY.md](SECURITY.md) for responsible disclosure
408422

409423
**Security contact:** Submit via GitHub Security Advisories (confidential)
410424

425+
## A2A Server (Optional)
426+
427+
Zeph includes an embedded [A2A protocol](https://github.com/a2aproject/A2A) server for agent-to-agent communication. When enabled, other agents can discover and interact with Zeph via the standard A2A JSON-RPC 2.0 API.
428+
429+
```bash
430+
ZEPH_A2A_ENABLED=true ZEPH_A2A_AUTH_TOKEN=secret ./target/release/zeph
431+
```
432+
433+
The server exposes:
434+
- `/.well-known/agent-card.json` — agent discovery (public, no auth)
435+
- `/a2a` — JSON-RPC endpoint (`message/send`, `tasks/get`, `tasks/cancel`)
436+
- `/a2a/stream` — SSE streaming endpoint
437+
438+
> [!TIP]
439+
> Set `ZEPH_A2A_AUTH_TOKEN` to secure the server with bearer token authentication. The agent card endpoint remains public per A2A spec.
440+
411441
## Feature Flags
412442

413443
| Feature | Default | Description |
414444
|---------|---------|-------------|
415-
| `a2a` | Enabled | [A2A protocol](https://github.com/a2aproject/A2A) client for agent-to-agent communication |
445+
| `a2a` | Enabled | [A2A protocol](https://github.com/a2aproject/A2A) client and server for agent-to-agent communication |
416446

417447
Disable optional features for a smaller binary:
418448

@@ -433,7 +463,7 @@ zeph (binary)
433463
├── zeph-memory SQLite + Qdrant, SemanticMemory orchestrator, summarization
434464
├── zeph-channels Telegram adapter (teloxide) with streaming
435465
├── zeph-tools ToolExecutor trait, ShellExecutor with bash parser
436-
└── zeph-a2a A2A protocol client, agent discovery, JSON-RPC 2.0 (optional)
466+
└── zeph-a2a A2A protocol client + server, agent discovery, JSON-RPC 2.0 (optional)
437467
```
438468

439469
</details>

config/default.toml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,18 @@ enabled = false
4242
# Maximum number of semantically relevant messages to recall
4343
recall_limit = 5
4444

45+
[a2a]
46+
# Enable A2A server for agent-to-agent communication
47+
enabled = false
48+
# Bind address
49+
host = "0.0.0.0"
50+
# HTTP port
51+
port = 8080
52+
# Public URL advertised in AgentCard (auto-generated if empty)
53+
public_url = ""
54+
# Rate limit: max requests per minute per IP (0 = unlimited)
55+
rate_limit = 60
56+
4557
[tools]
4658
# Enable tool execution (bash commands)
4759
enabled = true

0 commit comments

Comments
 (0)