# Envoy API Reference
All endpoints live at `http://127.0.0.1:9876`. Most require an `X-Agent-Id`
header identifying the calling agent.
---
## Core Endpoints
### `GET /health`
Health check. No auth required.
```json
{"status":"ok","uptime_seconds":152986,"agents_online":2}
```
### `GET /stats`
Server statistics (messages, agents, events, token counters).
### `GET /metrics`
Prometheus metrics endpoint. No auth required (public, like `/health`).
Returns Prometheus exposition format text with:
| `envoy_requests_total` | counter | `method`, `path`, `status` | Total HTTP requests by method, normalized path, and status class (2xx/4xx/5xx) |
| `envoy_request_duration_ms` | histogram | `path` | Request latency in milliseconds. Buckets: 0.5, 1, 5, 10, 50, 100, 500, 1000, 5000 |
| `envoy_agents_online` | gauge | (none) | Number of currently active agents |
| `envoy_messages_pending` | gauge | (none) | Reserved for future use (currently always 0) |
| `envoy_ws_connections` | gauge | (none) | Reserved for future use (currently always 0) |
Path segments that look like IDs (numeric, `id*` prefix, or UUID format) are collapsed to `:id` to prevent cardinality explosion. For example, `/agents/id1/messages/42/ack` becomes `/agents/:id/messages/:id/ack`.
Example scrape config:
```yaml
scrape_configs:
- job_name: 'envoy'
static_configs:
- targets: ['127.0.0.1:9876']
scrape_interval: 15s
metrics_path: /metrics
```
---
## Agent Lifecycle
### `POST /agents` -- Register
Registers a new agent or returns an existing one (idempotent by name).
**Request body:**
| Field | Type | Required | Notes |
|-------------|--------|----------|------------------------------------|
| `name` | string | yes | Human-readable name |
| `kind` | string | yes | Agent type (e.g. "claude", "hermes") |
| `parent_id` | string | no | Parent agent ID for subagents |
**Response (200):**
```json
{
"agent_id": "id1117",
"is_new": true,
"kind": "hermes",
"name": "claude-main",
"online": true,
"parent_id": null,
"lifecycle": "active"
}
```
The response `agent_id` must be used as `X-Agent-Id` in all subsequent calls.
Subagents receive hierarchical IDs (e.g. parent `id1` -> child `id1.1`).
### `GET /agents` -- List agents
Requires `X-Agent-Id` header.
### `GET /agents/{agent_id}` -- Get agent details
### `DELETE /agents/{agent_id}` -- Disconnect agent
### `POST /agents/{agent_id}/retire` -- Retire agent
Cascades to all children.
**Request body:** `{"agent_id": "id1117"}`
**Response:** `{"affected": ["id1117", "id1117.1"], "retired": true}`
### `POST /heartbeat`
Keep-alive ping.
---
## Messaging
Messages use a **poll-based** model. There is no push/WebSocket notification
for incoming messages -- agents must poll `/agents/{id}/messages/pending`
periodically. This is a known limitation inherited from the MCP query-based
interface.
### `POST /messages` -- Send message
**Request body:**
| `from` | string | yes | Sender agent ID |
| `to` | string | yes | Recipient agent ID |
| `type` | string | yes | "direct" or "broadcast" |
| `parts`| array | yes | Message parts (see below) |
Each part is a flat object: `{"text": "message content"}`.
**Response:** `{"message_id": "6751", "status": "delivered"}`
### `GET /agents/{agent_id}/messages/pending` -- Poll for messages
Returns undelivered messages for this agent.
### `GET /messages/{message_id}` -- Get message details
### `POST /messages/{message_id}/ack` -- Acknowledge message
**Request body:** `{"agent_id": "id1114"}`
**Response:** `{"acked_by": ["id1114"], "message_id": "6751"}`
---
## Circuit Breaker
### `GET /agents/{agent_id}/circuit`
Circuit breaker status for an agent.
### `POST /agents/{agent_id}/circuit/failure`
Record a circuit breaker failure event.
---
## Atheneum Sessions
All session/evidence endpoints are prefixed with `/atheneum/`.
### `POST /atheneum/sessions` -- Create session
**Request body:**
| `session_id` | string | yes | UUID, generated by client |
| `agent` | string | yes | Agent name |
| `project` | string | yes | Project name |
| `tool` | string | no | Default: "cli" |
| `trigger` | string | no | Default: "manual" |
| `model` | string | no | Model identifier |
| `git_branch` | string | no | Current git branch |
| `git_head` | string | no | Current HEAD commit SHA |
| `parent_session_id`| string | no | Parent session for subagents |
**Response:** `{"session_id": "...", "recorded": true}`
### `GET /atheneum/sessions` -- Query sessions
**Query params:** `project`, `last` (default 5), `parent_id`
### `PATCH /atheneum/sessions/{id}/end` -- End session
**Request body:**
| `exit_status` | string | yes |
| `prompt_count` | u32 | no |
| `tool_call_count` | u32 | no |
| `file_write_count` | u32 | no |
| `commit_count` | u32 | no |
| `test_run_count` | u32 | no |
| `total_input_tokens` | u64 | no |
| `total_output_tokens`| u64 | no |
| `total_cost_usd` | f64 | no |
**Response:** `{"status": "ended", "session_id": "..."}`
### `POST /atheneum/sessions/{id}/handover` -- Subagent handover
**Request body:**
| `summary` | string | yes | What was done |
| `files_changed`| string[] | no | Files modified |
| `outcome` | string | no | Default: "complete" |
**Response:** `{"recorded": true}`
---
## Evidence Recording
All evidence endpoints return `{"recorded": true}` on success.
### `POST /atheneum/prompts` -- Record a prompt
| `session_id` | string | yes |
| `role` | string | yes |
| `sequence` | u32 | no |
| `input_hash` | string | yes |
| `input_tokens` | u64 | no |
| `output_hash` | string | no |
| `output_tokens` | u64 | no |
| `latency_ms` | u64 | no |
| `model` | string | no |
| `cost_usd` | f64 | no |
### `POST /atheneum/tool-calls` -- Record a tool call
| `session_id` | string | yes |
| `tool_name` | string | yes |
| `tool_version` | string | no |
| `input_hash` | string | no |
| `input_summary` | string | no |
| `output_hash` | string | no |
| `output_summary` | string | no |
| `exit_status` | string | yes |
| `latency_ms` | u64 | no |
| `input_tokens_est`| u64 | no |
| `tool_category` | string | no |
### `POST /atheneum/file-writes` -- Record a file write
| `session_id` | string | yes |
| `file_path` | string | yes |
| `file_id` | string | no |
| `before_hash` | string | no |
| `after_hash` | string | no |
| `lines_added` | u32 | no |
| `lines_deleted`| u32 | no |
| `lines_changed`| u32 | no |
| `write_type` | string | no |
### `POST /atheneum/commits` -- Record a commit
| `session_id` | string | yes |
| `commit_sha` | string | yes |
| `parent_sha` | string | no |
| `message` | string | yes |
| `author` | string | yes |
| `files_changed` | u32 | no |
| `lines_inserted`| u32 | no |
| `lines_deleted` | u32 | no |
| `commit_type` | string | yes |
| `feature_tag` | string | no |
### `POST /atheneum/test-runs` -- Record a test run
| `session_id` | string | yes |
| `test_name` | string | yes |
| `test_suite` | string | no |
| `test_command` | string | no |
| `result` | string | yes |
| `duration_ms` | u64 | no |
| `logs_summary` | string | no |
| `commit_sha` | string | no |
### `POST /atheneum/fix-chains` -- Record a fix chain
| `session_id` | string | yes |
| `bug_commit_sha` | string | yes |
| `fix_commit_sha` | string | yes |
| `fix_type` | string | yes |
| `severity` | string | yes |
| `cycles_to_fix` | u32 | no |
| `time_to_fix_ms` | u64 | no |
### `POST /atheneum/bench-runs` -- Record a benchmark run
| `session_id` | string | yes |
| `bench_name` | string | yes |
| `mean_ns` | i64 | no |
| `median_ns` | i64 | no |
| `p95_ns` | i64 | no |
| `is_regression`| bool | no |
### `POST /atheneum/events` -- Record a generic event
| `session_id` | string | yes |
| `event_type` | string | yes |
| `entity_id` | string | yes |
| `payload` | object | yes |
### `GET /atheneum/events` -- Query events
**Query params:** `session_id`, `event_type`, `limit` (default 50)
---
## Discoveries & Knowledge
### `POST /atheneum/discoveries` -- Create discovery
| `agent` | string | yes |
| `discovery_type` | string | yes |
| `target` | string | yes |
| `metadata` | object | no |
**Response:** `{"discovery_id": 7502, "agent": "...", "target": "...", "discovery_type": "..."}`
### `GET /atheneum/discoveries` -- List discoveries
### `GET /atheneum/knowledge` -- Query knowledge base
### `GET /atheneum/search` -- Search knowledge
### `GET /atheneum/context` -- Get project context
**Query params:** `project`, `limit`
---
## Cross-Project Navigation
### `GET /atheneum/cross/search` -- Cross-project symbol search
**Query params:** `q`, `language`, `k` (default 5)
Searches symbols across all registered project databases.
**Response:**
```json
{
"query": "build_router",
"language": "rust",
"count": 3,
"results": [
{"project": "envoy", "id": 25495, "kind": "Symbol", "name": "build_router", ...}
]
}
```
### `GET /atheneum/cross/navigate` -- Cross-project graph walk
**Query params:** `q`, `language`, `k` (default 5), `depth` (default 1)
BFS from search hits into per-project subgraphs. Returns entities and edges.
**Response:**
```json
{
"count": 1,
"views": [
{
"project": "envoy",
"entry_id": 25495,
"entities": [...],
"edges": [{"id": 31638, "kind": "CALLER", "from_id": 25495, "to_id": 25518}]
}
]
}
```
---
## Graph Navigation
### `GET /atheneum/graph/stats` -- Graph statistics
Returns entity count, edge count, and per-kind breakdowns.
### `GET /atheneum/graph/entity/{id}` -- Get entity by ID
### `GET /atheneum/graph/entity/{id}/neighbors` -- Get neighbors
Returns incoming and outgoing edges for an entity.
### `GET /atheneum/graph/subgraph/{id}` -- BFS subgraph view
**Query params:** `depth` (default 2)
---
## Handoffs (Inter-Agent Work Transfer)
### `POST /atheneum/handoffs` -- Create handoff
### `GET /atheneum/handoffs/pending` -- Get pending handoff
### `POST /atheneum/handoffs/{id}/claim` -- Claim a handoff
---
## Tasks
### `POST /atheneum/tasks` -- Create task
### `GET /atheneum/tasks` -- List tasks
### `GET /atheneum/tasks/{id}` -- Task details
### `PATCH /atheneum/tasks/{id}/status` -- Update task status
### `POST /atheneum/tasks/{id}/requirements` -- Add requirement to task
---
## Event Ingestion
### `POST /events/hook` -- Hook event (envoy-hook)
### `POST /events/gate` -- Quality gate event
### `POST /events/ci` -- CI event
### `POST /events/doc` -- Documentation event
### `POST /events/verify` -- Verification event
### `GET /events` -- Query all events
### `GET /audit` -- Query audit trail
---
## Dependencies
### `POST /dependencies` -- Create dependency
### `GET /dependencies/blocker/{agent_id}` -- Get blockers
### `GET /dependencies/dependent/{agent_id}` -- Get dependents
### `POST /dependencies/{dep_id}/resolve` -- Resolve dependency
---
## Configuration
### `GET /nudge-config` -- Get nudge configuration
### `POST /nudge-config` -- Update nudge configuration
### `GET /projects/{name}/config` -- Get project config
### `POST /projects/{name}/config` -- Set project config
---
## Subscriptions
### `POST /subscriptions` -- Subscribe agent to project events
### `GET /subscriptions/{agent_id}` -- List subscriptions
### `DELETE /subscriptions/{agent_id}/{project}` -- Unsubscribe
---
## WebSocket
### `GET /ws/{agent_id}` -- WebSocket connection
Upgrade to WebSocket for real-time events. The standard MCP interface is
poll-based (no push), so this endpoint exists for future use.