core/docs/workbench/api-contract-v1.md
Shay 8a24ebe726
feat(W-026): read-only workbench API (ADR-0160 Phase 1) (#292)
* feat(W-026): add read-only workbench API

* fix(workbench): harden read-only API review gaps
2026-05-26 10:16:35 -07:00

7.1 KiB

CORE Workbench API Contract v1

Status: draft Scope: read-only local operator API

This contract defines the initial API surface for CORE Workbench v1.

The API exists to expose:

  • runtime observability,
  • replay evidence,
  • proposal visibility,
  • eval visibility,
  • and trace inspection

without widening the mutation surface.

Transport

  • HTTP JSON
  • optional websocket stream later
  • local-first (127.0.0.1) in v1

Response envelope

All responses MUST include generated_at as an ISO-8601 UTC timestamp. generated_at is wall-clock UTC and not part of replay state. Clients hashing or caching responses must exclude this field. Successful responses:

{
  "ok": true,
  "generated_at": "2026-05-26T00:00:00Z",
  "data": {}
}

Errors:

{
  "ok": false,
  "generated_at": "2026-05-26T00:00:00Z",
  "error": {
    "code": "not_found",
    "message": "artifact not found"
  }
}

W-026 error codes:

  • bad_request
  • not_found
  • unsupported
  • read_error
  • eval_failed
  • runtime_unavailable

unauthorized is reserved for a later auth ADR. W-026 remains unauthenticated and local-only by default.

Exception mapping:

Condition HTTP status Error code
malformed request, invalid path, unsafe traversal 400 bad_request
well-formed missing artifact/proposal/lane/trace 404 not_found
artifact exceeds the W-026 read size limit 413 read_error
deferred W-027+ route 501 unsupported
filesystem read failure 500 read_error
unexpected runtime failure 500 runtime_unavailable

W-026 execution model

The W-026 API is a single-operator local service. It does not implement CORS preflight; browser clients must run on the same origin until W-027 defines the frontend serving model. Eval execution is serialized per server instance so two /evals/run requests cannot race over shared runtime checkpoint files.

Side effects on engine_state/

W-026 is read-only with respect to teaching corpora and pack data. Two routes touch runtime checkpoint surfaces governed by existing ADRs:

  • GET /runtime/status instantiates EngineStateStore and reads engine_state/manifest.json via load_manifest.
  • POST /evals/run with lane="contemplation_quality" transitively invokes the ADR-0159 replay baseline. That path can create normal ChatRuntime checkpoints under engine_state/, governed by ADR-0146 and ADR-0150.

These checkpoint writes are runtime artifacts, not teaching/corpus/pack mutation. They are intentionally excluded from the W-026 read-only snapshot invariant.


Runtime

GET /health

Purpose:

Basic liveness/readiness.

Response:

{
  "ok": true,
  "data": {
    "status": "ok"
  }
}

GET /runtime/status

Purpose:

Expose runtime metadata.

Response:

{
  "ok": true,
  "data": {
    "backend": "rust",
    "git_revision": "abc123",
    "engine_state_present": true,
    "checkpoint_revision": "abc123",
    "revision_warning": false,
    "active_session_id": "session-001"
  }
}

Chat

POST /chat/turn

Purpose:

Execute a normal runtime turn.

Request:

{
  "prompt": "What does alpha cause?"
}

Response:

{
  "ok": true,
  "data": {
    "turn_id": "turn-001",
    "surface": "alpha causes beta",
    "grounding_source": "teaching",
    "trace_hash": "sha256:...",
    "proposal_state": null,
    "replay_available": true
  }
}

Important:

This endpoint must use the existing runtime path. It must not introduce a parallel persistence layer.


Trace

GET /trace/{turn_id}

Purpose:

Inspect trace evidence for a turn.

W-026 status:

Trace storage is not implemented in W-026. Unknown turn ids MUST return 404 not_found. The API must never return a synthetic empty trace as a successful response.

Response:

{
  "ok": true,
  "data": {
    "turn_id": "turn-001",
    "surface": "...",
    "articulation_surface": "...",
    "walk_surface": "...",
    "trace_hash": "sha256:...",
    "grounding_source": "teaching",
    "replay_digest": "sha256:...",
    "proposal_refs": [],
    "admissibility": {
      "rejected_attempts": 0
    }
  }
}

Replay

GET /replay/{artifact_id}

Purpose:

Return replay comparison information.

Important:

The API must not claim "equivalent": true unless a real replay/compare path has run. Comparing an artifact digest to itself is not replay evidence. Until the replay theater is wired, this route should return unsupported or a non-equivalent comparison with explicit divergence evidence.

Response:

{
  "ok": true,
  "data": {
    "artifact_id": "artifact-001",
    "original_hash": "sha256:...",
    "replay_hash": null,
    "equivalent": false,
    "divergences": [
      {
        "path": "$",
        "original": "artifact digest",
        "replay": null,
        "severity": "info"
      }
    ]
  }
}

Proposals

GET /proposals

Purpose:

List proposal metadata.

Implementation rule:

Proposal reads MUST derive their current view by replaying teaching.proposals.ProposalLog.current_state(). The append-only JSONL file is an event log, not a table of proposal records.

Response:

{
  "ok": true,
  "data": {
    "items": [
      {
        "proposal_id": "proposal-001",
        "state": "pending",
        "source_kind": "contemplation",
        "replay_equivalent": true,
        "created_at": "2026-05-26T00:00:00Z"
      }
    ]
  }
}

GET /proposals/{proposal_id}

Purpose:

Detailed proposal inspection.

Response:

{
  "ok": true,
  "data": {
    "proposal_id": "proposal-001",
    "state": "pending",
    "source_kind": "contemplation",
    "replay_equivalent": true,
    "proposed_chain": {},
    "replay_evidence": {},
    "downstream_effect": {},
    "artifact_refs": []
  }
}

Forbidden:

  • accept endpoint
  • reject endpoint
  • mutation endpoint

Evals

GET /evals

Purpose:

List eval lanes.

Response:

{
  "ok": true,
  "data": {
    "lanes": [
      "cognition",
      "learning-arc",
      "contemplation-quality"
    ]
  }
}

POST /evals/run

Purpose:

Run a read-only eval lane.

Request:

{
  "lane": "contemplation-quality"
}

Response:

{
  "ok": true,
  "data": {
    "lane": "contemplation-quality",
    "passed": true,
    "metrics": {}
  }
}

Constraints:

  • only lanes explicitly marked safe/read-only may run through the API
  • mutation-capable workflows are forbidden in v1

Artifacts

GET /artifacts

Purpose:

Enumerate readable runtime artifacts.

Response:

{
  "ok": true,
  "data": {
    "items": []
  }
}

GET /artifacts/{artifact_id}

Purpose:

Read structured artifact content.

Constraints:

  • artifacts must be repo-root constrained
  • no arbitrary path traversal
  • no arbitrary shell execution

Security Constraints

Forbidden in v1

  • proposal mutation
  • corpus mutation
  • workflow dispatch
  • arbitrary file reads
  • remote execution
  • arbitrary command execution
  • hidden background jobs

Required

  • deterministic serialization
  • stable artifact ids
  • digest visibility
  • explicit provenance where available
  • read-only route tests