Reconcile the living docs with the shipped Vault evidence surface: - README "Shipped surfaces": record the Vault P0 (honest empty/unavailable framing, #760), P1 (inspector depth + status/facet/text filters + evidence-rail progression, #762/#763/#764), and P2 (read-only exact-CGA recall, #766) arc as complete; enrich the Vault entry row. Read-only throughout — no runtime controls. - wave-R: extend the Vault surface bullet with the recall endpoint and the P0/P1/P2 completion note + the +inf-self-match-sentinel honesty point. - api-contract-v1: add GET /vault/entries/{index}/recall (purpose, errors, trust boundary, cross-link to the data shape). - data-shapes-v1: add VaultRecallHit / VaultRecall types. - UI-UX-GUIDE: enrich the Vault row (inspector depth, filters, rail, opt-in exact-CGA recall; read-only). Docs-only; no code or contract behavior change.
19 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_requestevidence_unavailablenot_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 |
| deterministic evidence source absent | 501 | evidence_unavailable |
| 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,
"leeway_evidence": null
}
}
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/{turn_id}
(Wave R3 — supersedes the unwired W-026 {artifact_id} placeholder.
Design record: docs/analysis/replay-moment-backend-scoping-2026-06-12.md.)
Purpose:
Re-execute a journaled turn's prompt in a sealed fresh runtime
(ChatRuntime(no_load_state=True) — no checkpoint load, no checkpoint
write, no proposal lineage) and compare the resulting envelope leaf-by-leaf
against the recorded TurnJournalEntry.
Important:
The API must not claim "equivalent": true unless a real re-execution has
run. Comparing a digest to itself is not replay evidence; if the replay
runtime fails, the route returns runtime_unavailable (500) and no
comparison object exists.
Honesty envelope: the claim demonstrated is same prompt, same genesis
substrate → bit-identical envelope. The original turn ran in its own fresh
runtime that may have loaded an engine-state checkpoint present at the
time; the journal does not record whether one existed, so origin_state is
"unrecorded" and a divergence means nondeterminism or origin-state
influence — the API never claims to distinguish them, and the frontend must
not render a divergence as a determinism-failure verdict.
Severity classes: every TurnJournalEntry field is classified exactly once
(enforced by test). critical divergences break equivalence; wall-clock
fields (timestamp, turn_cost_ms, journal_digest) are informational
and never do.
Errors: unknown or malformed turn_id → 404 not_found; replay runtime
failure → 500 runtime_unavailable. The route is read-only: it appends no
journal entry and writes no engine state.
Response:
{
"ok": true,
"data": {
"turn_id": 7,
"comparison_basis": "sealed_fresh_runtime_single_turn",
"origin_state": "unrecorded",
"original_trace_hash": "sha256:...",
"replay_trace_hash": "sha256:...",
"equivalent": true,
"replay_turn_cost_ms": 412,
"divergences": [
{
"path": "timestamp",
"original": "2026-06-12T18:00:00+00:00",
"replay": "2026-06-12T23:55:01+00:00",
"severity": "informational"
}
]
}
}
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
R2 Read Projections
These endpoints are read-only projections over existing artifacts. They do not create event stores, run stores, pack stores, or vault persistence.
GET /packs
Purpose:
List readable pack manifests from language_packs/data/*/manifest.json and
cleanly readable JSON manifests under packs/*/*/manifest.json.
Query:
limit: non-negative integer, default100offset: non-negative integer, default0
Trust boundary:
Pack list reads only fixed repository roots. GET /packs/{pack_id} validates
pack_id against the safe pattern [A-Za-z0-9][A-Za-z0-9_.-]{0,127} before
any filesystem access. Path traversal and encoded slashes return
400 bad_request.
Response:
{
"ok": true,
"data": {
"items": [
{
"pack_id": "en_core_cognition_v1",
"source": "language_pack",
"manifest_path": "language_packs/data/en_core_cognition_v1/manifest.json",
"version": "1.2.0",
"language": "en",
"modality": null,
"determinism_class": "D0",
"checksum": "82d5...",
"checksums": {
"checksum": "82d5..."
}
}
],
"limit": 100,
"offset": 0
}
}
Manifest checksum fields are surfaced verbatim from the manifest. The API also
returns a separate manifest_digest on detail, computed from the manifest file
bytes, so byte integrity and manifest-authored checksums remain distinct.
GET /packs/{pack_id}
Purpose:
Return one pack manifest projection. Unknown safe ids return 404 not_found;
unsafe ids return 400 bad_request.
Response includes the summary fields plus:
{
"manifest_digest": "sha256:...",
"manifest": {}
}
GET /audit/events
Purpose:
Return a merged audit timeline over existing deterministic artifacts:
engine_state/manifest.jsonwhen present,teaching/proposals/proposals.jsonl,teaching/math_proposals/proposals.jsonl,- persisted Workbench telemetry JSONL files under
workbench_data/.
The route does not invent or append an event store. Events are sorted by
timestamp, then source/path/type/id tiebreakers.
Query:
limit: non-negative integer, default100offset: non-negative integer, default0
Each event includes source and mutation_boundary. Mutation-boundary events
identify review transitions, accepted corpus appends, operator telemetry, or
engine checkpoint boundaries.
GET /runs
Purpose:
List deterministic run/session projections that can be derived from existing artifacts.
Current evidence sources:
workbench_data/turn_journal.jsonlasworkbench_turn_journalwhen turn entries exist.engine_state/manifest.jsonasengine_state_checkpointwhen a checkpoint manifest exists.
Known gap:
The current persisted artifacts do not record a separate durable user-facing
session id. The API exposes the artifact boundary and includes evidence_gap
instead of synthesizing a false session identity.
GET /runs/{session_id}
Purpose:
Return detail for a deterministic run projection. Unknown ids return
404 not_found. Turn journal details include paginated turns, where each turn
links to /trace/{turn_id}. Run detail also includes identity_continuity
when an engine-state manifest is available: a backend-owned projection of the
stamped engine_identity, parent_engine_identity, current recomputed engine
identity, lineage relation, and reboot-verification status. Legacy manifests
that do not stamp identity return missing_evidence; the route does not
synthesize continuity from absent fields.
Query:
limit: non-negative integer for turn refs, default100offset: non-negative integer for turn refs, default0
GET /contemplation/runs
Purpose:
List committed contemplation process reports from contemplation/runs/*.json.
The API does not execute contemplation and does not apply proposed actions.
Query:
limit: non-negative integer, default100offset: non-negative integer, default0
Each item includes the report filename stem as run_id, source path/digest,
prompt/cold-subject metadata when present, scene count, and the report-authored
boolean gates (learning_arc_closed, all_claims_supported,
active_corpus_byte_identical) as nullable evidence.
GET /contemplation/runs/{run_id}
Purpose:
Return one persisted contemplation process trace. Unknown or unsafe ids return
404 not_found. Detail includes before, after, the first engine/proposed
chain found in the scene details, and ordered scenes.
The route is an inspector over committed report evidence. It does not ratify findings, promote proposals, rerun contemplation, or synthesize a successful learning claim from absent scene fields.
GET /vault/summary
Purpose:
Return a cold summary of persisted vault evidence only when
engine_state/session_state.json contains a Shape B+ vault snapshot.
If the persisted snapshot is absent, the route returns:
{
"ok": false,
"error": {
"code": "evidence_unavailable",
"message": "vault evidence unavailable: engine_state/session_state.json is absent"
}
}
The route never reaches into live runtime memory.
GET /vault/entries
Purpose:
Return metadata for persisted vault entries. The endpoint surfaces metadata and a digest of each persisted versor encoding; it does not emit raw versor coordinates or approximate recall scores.
Query:
limit: non-negative integer, default100offset: non-negative integer, default0
GET /vault/entries/{index}/recall
Purpose:
Read-only proof that a persisted vault entry is recallable by CORE's actual
exact CGA machinery. Rehydrates the persisted VaultStore
(VaultStore.from_dict — bit-exact versors, no reprojection) and runs the real
VaultStore.recall using the selected entry's own stored versor as the query.
The exact cga_inner scan — never ANN / cosine / approximate.
recall's exact-self-match +inf sentinel never crosses the boundary: each hit
reports the genuine finite cga_inner plus an exact_self_match flag. The raw
versor never leaves the engine — only content-addressed digests. The persisted
file is never written and the live runtime is never touched.
Path parameter:
index: the entry'sentry_index(live deque position).
Errors:
- Non-integer / out-of-range
index→404 not_found. - Absent persisted snapshot →
501 evidence_unavailable.
See VaultRecall in data-shapes-v1.md.
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
Calibration / Serving Discipline
GET /calibration/classes
Purpose:
Read committed practice evidence and expose per-class reliability/license rows.
The reader imports core.reliability_gate for Wilson floors and license
decisions. It does not re-run a lane and does not mutate a license.
Response:
{
"ok": true,
"data": {
"items": [
{
"class_name": "additive",
"correct": 95,
"wrong": 5,
"refused": 50,
"committed": 100,
"reliability_floor": 0.86084162,
"coverage": 0.666666667,
"propose_required": 0.85,
"propose_licensed": true,
"serve_required": 0.99,
"serve_licensed": false,
"source_path": "evals/gsm8k_math/practice/v1/report.json",
"source_digest": "sha256:..."
}
]
}
}
GET /serving/metrics
Purpose:
Read committed serving reports and expose correct/refused/wrong counts. This is the source for the global wrong=0 frame. The route mirrors non-zero wrong counts honestly if a committed report contains them.
Response:
{
"ok": true,
"data": {
"items": [
{
"lane": "train_sample",
"correct": 4,
"refused": 46,
"wrong": 0,
"sample_count": 50,
"source_path": "evals/gsm8k_math/train_sample/v1/report.json",
"source_digest": "sha256:..."
}
]
}
}
Leeway Evidence Tuple
B4 annotations must use the nullable leeway_evidence field rather than
frontend inference:
{
"class_name": "additive",
"license": "PROPOSE",
"theta": 0.85,
"claim_disclosure": "approximate",
"source_digest": "sha256:...",
"calibration_evidence_ref": "calibration:additive"
}
When absent, the UI renders explicit absence. It must not infer this tuple from route-local calibration data.
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