API REFERENCE
API reference
Rendered from canonical revision 2ccb1710c71144e239606b5a43536f70f2582714.
openapi: 3.1.0
info:
title: Remudero Daemon Service Surface
version: 0.1.0
description: >
The daemon's ONE tailnet service surface (MASTER-PLAN §7A): REST + SSE,
single port, bearer-scoped (read vs. write). No client gets a private
backdoor -- packages/api-client is GENERATED from this document (see
scripts/generate-api-client.mjs) and is the ONLY way any client talks to
the daemon.
v0 (W3-T1a, src/lib/service.ts) shipped the generic mechanism only: bearer
read/write scope enforcement, a shared JSON error envelope, and SSE
framing, with `paths` intentionally empty -- no business route yet.
W3-T2 (MASTER-PLAN §7, WS-5a -- the read-only live board) adds the first
two: GET /v1/status (task/ledger-state snapshot) and GET /v1/status/stream
(its SSE live-update companion), built on that same mechanism via
src/lib/board.ts.
W3-T5 (MASTER-PLAN §7 -- human-in-the-loop panel actions) adds the first
write routes: POST /v1/control/{pause,resume,stop},
POST /v1/questions/answer, and POST /v1/manual/approve, all write-scoped
and built via src/lib/panel-actions.ts.
W3-T6 (MASTER-PLAN §7B -- the plan->task->PR graph + interactive plan
adjustment) adds GET /v1/feedback (the inbox), POST /v1/feedback (submit
feedback / answer a grill, origin=ui), GET /v1/trace (the plan->task->PR
provenance graph, W1-T43), and POST /v1/feedback/decision (accept/reject a
proposal), all built via src/lib/panel-graph.ts.
W3-T8 (MASTER-PLAN §5B/§7 -- panel skill actions) adds GET /v1/skills: the
panel's action-button set, resolved fresh from `.remudero/skills/<name>.yaml`
(the SAME registry `rmd skill list` reads, W1-T44) via src/lib/panel-skills.ts
-- one button per registry entry, generated, never hard-coded. POST
/v1/skills/run (src/lib/panel-skill-run.ts) invokes one: today `{ skill:
"plan", mode: "clarify" }` (Refine) is wired, grounding via the real §5C task
linter and parking a `grilling` feedback entry the existing GET/POST
/v1/feedback (W3-T6) already renders inline and answers.
Every route the daemon's OTHER surfaces still need (re-prioritize, ...) is
registered by later tasks the same way -- this document is additive, never
rewritten wholesale.
servers:
- url: "https://{tailnetHost}"
description: The daemon over Tailscale, one port for REST and SSE alike.
variables:
tailnetHost:
default: localhost:4317
components:
securitySchemes:
bearerRead:
type: http
scheme: bearer
description: >
Read-scoped bearer token. Grants GET access to read-scoped routes and
SSE streams. A write-scoped token also satisfies this scope (write is
a superset of read).
bearerWrite:
type: http
scheme: bearer
description: >
Write-scoped bearer token. Required for any route whose `scope` is
`write` (src/lib/service.ts's `Scope`).
schemas:
Error:
type: object
description: >
The JSON error envelope every non-2xx response on the surface
returns (src/lib/service.ts's `sendJson` error paths).
required:
- error
properties:
error:
type: string
description: >
`unauthorized` (401, no/unrecognized bearer token), `forbidden`
(403, recognized token missing the required scope), `not_found`
(404, no route registered for this method + path),
`invalid_request` (400, a write route's JSON body failed
validation -- W3-T5's panel-action routes fail loud BEFORE any
side effect, src/lib/panel-actions.ts's `jsonAction`), or
`internal_error` (500, the route handler threw).
enum:
- unauthorized
- forbidden
- not_found
- invalid_request
- internal_error
required_scope:
type: string
description: Present only on a 403 -- the scope the caller's token was missing.
enum:
- read
- write
required_tier:
type: string
description: >
W1-T404 -- present only on a 403 refused for an insufficient WRITE TIER (once
src/lib/service.ts's `enforceWriteTiers` is turned on; not yet set by `rmd serve`'s
own production wiring). `low` (bookkeeping), `middle` (reversible but disruptive,
or a spend force multiplier) or `high` (spends money or moves code) -- the tier the
caller's token was missing, alongside `required_scope: write`.
enum:
- low
- middle
- high
StatusProjection:
type: object
description: >
One task's projected merge-state, derived from GitHub (src/lib/status.ts's
`StatusProjection` -- never written back to plan/tasks.yaml). This is the
per-task "live state" the read-only board (W3-T2) renders.
required:
- taskId
- status
- merged
- source
properties:
taskId:
type: string
description: The plan task id (plan/tasks.yaml's `id`).
status:
type: string
description: Derived status label in the plan's vocabulary (src/lib/plan.ts's TaskStatus).
enum:
- queued
- recon
- prompted
- running
- review
- fixing
- diagnosing
- blocked
- merged
- done
merged:
type: boolean
description: The single fact dependency-gating cares about -- has this task landed?
source:
type: string
description: Which precedence source resolved this projection (`none` if GitHub was read but had no evidence; `throttled` if GitHub could not be read at all).
enum:
- ledger
- pr-field
- trailer
- correction
- none
- throttled
prNumber:
type: number
prUrl:
type: string
prState:
type: string
rejected_candidates:
type: array
description: >
Trailer search hits rejected by the ownership/anchor asserts, each with a
machine-readable reason. Present only when a candidate was actually rejected.
items:
type: object
required:
- pr
- reason
properties:
pr:
type: string
reason:
type: string
phase:
type: string
enum: [recon, implement, review, fix-rung]
startedAt:
type: string
format: date-time
elapsedMs:
type: number
minimum: 0
workerState:
type: string
enum: [working, tool-executing, quiet]
workerStateSince:
type: string
format: date-time
workerTelemetry:
type: object
description: >
Bounded worker observability. It names the active role/provider/model and the
current or last tool, but never carries prompts, tool arguments, or tool output.
properties:
role:
type: string
enum: [recon, implementer, reviewer, fixer, triage, retro, unknown]
provider:
type: string
requestedModel:
type: string
servedModel:
type: string
currentTool:
type: string
currentToolReason:
type: string
currentToolStartedAt:
type: string
format: date-time
lastTool:
type: object
required: [name, durationMs, completedAt]
properties:
name:
type: string
durationMs:
type: number
minimum: 0
completedAt:
type: string
format: date-time
outcome:
type: string
enum: [success, error]
lastEventAt:
type: string
format: date-time
lastEventKind:
type: string
enum: [working, tool-executing, message]
firstSignalAt:
type: string
format: date-time
firstSignalLatencyMs:
type: number
minimum: 0
StatusSnapshot:
type: object
description: >
GET /v1/status's body -- one StatusProjection per plan task, as of `generated_at`, plus
(W1-T163, when the daemon's per-token last-seen marker store is wired) the calling
token's own "since you last checked" recap.
required:
- generated_at
- tasks
properties:
generated_at:
type: string
tasks:
type: array
items:
$ref: "#/components/schemas/StatusProjection"
recap:
type: array
description: >
W1-T163: every recap-worthy event (src/lib/recap.ts) after this token's PRIOR
marker -- absent entirely on a daemon with no last-seen store wired; `[]` on this
token's first-ever view (nothing to recap FROM).
items:
$ref: "#/components/schemas/RecapEvent"
sinceCheckpoint:
type: string
description: >
W1-T163: this token's marker value BEFORE this request advanced it -- the timestamp
`recap` was computed as-of. Absent alongside `recap` for the same two reasons.
RepoDashboardHealth:
type: object
description: >
The health projection for one managed repository. The current daemon has no per-repo
health source, so every measurement is explicitly unknown rather than rendered as zero or
healthy.
required:
- status
- queuedtasks
- errorrate
- last_run
- alerts
properties:
status:
type: string
enum:
- unknown
queuedtasks:
type: [integer, "null"]
errorrate:
type: [number, "null"]
last_run:
type: [string, "null"]
alerts:
type: [array, "null"]
items:
type: string
RepoDashboardTelemetry:
type: object
description: Per-repository telemetry, unavailable until a durable per-repo aggregation source exists.
required:
- tokens7d
- modelsused
- cost_7d
properties:
tokens7d:
type: [number, "null"]
modelsused:
type: array
items:
type: string
cost_7d:
type: [number, "null"]
RepoDashboardSettings:
type: object
description: Per-repository settings, unavailable until durable settings persistence exists.
required:
- proofpolicy
- workerpoolsize
- alertthreshold
properties:
proofpolicy:
type: [string, "null"]
workerpoolsize:
type: [integer, "null"]
alertthreshold:
type: [number, "null"]
RepoDashboardEntry:
type: object
description: >
One repository from the validated `.remudero/managed-repos.json` set. Managed membership
is not evidence of OAuth connection, activation, health, telemetry, or settings.
required:
- id
- reponame
- repourl
- connected_at
- active
- managed
- source
- health
- telemetry
- settings
properties:
id:
type: string
description: Canonical `owner/repo` identity from the managed-repo set.
reponame:
type: string
description: Repository name from the canonical identity.
repourl:
type: string
description: Deterministic GitHub URL for the canonical identity; no GitHub read is implied.
connected_at:
type: [string, "null"]
active:
type: [boolean, "null"]
managed:
type: boolean
source:
type: string
enum:
- managed-repos
health:
$ref: "#/components/schemas/RepoDashboardHealth"
telemetry:
$ref: "#/components/schemas/RepoDashboardTelemetry"
settings:
$ref: "#/components/schemas/RepoDashboardSettings"
RepoDashboardResult:
type: object
description: >
GET /v1/repos's read-only managed-repo portfolio. An empty `repos` array is a measured
empty managed set; it is not an unavailable response.
required:
- generated_at
- source
- repos
properties:
generated_at:
type: string
source:
type: string
enum:
- managed-repos
repos:
type: array
items:
$ref: "#/components/schemas/RepoDashboardEntry"
RecapEvent:
type: object
description: >
W1-T163 -- one "since you last checked" row (src/lib/recap.ts): a single ledger event
after the caller's per-token marker.
required:
- kind
- taskId
- ts
properties:
kind:
type: string
enum:
- merged
- blocked
- escalated
- question_answered
- retro
taskId:
type: string
description: The originating ledger line's `task_id` verbatim -- `RETRO` for a fleet-wide retro event.
ts:
type: string
detail:
type: string
description: A short human-readable detail (verdict string, escalation class, answer text, retro stats).
title:
type: string
description: The plan task's own title -- present only when `taskId` names a real plan task.
taskCardLink:
type: string
description: >
A same-page `#task=<id>` hash link into the console's task card -- present ONLY
when `taskId` names a real plan task (a fleet-wide event like `retro` has none).
# ── W3-T5 (MASTER-PLAN §7, human-in-the-loop panel actions) -- request/result shapes for
# the six write routes src/lib/panel-actions.ts registers. Every one is write-scoped and
# ledgered with the caller's bearer as `origin` (src/lib/panel-actions.ts's `bearerTokenId`).
PauseRequest:
type: object
description: POST /v1/control/pause's body -- drain-and-hold, an optional human-readable reason.
properties:
reason:
type: string
PauseResult:
type: object
required:
- paused
properties:
paused:
type: boolean
reason:
type:
- string
- "null"
ResumeResult:
type: object
description: POST /v1/control/resume's body -- clears BOTH STOP and PAUSE; reports what it cleared.
required:
- clearedStop
- clearedPause
properties:
clearedStop:
type: boolean
clearedPause:
type: boolean
StopRequest:
type: object
description: POST /v1/control/stop's body -- the hard kill, an optional human-readable reason.
properties:
reason:
type: string
StopResult:
type: object
required:
- stopped
properties:
stopped:
type: boolean
reason:
type:
- string
- "null"
AnswerQuestionRequest:
type: object
description: >
POST /v1/questions/answer's body -- an operator's answer to a QUESTION-contract entry
(worker.ts's plan/questions.ndjson), addressed by the task it was raised on (v0 routing
has no path params, src/lib/service.ts).
required:
- taskId
- answer
properties:
taskId:
type: string
answer:
type: string
AnswerQuestionResult:
type: object
required:
- ok
- taskId
- answer
properties:
ok:
type: boolean
taskId:
type: string
answer:
type: string
ApproveManualRequest:
type: object
description: >
POST /v1/manual/approve's body -- check off a MANUAL-queue item (MASTER-PLAN §4): closes
the named `escalation-manual`-labeled GitHub issue (src/lib/escalate.ts).
required:
- taskId
- issueUrl
properties:
taskId:
type: string
issueUrl:
type: string
ApproveManualResult:
type: object
required:
- ok
- taskId
- issueUrl
properties:
ok:
type: boolean
taskId:
type: string
issueUrl:
type: string
MarkEscalationHandledRequest:
type: object
description: >
POST /v1/escalation/mark-handled's body (W1-T182) -- the NEEDS ME affordance an
ESCALATION row (any class: BLOCKED/MANUAL/HARD_STOP/GRILL) actually supports, distinct
from ApproveManualRequest's MANUAL-queue check-off: "approve" has no defined verb for an
escalation. Closes the named `needs-human`-labeled GitHub issue (src/lib/escalate.ts);
the name is deliberately "mark handled", not "approve" or "resolve" -- closing the issue
does not, by itself, imply the underlying block is fixed.
required:
- taskId
- issueUrl
properties:
taskId:
type: string
issueUrl:
type: string
MarkEscalationHandledResult:
type: object
required:
- ok
- taskId
- issueUrl
properties:
ok:
type: boolean
taskId:
type: string
issueUrl:
type: string
# ── W3-T6 (MASTER-PLAN §7B) -- the plan->task->PR graph + interactive plan adjustment:
# submit feedback, answer grills, accept/reject a proposal. Built over the SAME feedback
# inbox (src/lib/feedback.ts) and trace chain (src/lib/trace.ts, W1-T43) already documented
# there; the four routes below are src/lib/panel-graph.ts's registrations.
FeedbackEntry:
type: object
description: >
One `plan/feedback/<id>.yaml` entry (src/lib/feedback.ts's `FeedbackEntry` -- the
§7B schema shape: capture -> triage -> gate).
required:
- id
- ts
- raw
- attachments
- origin
- status
- proposal_pr
properties:
id:
type: string
ts:
type: string
raw:
type: string
attachments:
type: array
items:
type: string
origin:
type: string
enum:
- cli
- ui
- issue
status:
type: string
enum:
- new
- grilling
- proposed
- accepted
- rejected
proposal_pr:
type:
- string
- "null"
description: Set once `rmd triage` opens a proposal PR for this entry; null until then.
unverified:
type: boolean
description: >
GET /v1/feedback only (W1-T257): true when this `proposed` entry's proposal_pr merge
state could not be read (GitHub outage) -- the row is kept, never dropped. Never
written to plan/feedback/<id>.yaml; a read-time decoration only.
discharged:
type: boolean
description: >
GET /v1/feedback only (W1-T1257): true when every task this entry filed
(`origin: feedback#<id>`) is credited MERGED -- the work the proposal produced has
shipped, even though `status` still names a decision about the proposal itself.
Derived fresh on every read, like `unverified` above; never written to
plan/feedback/<id>.yaml and never auto-advances `status` -- whether a discharged
entry should advance stays a human call.
dischargeUndecidable:
type: boolean
description: >
GET /v1/feedback only (W1-T1257): true when `discharged` could not be determined
because the merged-set read failed or was truncated -- a partial read, never
mistaken for "not discharged". Mutually exclusive with `discharged`; a read-time
decoration only, exactly like `unverified`.
FeedbackInboxResult:
type: object
description: GET /v1/feedback's body -- every captured feedback entry, oldest first.
required:
- entries
properties:
entries:
type: array
items:
$ref: "#/components/schemas/FeedbackEntry"
SubmitFeedbackRequest:
type: object
description: >
POST /v1/feedback's body -- submit feedback from the panel (ALWAYS captured with
origin: ui, never taken from this body). `replyTo`, if given, must name an existing
entry parked `grilling` -- this is "answer a grill" v1 (src/lib/panel-graph.ts's header
explains why): the answer is captured as a fresh feedback entry that re-enters triage,
rather than a second, parallel answer-delivery primitive ahead of the still-unbuilt
W1-T42 grill mechanics.
required:
- text
properties:
text:
type: string
attachments:
type: array
description: http(s) links ONLY -- a local file path would resolve against the daemon's own filesystem, not the operator's.
items:
type: string
replyTo:
type: string
description: The `grilling` feedback id this submission answers, if any.
SubmitFeedbackResult:
type: object
required:
- ok
- entry
properties:
ok:
type: boolean
entry:
$ref: "#/components/schemas/FeedbackEntry"
OperatorActivityItem:
type: object
description: One bounded activity, workstream, or authoritative artifact row.
required:
- id
- kind
- summary
- source
- observedAt
- freshness
properties:
id:
type: string
kind:
type: string
enum:
- activity
- workstream
- artifact
summary:
type: string
source:
type: string
observedAt:
type: string
format: date-time
freshness:
type: string
enum:
- verified
- stale
- unavailable
- unknown
- not-collected
taskId:
type: string
repository:
type: string
state:
type: string
enum:
- active
- blocked
- queued
- completed
- unknown
reason:
type: string
href:
type: string
OperatorActivityResult:
type: object
description: GET /v1/operator-activity's bounded, source-labeled read projection.
required:
- version
- state
- source
- observedAt
properties:
version:
type: string
enum:
- operator-activity-v1
state:
type: string
enum:
- verified
- stale
- unavailable
- unknown
- not-collected
source:
type: string
observedAt:
type: string
format: date-time
cursor:
type: string
items:
type: array
maxItems: 200
items:
$ref: "#/components/schemas/OperatorActivityItem"
truncated:
type: boolean
reason:
type: string
detail:
type: string
TraceRun:
type: object
description: One run named on a task's ledger lines (src/lib/trace.ts's `TraceRun`).
required:
- runId
properties:
runId:
type: string
verdict:
type: string
prUrl:
type: string
prState:
type: string
mergeSha:
type: string
TraceTaskNode:
type: object
required:
- id
- title
- runs
properties:
id:
type: string
title:
type: string
origin:
type: string
runs:
type: array
items:
$ref: "#/components/schemas/TraceRun"
TraceFeedbackNode:
type: object
required:
- id
- raw
- ts
- origin
- status
properties:
id:
type: string
raw:
type: string
ts:
type: string
origin:
type: string
status:
type: string
proposalPr:
type: string
proposalPrState:
type: string
proposalMergeSha:
type: string
TraceChain:
type: object
description: >
The plan->task->PR provenance chain the panel renders as a graph (src/lib/trace.ts's
`TraceChain`, W1-T43): a feedback -> proposal PR -> task(s) -> run(s) -> PR(s) -> sha,
entered either FORWARD (from a feedback id) or REVERSE (from a task id).
required:
- direction
- tasks
properties:
direction:
type: string
enum:
- forward
- reverse
feedback:
$ref: "#/components/schemas/TraceFeedbackNode"
tasks:
type: array
items:
$ref: "#/components/schemas/TraceTaskNode"
TraceResult:
type: object
description: GET /v1/trace's body -- the structured chain (for the graph render) plus the pre-rendered plain-text tree (`rmd trace`'s own output).
required:
- chain
- rendered
properties:
chain:
$ref: "#/components/schemas/TraceChain"
rendered:
type: string
ProposalDecisionRequest:
type: object
description: POST /v1/feedback/decision's body -- accept or reject a `proposed` entry.
required:
- id
- decision
properties:
id:
type: string
decision:
type: string
enum:
- accept
- reject
ProposalDecisionResult:
type: object
required:
- ok
- id
- status
- proposalPr
properties:
ok:
type: boolean
id:
type: string
status:
type: string
proposalPr:
type:
- string
- "null"
OperatorAgentEvidence:
type: object
required:
- label
- value
- source
- observedAt
- freshness
properties:
label:
type: string
value:
type: string
source:
type: string
observedAt:
type: string
format: date-time
freshness:
type: string
enum: [verified, stale, unavailable]
OperatorAgentProposal:
type: object
required:
- proposalId
- repo
- proposalText
- confidence
- reasoning
- category
- status
- createdAt
- evidence
properties:
proposalId:
type: string
repo:
type: string
proposalText:
type: string
confidence:
type: number
minimum: 0
maximum: 1
reasoning:
type: string
category:
type: string
enum: [optimize, fix, scale]
status:
type: string
enum: [pending, accepted, rejected, expired]
createdAt:
type: string
format: date-time
expiresAt:
type: string
format: date-time
evidence:
type: array
items:
$ref: "#/components/schemas/OperatorAgentEvidence"
OperatorAgentHistory:
type: object
required:
- proposalId
- repo
- proposalText
- confidence
- reasoning
- category
- status
- createdAt
- evidence
- decisionHistory
properties:
proposalId:
type: string
repo:
type: string
proposalText:
type: string
confidence:
type: number
reasoning:
type: string
category:
type: string
enum: [optimize, fix, scale]
status:
type: string
enum: [pending, accepted, rejected, expired]
createdAt:
type: string
format: date-time
expiresAt:
type: string
format: date-time
evidence:
type: array
items:
$ref: "#/components/schemas/OperatorAgentEvidence"
decisionHistory:
type: array
items:
type: object
required: [decision, at]
properties:
decision:
type: string
enum: [accepted, rejected, more-info]
at:
type: string
format: date-time
note:
type: string
outcome:
type: object
required: [summary, observedAt]
properties:
summary:
type: string
helped:
type: boolean
observedAt:
type: string
format: date-time
evidence:
type: array
items:
type: string
OperatorAgentProposalList:
type: object
required: [proposals, source]
properties:
proposals:
type: array
items:
$ref: "#/components/schemas/OperatorAgentHistory"
source:
type: string
enum: [ledger]
ContextRetention:
type: object
required: [policy, expiresAt]
properties:
policy:
type: string
maxLength: 320
expiresAt:
type: string
format: date-time
ContextRevocation:
type: object
required: [state]
properties:
state:
type: string
enum: [active, revoked]
revokedAt:
type: string
format: date-time
reason:
type: string
maxLength: 500
ContextItem:
type: object
description: >
Bounded context-item-v1 memory. Content is accepted only for the assistant's internal
reader and is never returned by the metadata inventory route.
required: [version, contextId, source, principal, purpose, sensitivity, authorityRef, observedAt, freshness, retention, visibility, derivationLinks, revocation, content]
properties:
version:
type: string
enum: [context-item-v1]
contextId:
type: string
maxLength: 160
source:
type: string
maxLength: 320
principal:
type: string
maxLength: 320
purpose:
type: string
maxLength: 320
sensitivity:
type: string
enum: [low, moderate, high, restricted]
authorityRef:
type: string
maxLength: 320
observedAt:
type: string
format: date-time
freshness:
type: string
enum: [fresh, stale, unavailable]
retention:
$ref: "#/components/schemas/ContextRetention"
visibility:
type: string
enum: [private, operator, shared]
derivationLinks:
type: array
maxItems: 20
items:
type: string
maxLength: 160
revocation:
$ref: "#/components/schemas/ContextRevocation"
content:
type: string
maxLength: 4000
ContextInventoryItem:
type: object
required: [version, contextId, source, principal, purpose, sensitivity, authorityRef, observedAt, freshness, retention, visibility, derivationLinks, revocation, availability]
properties:
version:
type: string
enum: [context-item-v1]
contextId:
type: string
maxLength: 160
source:
type: string
maxLength: 320
principal:
type: string
maxLength: 320
purpose:
type: string
maxLength: 320
sensitivity:
type: string
enum: [low, moderate, high, restricted]
authorityRef:
type: string
maxLength: 320
observedAt:
type: string
format: date-time
freshness:
type: string
enum: [fresh, stale, unavailable]
retention:
$ref: "#/components/schemas/ContextRetention"
visibility:
type: string
enum: [private, operator, shared]
derivationLinks:
type: array
maxItems: 20
items:
type: string
maxLength: 160
revocation:
$ref: "#/components/schemas/ContextRevocation"
availability:
type: string
enum: [available, stale, unavailable, revoked, deleted]
deletionReceipt:
$ref: "#/components/schemas/ContextReceipt"
ContextReceipt:
type: object
required: [receiptId, contextId, operation, at, authorityRef, affectedDerivations]
properties:
receiptId:
type: string
maxLength: 160
contextId:
type: string
maxLength: 160
operation:
type: string
enum: [revoke, delete]
at:
type: string
format: date-time
authorityRef:
type: string
maxLength: 320
affectedDerivations:
type: integer
minimum: 0
ContextReceiptResult:
type: object
required: [ok, existing, receipt]
properties:
ok:
type: boolean
enum: [true]
existing:
type: boolean
receipt:
$ref: "#/components/schemas/ContextReceipt"
ContextList:
type: object
required: [items, stale, absent, source, asOf]
properties:
items:
type: array
items:
$ref: "#/components/schemas/ContextInventoryItem"
stale:
type: array
items:
type: string
absent:
type: boolean
source:
type: string
enum: [ledger]
asOf:
type: string
format: date-time
ContextRegistration:
type: object
required: [context]
properties:
context:
$ref: "#/components/schemas/ContextItem"
ContextActionRequest:
type: object
required: [contextId, authorityRef]
properties:
contextId:
type: string
maxLength: 160
authorityRef:
type: string
maxLength: 320
reason:
type: string
maxLength: 500
OperatorAgentProposalRegistration:
type: object
required: [proposal]
properties:
proposal:
$ref: "#/components/schemas/OperatorAgentProposal"
OperatorAgentDecisionRequest:
type: object
required: [proposalId, decision]
properties:
proposalId:
type: string
decision:
type: string
enum: [accepted, rejected, more-info]
note:
type: string
OperatorAgentOutcomeRequest:
type: object
required: [proposalId, outcome]
properties:
proposalId:
type: string
outcome:
type: object
required: [summary, observedAt]
properties:
summary:
type: string
helped:
type: boolean
observedAt:
type: string
format: date-time
evidence:
type: array
items:
type: string
OperatorAgentSettings:
type: object
required: [enabled, confidenceThreshold]
properties:
enabled:
type: boolean
confidenceThreshold:
type: number
minimum: 0.9
maximum: 0.99
OperatorAgentSettingsScope:
type: object
description: Settings are durable per repository; the console must select the repository explicitly.
required: [kind, repository]
properties:
kind:
type: string
enum: [repository]
repository:
type: string
minLength: 1
OperatorAgentSettingsRequest:
type: object
required: [settings]
properties:
settings:
$ref: "#/components/schemas/OperatorAgentSettings"
scope:
$ref: "#/components/schemas/OperatorAgentSettingsScope"
OperatorAgentSettingsResult:
type: object
required: [settings, source]
properties:
settings:
$ref: "#/components/schemas/OperatorAgentSettings"
source:
type: string
enum: [ledger, default]
scope:
$ref: "#/components/schemas/OperatorAgentSettingsScope"
updatedAt:
type: string
format: date-time
OperatorAgentExperimentScope:
type: object
required: [repo]
properties:
repo:
type: string
minLength: 1
taskType:
type: string
lane:
type: string
provider:
type: string
modelPolicy:
type: string
evidenceAnchors:
type: array
maxItems: 12
items:
type: string
OperatorAgentExperimentBaseline:
type: object
required: [metricName, value, unit, denominator, comparisonPopulation, windowStart, windowEnd, source, freshness]
properties:
metricName:
type: string
value:
type: number
unit:
type: string
denominator:
type: integer
minimum: 1
comparisonPopulation:
type: string
windowStart:
type: string
format: date-time
windowEnd:
type: string
format: date-time
source:
type: string
freshness:
type: string
enum: [verified, stale, unavailable]
OperatorAgentExperimentIntervention:
type: object
required: [summary, plan]
properties:
summary:
type: string
plan:
type: string
taskId:
type: string
prUrl:
type: string
format: uri
OperatorAgentExperimentRollback:
type: object
required: [plan, reason]
properties:
plan:
type: string
reason:
type: string
receipt:
type: string
OperatorAgentExperiment:
type: object
required: [version, experimentId, hypothesis, intervention, scope, baseline, rollback, createdAt, state]
properties:
version:
type: string
enum: [experiment-v1]
experimentId:
type: string
proposalId:
type: string
hypothesis:
type: string
intervention:
$ref: "#/components/schemas/OperatorAgentExperimentIntervention"
scope:
$ref: "#/components/schemas/OperatorAgentExperimentScope"
baseline:
$ref: "#/components/schemas/OperatorAgentExperimentBaseline"
rollback:
$ref: "#/components/schemas/OperatorAgentExperimentRollback"
createdAt:
type: string
format: date-time
state:
type: string
enum: [proposed]
OperatorAgentExperimentOutcome:
type: object
required: [state, summary, observedAt]
properties:
state:
type: string
enum: [observing, succeeded, neutral, regressed, unmeasurable]
summary:
type: string
observedAt:
type: string
format: date-time
source:
type: string
freshness:
type: string
enum: [verified, stale, unavailable]
attribution:
type: string
enum: [complete, missing, mixed]
denominator:
type: integer
minimum: 0
comparisonPopulation:
type: string
metricName:
type: string
value:
type: number
reason:
type: string
OperatorAgentExperimentHistory:
type: object
required: [version, experimentId, hypothesis, intervention, scope, baseline, rollback, createdAt, state, events]
properties:
version:
type: string
enum: [experiment-v1]
experimentId:
type: string
proposalId:
type: string
hypothesis:
type: string
intervention:
$ref: "#/components/schemas/OperatorAgentExperimentIntervention"
scope:
$ref: "#/components/schemas/OperatorAgentExperimentScope"
baseline:
$ref: "#/components/schemas/OperatorAgentExperimentBaseline"
rollback:
$ref: "#/components/schemas/OperatorAgentExperimentRollback"
createdAt:
type: string
format: date-time
state:
type: string
enum: [proposed, approved, observing, succeeded, neutral, regressed, rolled_back, expired, unmeasurable, rejected]
events:
type: array
items:
type: object
required: [kind, at]
properties:
kind:
type: string
enum: [decision, outcome, rollback]
at:
type: string
format: date-time
decision:
type: string
enum: [approved, rejected]
outcome:
$ref: "#/components/schemas/OperatorAgentExperimentOutcome"
rollback:
$ref: "#/components/schemas/OperatorAgentExperimentRollback"
note:
type: string
outcome:
$ref: "#/components/schemas/OperatorAgentExperimentOutcome"
OperatorAgentExperimentList:
type: object
required: [experiments, source]
properties:
experiments:
type: array
items:
$ref: "#/components/schemas/OperatorAgentExperimentHistory"
source:
type: string
enum: [ledger]
OperatorAgentExperimentRegistration:
type: object
required: [experiment]
properties:
experiment:
$ref: "#/components/schemas/OperatorAgentExperiment"
OperatorAgentExperimentDecisionRequest:
type: object
required: [experimentId, decision]
properties:
experimentId:
type: string
decision:
type: string
enum: [approved, rejected]
note:
type: string
OperatorAgentExperimentOutcomeRequest:
type: object
required: [experimentId, outcome]
properties:
experimentId:
type: string
outcome:
$ref: "#/components/schemas/OperatorAgentExperimentOutcome"
OperatorAgentExperimentRollbackRequest:
type: object
required: [experimentId, rollback]
properties:
experimentId:
type: string
rollback:
$ref: "#/components/schemas/OperatorAgentExperimentRollback"
# ── W1-T3856 -- experiment-promotion-v1: the guarded flow from a candidate policy to measured
# production exposure (deterministic replay, shadow observation, canary promotion, stop
# conditions, automatic rollback) that a later-approved experiment-v1 record may take.
PromotionScope:
type: object
required: [repo, policyScope]
properties:
repo:
type: string
minLength: 1
policyScope:
type: string
minLength: 1
description: The unit canary exposure is serialized against; at most one active promotion may hold a given (repo, policyScope) pair.
taskType:
type: string
lane:
type: string
PromotionGuardMetric:
type: object
required: [metricName, unit, direction, abortThreshold]
properties:
metricName:
type: string
unit:
type: string
direction:
type: string
enum: [max, min]
abortThreshold:
type: number
PromotionRollback:
type: object
required: [plan, reason]
properties:
plan:
type: string
reason:
type: string
receipt:
type: string
PromotionRecord:
type: object
required:
- version
- promotionId
- candidate
- baseline
- scope
- comparisonPopulation
- denominatorFloor
- observationWindow
- guardMetrics
- maxExposure
- owner
- expiresAt
- rollback
- createdAt
- state
properties:
version:
type: string
enum: [experiment-promotion-v1]
promotionId:
type: string
experimentId:
type: string
description: Links to the experiment-v1 record (W1-T3853) this promotion progresses.
candidate:
type: string
baseline:
type: string
scope:
$ref: "#/components/schemas/PromotionScope"
comparisonPopulation:
type: string
denominatorFloor:
type: integer
minimum: 5
observationWindow:
type: object
required: [start, end]
properties:
start:
type: string
format: date-time
end:
type: string
format: date-time
guardMetrics:
type: array
minItems: 1
maxItems: 12
items:
$ref: "#/components/schemas/PromotionGuardMetric"
maxExposure:
type: number
minimum: 0
maximum: 1
description: The bounded maximum fraction of traffic the canary may take.
owner:
type: string
expiresAt:
type: string
format: date-time
rollback:
$ref: "#/components/schemas/PromotionRollback"
createdAt:
type: string
format: date-time
state:
type: string
enum: [proposed]
PromotionReplayResult:
type: object
required: [caseId, matched]
description: >
The bounded per-case receipt submitted and persisted: whether the candidate and baseline
agreed. The raw candidate/baseline outputs replayPromotion() computes are not part of the
durable wire contract -- they may be arbitrarily large or unbounded -- so only the
comparison result is recorded.
properties:
caseId:
type: string
matched:
type: boolean
PromotionReplaySummary:
type: object
required: [version, corpusSize, matched, mismatched, deterministic, sideEffectFree, results]
properties:
version:
type: string
enum: [experiment-promotion-v1]
corpusSize:
type: integer
minimum: 1
matched:
type: integer
minimum: 0
mismatched:
type: integer
minimum: 0
deterministic:
type: boolean
sideEffectFree:
type: boolean
enum: [true]
results:
type: array
items:
$ref: "#/components/schemas/PromotionReplayResult"
PromotionGuardObservation:
type: object
required: [metricName, value, denominator, freshness, comparisonPopulation, observedAt]
properties:
metricName:
type: string
value:
type: number
denominator:
type: integer
minimum: 0
freshness:
type: string
enum: [verified, stale, unavailable]
comparisonPopulation:
type: string
observedAt:
type: string
format: date-time
PromotionGuardEvaluation:
type: object
required: [state, reasons, breachedMetrics]
properties:
state:
type: string
enum: [ready, unmeasurable, regressed]
reasons:
type: array
items:
type: string
breachedMetrics:
type: array
items:
type: string
AssistantTrustControlResult:
type: object
description: >
One control case (W1-T3882): a `positive` control proves the corpus is visible (a
known-good case the candidate must pass); a `negative` control proves a restraint
failure is detectable (a known-bad case the candidate must flag, never pass silently).
required: [caseId, metricName, controlType, expectedOutcome, observedOutcome]
properties:
caseId:
type: string
metricName:
type: string
enum:
- proactivity_precision
- dropped_thread_recovery
- clarification_burden
- intervention_rate
- unauthorized_side_effect_rate
- stale_context_use
- receipt_completeness
- rollback_success
- time_to_human_attention
controlType:
type: string
enum: [positive, negative]
expectedOutcome:
type: string
enum: [pass, flagged]
observedOutcome:
type: string
enum: [pass, flagged]
AssistantTrustEvidence:
type: object
description: >
Restraint and recovery evidence (W1-T3882) that gates a promotion above the base
guardrail evaluation: any unauthorized side effect, stale-context use, incomplete
receipt, or failed rollback blocks promotion outright.
required: [unauthorizedSideEffects, staleContextUses, receiptsComplete, rollbackAttempted, rollbackSucceeded]
properties:
unauthorizedSideEffects:
type: integer
minimum: 0
staleContextUses:
type: integer
minimum: 0
receiptsComplete:
type: boolean
rollbackAttempted:
type: boolean
rollbackSucceeded:
type: boolean
RawAssistantTrustContext:
type: object
description: >
Raw material an evaluation MIGHT be handed — never persisted or trained on. Only bounded
counts and a secret-scrubbed, length-capped note ever leave this boundary; see
`AssistantTrustEvaluation.evidence`.
properties:
prompts:
type: array
maxItems: 200
items:
type: string
transcripts:
type: array
maxItems: 200
items:
type: string
credentials:
type: array
maxItems: 200
items:
type: string
note:
type: string
RedactedAssistantTrustEvidence:
type: object
required: [promptCount, transcriptCount, credentialCount, note, redacted]
properties:
promptCount:
type: integer
minimum: 0
transcriptCount:
type: integer
minimum: 0
credentialCount:
type: integer
minimum: 0
note:
type: string
redacted:
type: boolean
enum: [true]
AssistantTrustEvaluation:
type: object
description: >
The evaluation layer above `PromotionGuardEvaluation` (W1-T3882): a candidate may reach
`ready` only when replay was deterministic and side-effect-free, the corpus carried
working positive and negative controls, and no unauthorized side effect, stale-context
use, incomplete receipt, or failed rollback was observed.
required: [state, reasons, evidence]
properties:
state:
type: string
enum: [ready, unmeasurable, blocked]
reasons:
type: array
items:
type: string
evidence:
$ref: "#/components/schemas/RedactedAssistantTrustEvidence"
OperatorAgentPromotionHistory:
type: object
required:
- version
- promotionId
- candidate
- baseline
- scope
- comparisonPopulation
- denominatorFloor
- observationWindow
- guardMetrics
- maxExposure
- owner
- expiresAt
- rollback
- createdAt
- state
- events
properties:
version:
type: string
enum: [experiment-promotion-v1]
promotionId:
type: string
experimentId:
type: string
candidate:
type: string
baseline:
type: string
scope:
$ref: "#/components/schemas/PromotionScope"
comparisonPopulation:
type: string
denominatorFloor:
type: integer
minimum: 5
observationWindow:
type: object
required: [start, end]
properties:
start:
type: string
format: date-time
end:
type: string
format: date-time
guardMetrics:
type: array
minItems: 1
maxItems: 12
items:
$ref: "#/components/schemas/PromotionGuardMetric"
maxExposure:
type: number
minimum: 0
maximum: 1
owner:
type: string
expiresAt:
type: string
format: date-time
rollback:
$ref: "#/components/schemas/PromotionRollback"
createdAt:
type: string
format: date-time
state:
type: string
enum:
- proposed
- replayed
- approved
- shadow
- canary
- observing
- promoted
- neutral
- regressed
- rolled_back
- expired
- unmeasurable
events:
type: array
items:
type: object
required: [kind, at, state]
properties:
kind:
type: string
enum: [replay, decision, advance, rollback]
at:
type: string
format: date-time
state:
type: string
replay:
$ref: "#/components/schemas/PromotionReplaySummary"
decision:
type: string
enum: [approved]
advance:
type: object
required: [target, guard]
properties:
target:
type: string
enum: [shadow, canary, observing, promoted]
guard:
$ref: "#/components/schemas/PromotionGuardEvaluation"
exposure:
type: number
assistantTrust:
$ref: "#/components/schemas/AssistantTrustEvaluation"
rollback:
$ref: "#/components/schemas/PromotionRollback"
note:
type: string
OperatorAgentPromotionList:
type: object
required: [promotions, source]
properties:
promotions:
type: array
items:
$ref: "#/components/schemas/OperatorAgentPromotionHistory"
source:
type: string
enum: [ledger]
OperatorAgentPromotionRegistration:
type: object
required: [promotion]
properties:
promotion:
$ref: "#/components/schemas/PromotionRecord"
OperatorAgentPromotionReplayRequest:
type: object
required: [promotionId, replay]
properties:
promotionId:
type: string
replay:
$ref: "#/components/schemas/PromotionReplaySummary"
OperatorAgentPromotionDecisionRequest:
type: object
required: [promotionId, decision]
properties:
promotionId:
type: string
decision:
type: string
enum: [approved]
note:
type: string
OperatorAgentPromotionAdvanceRequest:
type: object
required: [promotionId, target]
properties:
promotionId:
type: string
target:
type: string
enum: [shadow, canary, observing, promoted]
observations:
type: array
maxItems: 12
items:
$ref: "#/components/schemas/PromotionGuardObservation"
exposure:
type: number
assistantTrust:
type: object
description: >
Optional assistant-trust evidence (W1-T3882). Its absence preserves the pre-W1-T3882
base-guardrail-only advance behaviour exactly; when present, the response carries an
`assistantTrust` evaluation and a `blocked`/`unmeasurable` result can demote an
otherwise-ready advance to `regressed`/`unmeasurable`.
required: [controls, evidence]
properties:
controls:
type: array
maxItems: 64
items:
$ref: "#/components/schemas/AssistantTrustControlResult"
evidence:
$ref: "#/components/schemas/AssistantTrustEvidence"
rawContext:
$ref: "#/components/schemas/RawAssistantTrustContext"
OperatorAgentPromotionRollbackRequest:
type: object
required: [promotionId, rollback]
properties:
promotionId:
type: string
rollback:
$ref: "#/components/schemas/PromotionRollback"
OperatorAgentConsequenceAction:
type: object
required: [consequenceClass, target, requiredApprovers]
properties:
id:
type: string
consequenceClass:
type: string
enum: [reversible, disruptive, irreversible, financial]
target:
type: object
required: [identity, source]
properties:
identity:
type: string
source:
type: string
enum: [trusted, external]
ambiguous:
type: boolean
scope:
type: object
properties:
repo:
type: string
instance:
type: string
evidence:
type: array
items:
type: object
required: [label, observedAt, maxAgeSeconds]
properties:
label:
type: string
observedAt:
type: string
format: date-time
maxAgeSeconds:
type: number
financial:
type: object
properties:
amount:
type: number
currency:
type: string
perActionCeiling:
type: number
aggregateCeiling:
type: number
aggregateSpentBefore:
type: number
quoteExpiresAt:
type: string
format: date-time
coolingOffSeconds:
type: integer
coolingOffStartedAt:
type: string
format: date-time
irreversible:
type: object
properties:
affectedResource:
type: string
recoveryAvailable:
type: boolean
recoveryStatement:
type: string
rollbackUnavailableReason:
type: string
confirmationNonce:
type: string
confirmationExpiresAt:
type: string
format: date-time
requiredApprovers:
type: integer
approvals:
type: array
items:
type: object
required: [approverId, approvedAt, source]
properties:
approverId:
type: string
approvedAt:
type: string
format: date-time
source:
type: string
enum: [trusted, external]
capabilityGrant:
type: object
required: [grantId]
properties:
grantId:
type: string
OperatorAgentConsequencePreflightRequest:
type: object
required: [action]
properties:
action:
$ref: "#/components/schemas/OperatorAgentConsequenceAction"
FollowUpQuietHours:
type: object
required: [timezone, start, end]
properties:
timezone:
type: string
start:
type: string
pattern: '^([01]\\d|2[0-3]):[0-5]\\d$'
end:
type: string
pattern: '^([01]\\d|2[0-3]):[0-5]\\d$'
FollowUpNotificationPolicy:
type: object
required: [enabled]
properties:
enabled:
type: boolean
quietHours:
$ref: "#/components/schemas/FollowUpQuietHours"
FollowUpCandidate:
type: object
required: [version, candidateId, sourceEvent, workstream, reason, freshness, deduplicationKey, maxAttempts, owner, createdAt]
description: A bounded follow-up candidate. It must carry a deadline or dependency and exactly one next action or question.
properties:
version:
type: string
enum: [follow-up-policy-v1]
candidateId:
type: string
sourceEvent:
type: string
workstream:
type: string
reason:
type: string
freshness:
type: string
enum: [verified, stale, unavailable]
deadline:
type: string
format: date-time
dependency:
type: string
quietHours:
$ref: "#/components/schemas/FollowUpQuietHours"
deduplicationKey:
type: string
maxAttempts:
type: integer
minimum: 1
maximum: 20
owner:
type: string
nextAction:
type: string
nextQuestion:
type: string
createdAt:
type: string
format: date-time
FollowUpEvaluation:
type: object
required: [version, candidateId, deduplicationKey, state, reason, at, attempts]
properties:
version:
type: string
enum: [follow-up-policy-v1]
candidateId:
type: string
deduplicationKey:
type: string
state:
type: string
enum: [scheduled, eligible, snoozed, suppressed, asked, accepted, rejected, expired, blocked]
reason:
type: string
at:
type: string
format: date-time
attempts:
type: integer
nextAction:
type: string
nextQuestion:
type: string
FollowUpReceipt:
type: object
required: [delivered, systemActed, completed, permissionToAct, at]
properties:
delivered:
type: boolean
answered:
type: boolean
systemActed:
type: boolean
authority:
type: string
completed:
type: boolean
enum: [false]
permissionToAct:
type: boolean
at:
type: string
format: date-time
FollowUpHistory:
type: object
required: [version, candidateId, sourceEvent, workstream, reason, freshness, deduplicationKey, maxAttempts, owner, createdAt, state, attempts, events]
properties:
version:
type: string
enum: [follow-up-policy-v1]
candidateId:
type: string
sourceEvent:
type: string
workstream:
type: string
reason:
type: string
freshness:
type: string
enum: [verified, stale, unavailable]
deadline:
type: string
format: date-time
dependency:
type: string
quietHours:
$ref: "#/components/schemas/FollowUpQuietHours"
deduplicationKey:
type: string
maxAttempts:
type: integer
owner:
type: string
nextAction:
type: string
nextQuestion:
type: string
createdAt:
type: string
format: date-time
state:
type: string
enum: [scheduled, eligible, snoozed, suppressed, asked, accepted, rejected, expired, blocked]
attempts:
type: integer
events:
type: array
items:
type: object
receipt:
$ref: "#/components/schemas/FollowUpReceipt"
notificationPolicy:
$ref: "#/components/schemas/FollowUpNotificationPolicy"
snoozedUntil:
type: string
format: date-time
FollowUpList:
type: object
required: [followUps, source]
properties:
followUps:
type: array
items:
$ref: "#/components/schemas/FollowUpHistory"
source:
type: string
enum: [ledger]
FollowUpEvaluationRequest:
type: object
required: [candidate]
properties:
candidate:
$ref: "#/components/schemas/FollowUpCandidate"
sourceTerminal:
type: boolean
dependencyAvailable:
type: boolean
notificationPolicy:
$ref: "#/components/schemas/FollowUpNotificationPolicy"
FollowUpControlRequest:
type: object
required: [candidateId, control]
properties:
candidateId:
type: string
control:
type: string
enum: [snooze, reject, revoke, policy]
until:
type: string
format: date-time
notificationPolicy:
$ref: "#/components/schemas/FollowUpNotificationPolicy"
FollowUpDeliveryRequest:
type: object
required: [candidateId]
properties:
candidateId:
type: string
answered:
type: boolean
systemActed:
type: boolean
authority:
type: string
# ── W3-T8 (MASTER-PLAN §5B/§7 -- panel skill actions) -- the button set
# src/lib/panel-skills.ts's ONE route returns, resolved from
# `.remudero/skills/<name>.yaml` (lib/skill.ts's `Skill`, W1-T44) the SAME
# way `rmd skill list` does.
SkillEntry:
type: object
description: >
One `.remudero/skills/<name>.yaml` entry (lib/skill.ts's `Skill`) -- the panel button
IS this registry entry (MASTER-PLAN §5B). `name` is the file's basename, never a
`name:` field inside the body, so it can never drift from what `rmd skill list`
reports it under.
required:
- name
- tools
- permission_profile
- output_contract
- grounding_sources
- gate
- tier
properties:
name:
type: string
description: The skill's identity -- its filename minus `.yaml`.
tools:
type: array
items:
type: string
permission_profile:
type: string
output_contract:
type: string
grounding_sources:
type: array
items:
type: string
gate:
type: string
tier:
type: string
SkillsListResult:
type: object
description: >
GET /v1/skills's body -- one SkillEntry per `.remudero/skills/<name>.yaml`, resolved
fresh on every request (src/lib/panel-skills.ts).
required:
- skills
properties:
skills:
type: array
items:
$ref: "#/components/schemas/SkillEntry"
# ── W3-T8 round 2 (MASTER-PLAN §5B/§7) -- invoking a registry skill button. Today ONE
# skill+mode is wired: { skill: "plan", mode: "clarify" }, the panel's "Refine" button
# (src/lib/panel-skill-run.ts).
RunSkillRequest:
type: object
description: >
POST /v1/skills/run's body -- which registry skill to invoke, an optional mode, and
(for `plan`/`clarify`, i.e. Refine) the plan task id it targets.
required:
- skill
properties:
skill:
type: string
description: A name present in the `.remudero/skills/<name>.yaml` registry (validated against GET /v1/skills's own source).
mode:
type: string
description: 'The skill mode, e.g. "clarify" for Refine (§5B: plan is ONE skill, THREE MODES).'
taskId:
type: string
description: The plan/tasks.yaml task id this invocation targets. Required for `plan`/`clarify` (Refine).
RunSkillResult:
type: object
description: >
POST /v1/skills/run's body -- the invoked skill echoed back plus the grill it parked.
Today always carries a `grilling` `feedback` entry: Refine is the only wired skill/mode,
and Refine always grills (grounds via the real §5C linter, never proposes outright).
required:
- ok
- skill
- taskId
- feedback
properties:
ok:
type: boolean
skill:
type: string
mode:
type: string
taskId:
type: string
feedback:
$ref: "#/components/schemas/FeedbackEntry"
# ── W1-T3899 (external-effect-v1) — connector truth is separate from an action receipt. ──
ExternalEffectPostcondition:
type: object
required: [path, equals]
properties:
path:
type: string
equals:
description: JSON value expected at the connector path.
type: object
additionalProperties: true
description:
type: string
ExternalEffect:
type: object
required:
- version
- originatingActionId
- originatingReceiptId
- capabilityGrantId
- connector
- targetIdentity
- requestedOperation
- preconditionSnapshot
- expectedPostconditions
- observation
- idempotencyKey
- reconciliationState
- retryPath
- evidenceReference
- safeToComplete
properties:
version:
type: string
enum: [external-effect-v1]
originatingActionId:
type: string
originatingReceiptId:
type: string
capabilityGrantId:
type: string
connector:
type: string
targetIdentity:
type: string
requestedOperation:
type: string
preconditionSnapshot:
type: object
additionalProperties: true
expectedPostconditions:
type: array
items:
$ref: "#/components/schemas/ExternalEffectPostcondition"
observedState:
type: object
additionalProperties: true
observation:
type: object
required: [status, maxAgeMs]
properties:
status:
type: string
enum: [fresh, stale, unavailable]
observedAt:
type: string
format: date-time
ageMs:
type: number
maxAgeMs:
type: number
idempotencyKey:
type: string
reconciliationState:
type: string
enum: [applied, refused, pending, partially-applied, drifted, stale, unobservable]
partialSuccess:
type: object
required: [satisfied, unsatisfied]
properties:
satisfied:
type: array
items: { type: string }
unsatisfied:
type: array
items: { type: string }
retryPath:
type: object
required: [kind, allowed, reason]
properties:
kind:
type: string
enum: [none, retry, compensation]
allowed:
type: boolean
reason:
type: string
attemptNumber:
type: integer
evidenceReference:
type: string
description: Opaque digest of redacted connector evidence; raw provider output is never stored.
safeToComplete:
type: boolean
reason:
type: string
responses:
Unauthorized:
description: No or unrecognized bearer token.
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
Forbidden:
description: A recognized token whose granted scope does not cover this route.
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
NotFound:
description: No route registered for this method + path.
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
InvalidRequest:
description: The JSON body failed validation -- no side effect occurred (src/lib/panel-actions.ts's `jsonAction`).
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
Conflict:
description: The requested proposal transition conflicts with the durable ledger state.
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
# W3-T2 (MASTER-PLAN §7, WS-5a): the read-only live board's first real business routes --
# a task/ledger-state snapshot (REST) and its live-update companion (SSE). Both are built on
# the v0 mechanism above (src/lib/service.ts) and existing projection logic (src/lib/status.ts)
# -- see src/lib/board.ts. Every OTHER route the design (plan render, worker stream tails,
# DECISIONS feed, escalation inbox, question backlog, cost meter) describes is explicit
# follow-on work, not added here.
paths:
/v1/repos:
get:
summary: The read-only managed-repo portfolio for the console dashboard.
description: >
Reads and validates `.remudero/managed-repos.json` (src/lib/managed-repos.ts). The
endpoint reports repository identity only; OAuth connection, activation, health,
telemetry, settings, logs, and test-run state remain explicitly unknown until they have
durable daemon sources.
security:
- bearerRead: []
responses:
"200":
description: The validated managed-repo portfolio, including an honest empty result.
content:
application/json:
schema:
$ref: "#/components/schemas/RepoDashboardResult"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
/v1/status:
get:
summary: The current board snapshot -- one StatusProjection per plan task.
description: >
Read-only live board (MASTER-PLAN §7, WS-5a -> W3-T2): the task-DAG live-state
projection, computed via src/lib/status.ts's `projectPlan` over the current plan +
ledger + GitHub (src/lib/board.ts's `computeBoardSnapshot`).
security:
- bearerRead: []
responses:
"200":
description: The current board snapshot.
content:
application/json:
schema:
$ref: "#/components/schemas/StatusSnapshot"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
# ── W3-T5 (MASTER-PLAN §7, human-in-the-loop panel actions) -- write routes over the SAME
# v0 mechanism (src/lib/service.ts), all write-scoped, all built by src/lib/panel-actions.ts.
# Depends on W3-T2's board existing (the panel acts on what the board shows). Every write
# is ledgered with the caller's bearer as `origin`, never the raw token (see
# src/lib/panel-actions.ts's header). Real `rmd serve` CLI wiring is later work, same split
# W3-T2's routes below already document.
/v1/control/pause:
post:
summary: Pause the fleet (drain-and-hold) -- no new spawns; an in-flight task still finishes.
description: >
MASTER-PLAN §4A/§4B: writes the PAUSE flag (src/lib/fleet-control.ts's `requestPause`),
checked at the top of every drain tick. Ledgers `panel.pause_requested`.
security:
- bearerWrite: []
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/PauseRequest"
responses:
"200":
description: PAUSE is now set.
content:
application/json:
schema:
$ref: "#/components/schemas/PauseResult"
"400":
$ref: "#/components/responses/InvalidRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
/v1/control/resume:
post:
summary: Resume the fleet -- clears BOTH STOP and PAUSE.
description: >
MASTER-PLAN §4A: the one command that always means go (src/lib/fleet-control.ts's
`resumeFleet`). Ledgers `panel.resume_requested`. No request body.
security:
- bearerWrite: []
responses:
"200":
description: STOP and PAUSE are both cleared (whichever were set).
content:
application/json:
schema:
$ref: "#/components/schemas/ResumeResult"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
/v1/control/stop:
post:
summary: STOP the fleet -- the hard kill, checked first, every tick.
description: >
MASTER-PLAN §4A/§4B: writes the STOP flag (src/lib/fleet-control.ts's `requestStop`),
checked before PAUSE and before picking up any new task. "STOP from the panel halts the
fleet within one tick" (W3-T5 acceptance) -- the SAME predicate drain.ts's loop already
checks first. Ledgers `panel.stop_requested`.
security:
- bearerWrite: []
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/StopRequest"
responses:
"200":
description: STOP is now set.
content:
application/json:
schema:
$ref: "#/components/schemas/StopResult"
"400":
$ref: "#/components/responses/InvalidRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
/v1/questions/answer:
post:
summary: Answer a QUESTION-contract entry (MASTER-PLAN §2), attributed to the panel's bearer.
description: >
"An escalation answered FROM the panel appears in the ledger with the panel's bearer
token as origin; the answer flows to the Architect" (W3-T5 acceptance) -- proof is the
resulting `panel.question_answered` ledger line (src/lib/panel-actions.ts).
security:
- bearerWrite: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/AnswerQuestionRequest"
responses:
"200":
description: The answer was recorded.
content:
application/json:
schema:
$ref: "#/components/schemas/AnswerQuestionResult"
"400":
$ref: "#/components/responses/InvalidRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
/v1/manual/approve:
post:
summary: Approve (check off) a MANUAL-queue item -- closes its GitHub issue.
description: >
MASTER-PLAN §4: "the MANUAL queue doubles as the human's to-do list -- rendered in the
control panel with check-off (= closing the issue)." Closes the named
`escalation-manual`-labeled issue (src/lib/escalate.ts) FIRST, then ledgers
`panel.manual_approved` -- a close that fails never produces a false-approved ledger line.
security:
- bearerWrite: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ApproveManualRequest"
responses:
"200":
description: The issue was closed and the approval recorded.
content:
application/json:
schema:
$ref: "#/components/schemas/ApproveManualResult"
"400":
$ref: "#/components/responses/InvalidRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
/v1/escalation/mark-handled:
post:
summary: Mark an ESCALATION row handled -- closes its GitHub issue.
description: >
W1-T182: the NEEDS ME affordance an ESCALATION row (any class) actually supports --
distinct from /v1/manual/approve's MANUAL-queue check-off, since "approve" has no
defined verb for an escalation. Closes the named `needs-human`-labeled issue
(src/lib/escalate.ts) FIRST, then ledgers `panel.escalation_marked_handled` -- a close
that fails never produces a false-handled ledger line. Closing the issue does not, by
itself, resolve the underlying block (the issue body says so); the row clears on the
NEXT poll once GET /v1/status's live join (src/lib/status.ts) reads it CLOSED.
security:
- bearerWrite: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/MarkEscalationHandledRequest"
responses:
"200":
description: The issue was closed and the action recorded.
content:
application/json:
schema:
$ref: "#/components/schemas/MarkEscalationHandledResult"
"400":
$ref: "#/components/responses/InvalidRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
# ── W3-T6 (MASTER-PLAN §7B) -- the plan->task->PR graph + interactive plan adjustment, over
# the SAME v0 mechanism (src/lib/service.ts), built by src/lib/panel-graph.ts. Depends on
# W3-T2's board (the panel this extends) and W1-T43's trace chain (src/lib/trace.ts). Every
# write is ledgered with the caller's bearer as `origin`, via the SAME `appendPanelLedger`
# primitive W3-T5's routes use. Real `rmd serve` CLI wiring is later work, same split every
# route on this surface already documents.
/v1/feedback:
get:
summary: The feedback inbox -- every captured entry, oldest first.
description: >
`plan/feedback/` (MASTER-PLAN §7B, W1-T40), listed via src/lib/feedback.ts's
`listFeedback`. Optional `?status=` filters to one lifecycle status.
security:
- bearerRead: []
responses:
"200":
description: The inbox.
content:
application/json:
schema:
$ref: "#/components/schemas/FeedbackInboxResult"
"400":
$ref: "#/components/responses/InvalidRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
post:
summary: Submit feedback from the panel -- ALWAYS captured with origin=ui.
description: >
"feedback submitted from the panel appears in the inbox with origin=ui and produces a
proposal PR" (W3-T6 acceptance) -- captures via src/lib/feedback.ts's `captureFeedback`,
ledgers `panel.feedback_submitted`. `replyTo` doubles this route as "answer a grill" v1
(src/lib/panel-graph.ts's header).
security:
- bearerWrite: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/SubmitFeedbackRequest"
responses:
"200":
description: The captured entry.
content:
application/json:
schema:
$ref: "#/components/schemas/SubmitFeedbackResult"
"400":
$ref: "#/components/responses/InvalidRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
/v1/trace:
get:
summary: The plan->task->PR provenance graph for one task or feedback id.
description: >
"the panel renders the plan→task→PR graph" (W3-T6 acceptance) -- the SAME two-entry-
point chain `rmd trace <id>` renders (W1-T43, src/lib/trace.ts), addressed by
`?id=<task-id-or-feedback-id>` (v0 routing has no path params, src/lib/service.ts).
security:
- bearerRead: []
responses:
"200":
description: The provenance chain.
content:
application/json:
schema:
$ref: "#/components/schemas/TraceResult"
"400":
$ref: "#/components/responses/InvalidRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
/v1/operator-activity:
get:
summary: Read the bounded operator activity, workstream, and artifact projection.
description: >
Read-only operator-activity-v1 composition over the existing ledger, plan projection,
dispatcher frontier, and authoritative trace links. The response never contains raw
prompts, transcripts, credentials, account identifiers, or browser-owned measurements.
Unavailable and unknown source states remain explicit rather than becoming empty healthy data.
security:
- bearerRead: []
responses:
"200":
description: Operator activity projection, including explicit non-verified states.
content:
application/json:
schema:
$ref: "#/components/schemas/OperatorActivityResult"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
/v1/feedback/decision:
post:
summary: Accept or reject a `proposed` feedback entry.
description: >
"accepting a proposal from the panel is ledgered with the panel bearer as origin"
(W3-T6 acceptance) -- src/lib/feedback.ts's `setFeedbackStatus`, ledgers
`panel.proposal_accepted`/`panel.proposal_rejected`. Only a `proposed` entry may be
decided (400 otherwise).
security:
- bearerWrite: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ProposalDecisionRequest"
responses:
"200":
description: The decision was recorded.
content:
application/json:
schema:
$ref: "#/components/schemas/ProposalDecisionResult"
"400":
$ref: "#/components/responses/InvalidRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
/v1/operator-agent/context:
get:
summary: Read the metadata-only personal context inventory.
description: >
Reads context-item-v1 envelopes from the ledger union, including rotated records. The
response excludes raw private content and reports stale or unavailable derived context.
security:
- bearerRead: []
parameters:
- name: purpose
in: query
required: false
schema:
type: string
- name: authorityRef
in: query
required: false
schema:
type: string
responses:
"200":
description: Metadata-only context inventory.
content:
application/json:
schema:
$ref: "#/components/schemas/ContextList"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
post:
summary: Register one bounded context-item-v1 envelope.
description: Records provenance, purpose, authority, retention, visibility, and derivation links before use.
security:
- bearerWrite: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ContextRegistration"
responses:
"201":
description: Context item registered; the response is metadata-only.
"200":
description: Identical context item already registered.
"400":
$ref: "#/components/responses/InvalidRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"409":
$ref: "#/components/responses/Conflict"
/v1/operator-agent/context/revoke:
post:
summary: Revoke a context item and its derived dependants.
security:
- bearerWrite: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ContextActionRequest"
responses:
"200":
description: Bounded revocation receipt.
content:
application/json:
schema:
$ref: "#/components/schemas/ContextReceiptResult"
"400":
$ref: "#/components/responses/InvalidRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
"409":
$ref: "#/components/responses/Conflict"
/v1/operator-agent/context/delete:
post:
summary: Delete a context item and retain its bounded deletion receipt.
security:
- bearerWrite: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ContextActionRequest"
responses:
"200":
description: Bounded deletion receipt; raw private content is not returned.
content:
application/json:
schema:
$ref: "#/components/schemas/ContextReceiptResult"
"400":
$ref: "#/components/responses/InvalidRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
/v1/operator-agent/proposals:
get:
summary: Read durable operator-agent proposals and decision history.
description: >
Returns only proposal, decision, and observed-outcome records reconstructed from the
append-only ledger, including supported rotated ledger files. No raw analytics payload or
bearer token is returned.
security:
- bearerRead: []
responses:
"200":
description: Durable operator-agent history.
content:
application/json:
schema:
$ref: "#/components/schemas/OperatorAgentProposalList"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
post:
summary: Register an evidence-backed operator-agent proposal.
description: >
Idempotently records the proposal identity and evidence in the ledger. A repeated
proposalId with different repository or text is rejected as a conflict.
security:
- bearerWrite: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/OperatorAgentProposalRegistration"
responses:
"201":
description: Proposal registered.
"200":
description: Identical proposal already registered.
"400":
$ref: "#/components/responses/InvalidRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"409":
$ref: "#/components/responses/Conflict"
/v1/operator-agent/proposals/decision:
post:
summary: Record an operator decision on an operator-agent proposal.
description: >
Records accepted, rejected, or more-info. More-info leaves the proposal pending and is
retained as a learning signal. Accepted proposals have no outcome until a later outcome
request supplies observed evidence.
security:
- bearerWrite: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/OperatorAgentDecisionRequest"
responses:
"200":
description: Decision recorded.
"400":
$ref: "#/components/responses/InvalidRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
"409":
$ref: "#/components/responses/Conflict"
/v1/operator-agent/proposals/outcome:
post:
summary: Record a later observed outcome for an accepted proposal.
security:
- bearerWrite: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/OperatorAgentOutcomeRequest"
responses:
"200":
description: Outcome recorded.
"400":
$ref: "#/components/responses/InvalidRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
"409":
$ref: "#/components/responses/Conflict"
/v1/operator-agent/settings:
get:
summary: Read durable operator-agent settings.
description: Returns the last valid ledger settings row, or explicit conservative defaults when none exists.
security:
- bearerRead: []
parameters:
- name: repository
in: query
required: false
description: Explicit repository scope. When present, only rows for this repository are read.
schema:
type: string
minLength: 1
responses:
"200":
description: Operator-agent settings.
content:
application/json:
schema:
$ref: "#/components/schemas/OperatorAgentSettingsResult"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
post:
summary: Persist bounded operator-agent settings.
security:
- bearerWrite: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/OperatorAgentSettingsRequest"
responses:
"200":
description: Settings persisted.
"400":
$ref: "#/components/responses/InvalidRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
/v1/operator-agent/experiments:
get:
summary: Read durable self-improvement experiment-v1 history.
description: >
Returns the append-only experiment record, approval decision, measured outcome, and
rollback events across the live ledger and supported rotated ledger files. Approval is
never presented as measured success; weak attribution is returned as unmeasurable.
security:
- bearerRead: []
responses:
"200":
description: Durable experiment history.
content:
application/json:
schema:
$ref: "#/components/schemas/OperatorAgentExperimentList"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
post:
summary: Register a bounded, falsifiable self-improvement experiment.
description: >
Records the hypothesis, intervention, baseline, scope, observation window, and rollback
path before any operator decision. The durable version is experiment-v1 and excludes raw
credentials or unbounded model prose.
security:
- bearerWrite: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/OperatorAgentExperimentRegistration"
responses:
"201":
description: Experiment registered.
"200":
description: Identical experiment already registered.
"400":
$ref: "#/components/responses/InvalidRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"409":
$ref: "#/components/responses/Conflict"
/v1/operator-agent/experiments/decision:
post:
summary: Approve or reject a self-improvement experiment.
description: Approval records intent only; no outcome or success rate is created until a later observation.
security:
- bearerWrite: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/OperatorAgentExperimentDecisionRequest"
responses:
"200":
description: Experiment decision recorded.
"400":
$ref: "#/components/responses/InvalidRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
"409":
$ref: "#/components/responses/Conflict"
/v1/operator-agent/experiments/outcome:
post:
summary: Record a later self-improvement experiment observation.
description: >
A claimed success, neutral result, or regression is normalised to unmeasurable when its
denominator, attribution, comparison population, metric, or freshness is insufficient.
security:
- bearerWrite: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/OperatorAgentExperimentOutcomeRequest"
responses:
"200":
description: Experiment observation recorded.
"400":
$ref: "#/components/responses/InvalidRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
"409":
$ref: "#/components/responses/Conflict"
/v1/operator-agent/experiments/rollback:
post:
summary: Record a rollback for a regressed self-improvement experiment.
description: Rollback appends a linked event and leaves the proposal, approval, and regression history intact.
security:
- bearerWrite: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/OperatorAgentExperimentRollbackRequest"
responses:
"200":
description: Rollback recorded.
"400":
$ref: "#/components/responses/InvalidRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
"409":
$ref: "#/components/responses/Conflict"
/v1/operator-agent/promotions:
get:
summary: Read durable experiment-promotion-v1 replay/shadow/canary history.
description: >
Returns the append-only promotion record and its replay, decision, advance, and rollback
events across the live ledger and supported rotated ledger files. The current state is
the last event's resulting state, adjusted for expiry.
security:
- bearerRead: []
responses:
"200":
description: Durable promotion history.
content:
application/json:
schema:
$ref: "#/components/schemas/OperatorAgentPromotionList"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
post:
summary: Register a bounded promotion; canary exposure is serialized per policy scope.
description: >
Records the candidate, baseline, comparable population, denominator floor, observation
window, guard metrics, maximum exposure, owner, expiry, and rollback path before any
replay or approval. At most one active (non-terminal) promotion may hold a given
(repo, policyScope) pair at a time.
security:
- bearerWrite: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/OperatorAgentPromotionRegistration"
responses:
"201":
description: Promotion registered.
"200":
description: Identical promotion already registered.
"400":
$ref: "#/components/responses/InvalidRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"409":
$ref: "#/components/responses/Conflict"
/v1/operator-agent/promotions/replay:
post:
summary: Attach a deterministic, side-effect-free replay summary to a proposed promotion.
description: >
The submitted summary is a bounded receipt of a replay pass already run over a historical
corpus by the caller; this route validates and persists it rather than re-executing
anything, moving the promotion from proposed to replayed.
security:
- bearerWrite: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/OperatorAgentPromotionReplayRequest"
responses:
"200":
description: Replay summary recorded.
"400":
$ref: "#/components/responses/InvalidRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
"409":
$ref: "#/components/responses/Conflict"
/v1/operator-agent/promotions/decision:
post:
summary: Approve a replayed promotion candidate.
description: Approval records intent only; only later guard-evaluated evidence can advance the promotion further.
security:
- bearerWrite: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/OperatorAgentPromotionDecisionRequest"
responses:
"200":
description: Promotion decision recorded.
"400":
$ref: "#/components/responses/InvalidRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
"409":
$ref: "#/components/responses/Conflict"
/v1/operator-agent/promotions/advance:
post:
summary: Advance a promotion one guarded step (shadow, canary, observing, or promoted).
description: >
`shadow` entry follows approval alone. Every later step evaluates freshly submitted guard
observations against the promotion's declared metrics, denominator floor, comparison
population, and window: a breach becomes `regressed`, insufficient evidence becomes
`unmeasurable`, and only a fully-ready evaluation reaches the requested target. Requesting
exposure above the promotion's declared maximum is refused.
security:
- bearerWrite: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/OperatorAgentPromotionAdvanceRequest"
responses:
"200":
description: Promotion advanced (or explicitly aborted to regressed/unmeasurable).
"400":
$ref: "#/components/responses/InvalidRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
"409":
$ref: "#/components/responses/Conflict"
/v1/operator-agent/promotions/rollback:
post:
summary: Record a rollback for an approved, shadow, canary, observing, regressed, or unmeasurable promotion.
description: Rollback appends a linked event and leaves the registration, replay, approval, and advance history intact.
security:
- bearerWrite: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/OperatorAgentPromotionRollbackRequest"
responses:
"200":
description: Rollback recorded.
"400":
$ref: "#/components/responses/InvalidRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
"409":
$ref: "#/components/responses/Conflict"
/v1/operator-agent/consequence/preflight:
post:
summary: Classify and evaluate a consequence-policy-v1 action before any capability it names may be used.
description: >
Every operator-agent action path that would go on to use a capability grant (see
`capability-grant-v1`) evaluates its consequence policy here FIRST. Refuses an ambiguous or
externally-sourced target, stale evidence, an expired quote/confirmation, an exceeded
per-action/aggregate financial ceiling, an active cooling-off window, or a shortfall of
distinct trusted approvers against the action's configured requirement. A `ready` response
is a preflight verdict only — it never itself uses a capability or reports success.
security:
- bearerWrite: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/OperatorAgentConsequencePreflightRequest"
responses:
"200":
description: The action is ready — every consequence-policy check passed.
"400":
$ref: "#/components/responses/InvalidRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"409":
description: The action was refused by consequence policy; the response names the machine-readable code and reason.
/v1/operator-agent/follow-ups:
get:
summary: Read durable proactive follow-up candidates and receipts.
description: Returns ledgered follow-up state after freshness, quiet-hours, deduplication, and human-stop evaluation.
security:
- bearerRead: []
responses:
"200":
description: Durable follow-up history.
content:
application/json:
schema:
$ref: "#/components/schemas/FollowUpList"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
/v1/operator-agent/follow-ups/evaluate:
post:
summary: Evaluate and ledger a proactive follow-up candidate.
description: Refuses stale, unavailable, expired, terminal, blocked, quiet-hour, disabled, or duplicate candidates.
security:
- bearerWrite: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/FollowUpEvaluationRequest"
responses:
"200":
description: Follow-up policy decision recorded.
content:
application/json:
schema:
$ref: "#/components/schemas/FollowUpEvaluation"
"400":
$ref: "#/components/responses/InvalidRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
/v1/operator-agent/follow-ups/control:
post:
summary: Apply a durable human follow-up stop or notification-policy change.
description: Snooze, reject, revoke, and notification-policy changes are append-only and survive restart.
security:
- bearerWrite: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/FollowUpControlRequest"
responses:
"200":
description: Follow-up control recorded.
"400":
$ref: "#/components/responses/InvalidRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
"409":
$ref: "#/components/responses/Conflict"
/v1/operator-agent/follow-ups/delivery:
post:
summary: Record a delivered follow-up receipt.
description: Delivery records an asked receipt and never implies completion or permission to act.
security:
- bearerWrite: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/FollowUpDeliveryRequest"
responses:
"200":
description: Delivery receipt recorded.
content:
application/json:
schema:
type: object
required: [ok, state, receipt]
properties:
ok:
type: boolean
state:
type: string
enum: [asked]
receipt:
$ref: "#/components/schemas/FollowUpReceipt"
"400":
$ref: "#/components/responses/InvalidRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
"409":
$ref: "#/components/responses/Conflict"
/v1/skills:
get:
summary: The panel's skill action-button set, resolved from the registry.
description: >
"each v1 skill appears as a panel button resolved from the registry (not hard-coded
UI)... adding a skill yaml adds a button with no UI code change" (W3-T8 acceptance) --
the SAME `.remudero/skills/<name>.yaml` registry `rmd skill list` reads (W1-T44,
src/lib/skill.ts), via src/lib/panel-skills.ts. Reloaded fresh on every request, so a
skill yaml added after the daemon boots appears on the very next request.
security:
- bearerRead: []
responses:
"200":
description: The button set.
content:
application/json:
schema:
$ref: "#/components/schemas/SkillsListResult"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
/v1/skills/run:
post:
summary: Invoke a registry skill button -- today, Refine (plan/clarify) only.
description: >
"invoking Refine from the panel runs the plan --mode=clarify skill and shows the grill
inline... the answer flows back" (W3-T8 acceptance) -- src/lib/panel-skill-run.ts.
`{ skill: "plan", mode: "clarify" }` (Refine) grounds via the REAL, ALREADY-MERGED §5C
linter (src/lib/task-linter.ts) and parks the result as a `grilling`
plan/feedback/<id>.yaml entry (src/lib/feedback.ts) -- the SAME entry GET /v1/feedback
already renders inline and POST /v1/feedback's `replyTo` already answers (W3-T6). Any
other skill/mode is a 400 naming exactly what is not yet wired, never a silent no-op.
security:
- bearerWrite: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/RunSkillRequest"
responses:
"200":
description: The invoked skill, echoed back with the grill it parked.
content:
application/json:
schema:
$ref: "#/components/schemas/RunSkillResult"
"400":
$ref: "#/components/responses/InvalidRequest"
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
/v1/status/stream:
get:
summary: SSE stream of task state flips -- one `status` event per changed StatusProjection.
description: >
Live companion to GET /v1/status (MASTER-PLAN §7, WS-5a -> W3-T2): the daemon tails
state/ledger.ndjson (src/lib/board.ts's `buildStatusStream`) and, for every new line
naming a task whose derived StatusProjection actually changed, emits an SSE `status`
event carrying that StatusProjection (src/lib/service.ts's SSE framing). Requires a
read-scoped bearer token, same as GET /v1/status.
security:
- bearerRead: []
responses:
"200":
description: >
An open text/event-stream connection. Event framing is outside this OpenAPI
subset's response-body modeling -- each `status` event's `data` payload is a
components.schemas.StatusProjection.
"401":
$ref: "#/components/responses/Unauthorized"
"403":
$ref: "#/components/responses/Forbidden"
"404":
$ref: "#/components/responses/NotFound"
Back to home