9 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_requestnot_foundunsupportedread_erroreval_failedruntime_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 Workbench API is a single-operator local service. It emits a narrow local
development CORS response for the Vite workbench origin
http://127.0.0.1:5173; this supports W-028's documented split-server manual
integration workflow without making the API a remote service. 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/statusinstantiatesEngineStateStoreand readsengine_state/manifest.jsonviaload_manifest.POST /evals/runwithlane="contemplation_quality"transitively invokes the ADR-0159 replay baseline. That path can create normalChatRuntimecheckpoints underengine_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 and return the full UI evidence envelope.
This is the only Workbench POST route that touches the live chat runtime. Turns are serialized per server instance by a module-level lock, matching ADR-0160's single-operator-local v1 doctrine.
Request:
{
"prompt": "What does alpha cause?"
}
Validation:
promptis required and must be a string.prompt.strip()must be non-empty.promptmust not exceed 4096 characters.- request
Content-Lengthmust not exceed 64 KiB.
Invalid prompt shape returns 400 bad_request. Oversize request bodies return
413 read_error.
Response:
{
"ok": true,
"generated_at": "2026-05-26T00:00:00Z",
"data": {
"prompt": "What does alpha cause?",
"surface": "alpha causes beta",
"articulation_surface": "alpha causes beta",
"walk_surface": "alpha -> beta",
"grounding_source": "teaching",
"epistemic_state": "decoded",
"normative_clearance": "cleared",
"normative_detail": "",
"trace_hash": "sha256:...",
"refusal_emitted": false,
"hedge_injected": false,
"mutation_mode": "runtime_turn",
"identity_verdict": {
"outcome": "cleared",
"runtime_detail": ""
},
"safety_verdict": {
"outcome": "cleared",
"runtime_detail": ""
},
"ethics_verdict": {
"outcome": "cleared",
"runtime_detail": ""
},
"proposal_candidates": [
{
"candidate_id": "abc123",
"source_kind": "discovery"
}
],
"turn_cost_ms": 17,
"checkpoint_emitted": true
}
}
Important:
This endpoint must use the existing runtime path. It must not introduce a parallel persistence layer.
Mutation boundary:
- A chat turn may write
engine_state/through the normal runtime checkpoint path governed by ADR-0146 and ADR-0150.checkpoint_emittedreports whether that occurred. - A chat turn must not mutate
teaching/,packs/, orlanguage_packs/data/. - A chat turn must not auto-accept proposals.
proposal_candidatescontains candidate identifiers only; it does not expose proposal acceptance/rejection affordances or candidate surfaces.surface,articulation_surface, andwalk_surfaceremain distinct. The user-facing response issurface;walk_surfaceis telemetry/evidence.
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