| title | Coven local API contract (coven.daemon.v1) | ||
|---|---|---|---|
| summary | The versioned coven.daemon.v1 contract under /api/v1: health negotiation, capability discovery, error envelopes, and additive compatibility rules. | ||
| read_when |
|
||
| description | The versioned coven.daemon.v1 contract under /api/v1: health negotiation, capability discovery, error envelopes, and additive compatibility rules. |
The Coven daemon socket API is a public compatibility boundary for comux and external clients such as @opencoven/coven.
GET /api/v1/healthexposesapiVersion: "coven.daemon.v1",covenVersion, and a machine-readablecapabilitiesobject.- Clients should read
/api/v1/healthbefore assuming any response shape from other endpoints. - Legacy unversioned routes such as
GET /healthremain early-MVP aliases; new clients should use/api/v1. - Control-plane clients should discover capabilities before sending action ids.
- All API failures are returned as structured
{ "error": { "code", "message", "details" } }envelopes. - Events include a monotonic
seqcursor for incremental reads.
GET /api/v1/health returns daemon reachability, the named contract version, coven version, and machine-readable capabilities:
{
"ok": true,
"apiVersion": "coven.daemon.v1",
"covenVersion": "0.0.0",
"capabilities": {
"sessions": true,
"events": true,
"eventCursor": "sequence",
"structuredErrors": true
},
"daemon": {
"pid": 12345,
"startedAt": "2026-05-09T06:43:00Z",
"socket": "/Users/alice/.coven/coven.sock"
}
}If the daemon metadata is unavailable, daemon may be null.
| Field | Type | Description |
|---|---|---|
sessions |
boolean | Sessions API (/sessions, /sessions/:id) is available. |
events |
boolean | Events API (/events) is available. |
eventCursor |
string | Cursor type supported; "sequence" means afterSeq is stable. |
structuredErrors |
boolean | All errors use the { error: { code, message, details } } shape. |
flowchart TD
Req[Incoming request] --> Parse{Parse + version check}
Parse -- bad shape --> ErrInvalid["400 invalid_request"]
Parse -- unknown version --> ErrInvalid
Parse -- ok --> Route{Route exists?}
Route -- no --> ErrNotFound["404 not_found"]
Route -- yes --> Validate{Field validation}
Validate -- cwd outside root --> ErrCwd["400 project_root_violation"]
Validate -- unknown harness/action --> ErrInvalid
Validate -- ok --> Action{Resource lookup}
Action -- session missing --> ErrSession["404 session_not_found"]
Action -- session not live --> ErrLive["409 session_not_live"]
Action -- PTY spawn fails --> ErrPty["500 pty_spawn_failed"]
Action -- runtime down --> ErrRuntime["503 runtime_unavailable"]
Action -- internal panic --> ErrInternal["500 internal_error"]
Action -- ok --> Success[Documented success shape]
ErrInvalid & ErrNotFound & ErrCwd & ErrSession & ErrLive & ErrPty & ErrRuntime & ErrInternal -->|"{ error: { code, message, details } }"| Client[Client branches on code]
All API errors use the following stable envelope. Clients must branch on error.code, not error.message:
{
"error": {
"code": "session_not_found",
"message": "Session was not found.",
"details": {
"sessionId": "abc-123"
}
}
}details is optional and included when extra context is useful.
| Code | HTTP status | Description |
|---|---|---|
not_found |
404 | Generic route not found. |
invalid_request |
400 or 404 | Malformed request or unsupported API version. |
session_not_found |
404 | Session id does not exist. |
session_not_live |
409 | Session exists but is not running. |
project_root_violation |
400 | cwd is outside the declared project root. |
pty_spawn_failed |
500 | PTY harness could not be launched. |
runtime_unavailable |
503 | The session runtime is unavailable. |
internal_error |
500 | Unexpected internal error. |
GET /api/v1/capabilities returns the daemon/control-plane capability catalog. This is the intended OpenMeow handshake for deciding which actions to show or route through Coven.
{
"capabilities": [
{
"id": "coven.control.actions",
"label": "Coven control-plane action router",
"adapter": "coven-daemon",
"status": "available",
"policy": "allow",
"actions": ["coven.capabilities.refresh"]
},
{
"id": "desktop.automation",
"label": "Desktop automation adapters",
"adapter": "desktop-use",
"status": "planned",
"policy": "requiresApproval",
"actions": []
}
]
}Known enum values in v1:
status:available,plannedpolicy:allow,requiresApproval
Clients should ignore unknown future capability ids and action ids unless they explicitly support them.
POST /api/v1/actions accepts a policy-shaped action envelope. The daemon validates the action id before any adapter work is allowed.
{
"action": "coven.capabilities.refresh",
"origin": "open-meow",
"intentId": "intent-1",
"args": {}
}Immediately completed safe actions return 200:
{
"ok": true,
"accepted": true,
"action": "coven.capabilities.refresh",
"status": "completed",
"event": {
"kind": "capabilities.refreshed",
"action": "coven.capabilities.refresh",
"origin": "open-meow",
"intentId": "intent-1",
"payload": { "capabilities": 3 }
}
}Unknown action ids return 400 and fail closed:
{
"ok": false,
"accepted": false,
"action": "desktop.deleteEverything",
"status": "rejected",
"reason": "unknown action `desktop.deleteEverything`"
}In v1, session responses stay as raw JSON objects using the Rust daemon's snake_case field names.
Endpoints that return this shape:
GET /api/v1/sessions→SessionRecord[]POST /api/v1/sessions→SessionRecordGET /api/v1/sessions/:id→SessionRecord
{
"id": "session-1",
"project_root": "/repo",
"harness": "codex",
"title": "Fix the tests",
"status": "running",
"exit_code": null,
"archived_at": null,
"created_at": "2026-05-09T06:43:00Z",
"updated_at": "2026-05-09T06:43:05Z"
}GET /api/v1/events returns a paginated envelope with monotonic seq cursors.
| Parameter | Required | Description |
|---|---|---|
sessionId |
Yes | Session to fetch events for. |
afterSeq |
No | Return only events with seq > afterSeq (preferred). |
afterEventId |
No | Compatibility cursor — resolves to a sequence position. |
limit |
No | Maximum number of events to return (daemon-enforced, max 1000). |
{
"events": [
{
"seq": 42,
"id": "event-uuid",
"session_id": "session-uuid",
"kind": "output",
"payload_json": "{\"data\":\"hello\"}",
"created_at": "2026-05-09T06:43:10Z"
}
],
"nextCursor": {
"afterSeq": 42
},
"hasMore": false
}nextCursor is null when there are no events. hasMore is true when a limit was applied and more events may exist.
- Poll
GET /events?sessionId=<id>to get all events (with optionallimit). - Use
nextCursor.afterSeqin subsequent requests:GET /events?sessionId=<id>&afterSeq=<seq>. - Repeat until
hasMoreisfalse.
This gives clients stable incremental reads. Exactly-once delivery also requires client-side checkpointing and idempotency.
sequenceDiagram
participant Client
participant Daemon as /api/v1/events
Client->>Daemon: GET ?sessionId=S1
Daemon-->>Client: { events: [seq 1..50], nextCursor: { afterSeq: 50 }, hasMore: true }
Client->>Client: persist last seq = 50
Client->>Daemon: GET ?sessionId=S1&afterSeq=50
Daemon-->>Client: { events: [seq 51..78], nextCursor: { afterSeq: 78 }, hasMore: false }
Client->>Client: persist last seq = 78
note over Client,Daemon: Client crash + restart
Client->>Daemon: GET ?sessionId=S1&afterSeq=78
Daemon-->>Client: { events: [seq 79..82], nextCursor: { afterSeq: 82 }, hasMore: false }
Persisting afterSeq survives daemon restarts: events are append-only and seq numbers are monotonic, so a resumed poll always picks up where it stopped.
Both live-control endpoints return the same accepted response shape on success:
POST /api/v1/sessions/:id/inputPOST /api/v1/sessions/:id/kill
{
"ok": true,
"accepted": true
}Shared non-success responses use the structured error envelope:
404when the session does not exist:
{
"error": {
"code": "session_not_found",
"message": "Session was not found.",
"details": { "sessionId": "session-1" }
}
}409when the session exists but is not live:
{
"error": {
"code": "session_not_live",
"message": "Session is not live.",
"details": { "sessionId": "session-1" }
}
}- comux reads the
capabilitiesobject from/healthto decide which features to use. - The
@opencoven/covenOpenClaw bridge (packages/openclaw-coven) is updated in this repo alongside the daemon and usesapiVersion === "coven.daemon.v1"as its contract guard. - Client updates to use
afterSeqcursors and paginated event envelopes may happen independently of the daemon update; the daemon-enforced shape is the source of truth. - The
supportedApiVersionsfield has been removed from the health response incoven.daemon.v1; clients should checkapiVersiondirectly.
coven.daemon.v1clients may rely on the documented field names and top-level response shapes above.- Additive fields are backward compatible. Clients should ignore unknown fields when safe.
- Any incompatible change must ship under a new
apiVersionvalue exposed byGET /api/v1/healthor its successor route. - Before a client switches to a new major contract, the Coven repo should publish updated contract docs and a migration note that maps the old shape to the new one.
- Call
GET /api/v1/health. - Verify
apiVersion === "coven.daemon.v1"andcapabilities.structuredErrors === true. - Check
capabilities.eventCursor === "sequence"before usingafterSeqpagination. - Only then depend on the documented
v1sessions/events shapes.
The coven.daemon.v1 contract covers daemon health, capability discovery, action routing, sessions, events, live input, and live kill. Do not treat future orchestration, handoff, or task-routing route names as reserved API until they are implemented and documented in this file.