Skip to content

Latest commit

 

History

History
336 lines (269 loc) · 11.6 KB

File metadata and controls

336 lines (269 loc) · 11.6 KB
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
Pinning a client to the daemon contract
Checking stable API shapes and error envelopes
description The versioned coven.daemon.v1 contract under /api/v1: health negotiation, capability discovery, error envelopes, and additive compatibility rules.

Coven local API contract

The Coven daemon socket API is a public compatibility boundary for comux and external clients such as @opencoven/coven.

Current stable version

  • GET /api/v1/health exposes apiVersion: "coven.daemon.v1", covenVersion, and a machine-readable capabilities object.
  • Clients should read /api/v1/health before assuming any response shape from other endpoints.
  • Legacy unversioned routes such as GET /health remain 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 seq cursor for incremental reads.

GET /api/v1/health

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.

Capability fields

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.

Structured error envelope

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]
Loading

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.

Stable error codes

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.

Capability catalog shape (v1)

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, planned
  • policy: allow, requiresApproval

Clients should ignore unknown future capability ids and action ids unless they explicitly support them.

Control action shape (v1)

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`"
}

Session record shape (v1)

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/sessionsSessionRecord[]
  • POST /api/v1/sessionsSessionRecord
  • GET /api/v1/sessions/:idSessionRecord
{
  "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"
}

Event record shape and cursor pagination (v1)

GET /api/v1/events returns a paginated envelope with monotonic seq cursors.

Query parameters

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).

Response envelope

{
  "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.

Incremental read pattern

  1. Poll GET /events?sessionId=<id> to get all events (with optional limit).
  2. Use nextCursor.afterSeq in subsequent requests: GET /events?sessionId=<id>&afterSeq=<seq>.
  3. Repeat until hasMore is false.

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 }
Loading

Persisting afterSeq survives daemon restarts: events are append-only and seq numbers are monotonic, so a resumed poll always picks up where it stopped.

Live control response shapes (v1)

Both live-control endpoints return the same accepted response shape on success:

  • POST /api/v1/sessions/:id/input
  • POST /api/v1/sessions/:id/kill
{
  "ok": true,
  "accepted": true
}

Shared non-success responses use the structured error envelope:

  • 404 when the session does not exist:
{
  "error": {
    "code": "session_not_found",
    "message": "Session was not found.",
    "details": { "sessionId": "session-1" }
  }
}
  • 409 when the session exists but is not live:
{
  "error": {
    "code": "session_not_live",
    "message": "Session is not live.",
    "details": { "sessionId": "session-1" }
  }
}

comux and OpenClaw bridge compatibility

  • comux reads the capabilities object from /health to decide which features to use.
  • The @opencoven/coven OpenClaw bridge (packages/openclaw-coven) is updated in this repo alongside the daemon and uses apiVersion === "coven.daemon.v1" as its contract guard.
  • Client updates to use afterSeq cursors and paginated event envelopes may happen independently of the daemon update; the daemon-enforced shape is the source of truth.
  • The supportedApiVersions field has been removed from the health response in coven.daemon.v1; clients should check apiVersion directly.

Compatibility and migration policy

  • coven.daemon.v1 clients 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 apiVersion value exposed by GET /api/v1/health or 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.

Recommended client handshake

  1. Call GET /api/v1/health.
  2. Verify apiVersion === "coven.daemon.v1" and capabilities.structuredErrors === true.
  3. Check capabilities.eventCursor === "sequence" before using afterSeq pagination.
  4. Only then depend on the documented v1 sessions/events shapes.

Scope boundary

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.