{"components": {"schemas": {"AgentCalls": {"description": "One value of a call's agent field in the overview's window, its calls, and what kind of caller it is.", "properties": {"agent": {"type": "string"}, "calls": {"type": "integer"}, "calls_in_sandboxes": {"description": "Of calls, those in visitors' sandboxes. A sandbox is seeded with synthetic calls from all three coding agents, so a coding agent whose calls in the window are all in sandboxes was governed nowhere else in it.", "type": "integer"}, "kind": {"description": "coding_agent for claude-code, codex, antigravity and muse; page for a visitor pressing a button on a page here; ci for a pipeline or a pre-commit check (agent ci or pre-commit); unknown for a row written before the agent field existed, and for any value not listed here.", "enum": ["coding_agent", "page", "ci", "unknown"], "type": "string"}}, "type": "object"}, "DecisionRow": {"description": "One ledger row, as a public page may show it: the project is an Acme-style alias or 'unlabelled' and the developer an 8-hex hash. Never the call's arguments or file content.", "properties": {"action_type": {"type": "string"}, "agent": {"type": "string"}, "category": {"type": "string"}, "category_label": {"type": "string"}, "cost_usd": {"type": "number"}, "developer_id": {"description": "An 8-hex hash, never the value sent.", "type": "string"}, "dry_run": {"type": "boolean"}, "hook_mode": {"description": "observe, managed, enforce or unknown, as the hook sent it.", "type": "string"}, "observed_reason": {"type": "string"}, "observed_rules": {"items": {"type": "string"}, "type": "array"}, "observed_target": {"type": "string"}, "origin": {"type": "string"}, "project_name": {"type": "string"}, "reason": {"type": "string"}, "review": {"enum": ["correct", "false_alarm", null], "type": ["string", "null"]}, "review_note": {"type": ["string", "null"]}, "reviewed_at": {"type": ["string", "null"]}, "rule": {"description": "The invariant that failed, or NONE. Kept as it always was.", "type": "string"}, "rule_key": {"description": "What decided the call, and what readiness, reviews and observe_rules group by: the id of the layering rule that decided, or LOOP, PROTECTED_PATH (which also covers destructive commands), UNREADABLE_WRITE, CREDENTIAL, BUDGET, HALTED_SESSION, or NONE when nothing flagged the call. Set on observations as well as refusals. Rows written before it existed are given one on the way out.", "type": "string"}, "session_id": {"type": "string"}, "stage": {"description": "The stage the call was judged under. A dry run is observe, a page call is enforce.", "enum": ["observe", "enforce"], "type": "string"}, "status": {"type": "string"}, "suggested_fix_kind": {"description": "The kind of suggested fix the call's response carried. Present only on a row whose call was sent one; the fix itself is never stored.", "enum": ["layering", "credential", "protected_path", "unreadable_write", "loop", "budget", "halted_session", "destructive_command"], "type": "string"}, "suggested_fix_validated": {"description": "Whether every proposed write of that fix passed the same gates. Present only beside suggested_fix_kind.", "type": "boolean"}, "target": {"type": "string"}, "timestamp": {"format": "date-time", "type": "string"}, "tool_name": {"type": "string"}, "verdict_id": {"type": "string"}}, "type": "object"}, "LayeringRule": {"properties": {"allow_imports": {"description": "Exceptions, a list of at most 40. When an allow and a forbid both match, the more specific pattern decides.", "items": {"type": "string"}, "type": "array"}, "description": {"description": "The sentence a developer reads when stopped.", "type": "string"}, "forbid_imports": {"description": "A list of at most 40 patterns. A dotted or slashed module prefix forbids it and everything beneath it, never a sibling that only shares its first letters. Globs are allowed.", "items": {"type": "string"}, "type": "array"}, "id": {"description": "What a refusal quotes. Must be unique within the set.", "type": "string"}, "mode": {"default": "enforce", "description": "enforce refuses the write. observe lets it run and records that the rule would have refused it. A missing or null mode means enforce. Any other value makes the whole save fail with 400, naming the rule.", "enum": ["enforce", "observe"], "type": "string"}, "when_path_matches": {"description": "A list of globs over the file path, at most 40, each at most 300 characters. ** crosses folders, * and ? stay inside one. Case is ignored.", "items": {"type": "string"}, "type": "array"}}, "required": ["id", "when_path_matches", "forbid_imports"], "type": "object"}, "OverviewPart": {"description": "The overview's totals over one side of sandbox_split, computed as the totals are.", "properties": {"approved": {"type": "integer"}, "calls": {"type": "integer"}, "false_alarms": {"type": "integer"}, "needs_review": {"type": "integer"}, "projects": {"type": "integer"}, "refused": {"type": "integer"}, "would_refuse": {"type": "integer"}}, "type": "object"}, "OverviewSource": {"description": "One source's share of the overview's window.", "properties": {"calls": {"description": "Calls recorded in the window under this source's projects.", "type": "integer"}, "projects": {"description": "Of by_project, the rows with this source.", "type": "integer"}}, "type": "object"}, "ProjectConfig": {"description": "A project's stage configuration, stored at PK=CONFIG#project#<name>.", "properties": {"created_at": {"type": ["string", "null"]}, "demoted_at": {"type": ["string", "null"]}, "history": {"description": "The last 20 promotions and demotions.", "items": {"properties": {"action": {"enum": ["promote", "demote"], "type": "string"}, "at": {"type": "string"}, "by": {"description": "8 hex of a hash of the credential used, or anonymous.", "type": "string"}, "enforce": {"items": {"type": "string"}, "type": "array"}, "observe": {"items": {"type": "string"}, "type": "array"}}, "type": "object"}, "type": "array"}, "observe_rules": {"description": "Rule keys whose refusals are recorded as observations while the project enforces.", "items": {"type": "string"}, "type": "array"}, "promoted_at": {"type": ["string", "null"]}, "sandbox": {"description": "True for a sandbox, which is gone 24 hours after it was made.", "type": "boolean"}, "stage": {"enum": ["observe", "enforce"], "type": "string"}, "updated_at": {"type": ["string", "null"]}}, "type": "object"}, "SelfCorrection": {"description": "How often an agent that was refused went on to do the same thing acceptably, read from the ledger rather than the rollups because it is a question about the order of calls in a session. A refusal of a hook or ci call self-corrected when one of the next ten calls of the same session aimed at the same file and was approved or only observed. Commands are left out on both sides: the ledger keeps only the program a command ran, never the rest of its line, so it cannot say which file a refused command was aimed at; a refused command is not considered, and a later command is never taken as a correction, though it counts as one of the calls in between. Calls recorded at the same instant are not ordered against each other: one that shares the refusal's instant is never its correction, and a tie can only lengthen a distance. Page calls are never considered, and nothing is refused in the observe stage, so a project there considers no refusal. Only a refusal whose session made a later call had a chance to be corrected: a live probe or a smoke test sends one call and stops, and is no agent failing to correct itself. So refusals_considered is split into refusals_with_later_call and refusals_without_later_call, and rate is taken over the first; until 2026-09-25 it was taken over refusals_considered. The read is bounded: complete is false when it stopped before the window's oldest day, and the figure then covers the newest rows_read rows only.", "properties": {"complete": {"description": "False when the bounded read stopped before it covered the whole window, or when the ledger could not be read: a store that cannot page the ledger, or a query that failed. A failed read is never reported as an empty window.", "type": "boolean"}, "median_calls_to_correct": {"description": "The median number of calls from a refusal to the call that corrected it, 1 being the very next call; null when nothing was corrected. Calls that share an instant with the correction are all counted as having come before it.", "type": ["number", "null"]}, "rate": {"description": "self_corrected / refusals_with_later_call, rounded to four places; null when no refusal had a later call, because a window whose refusals all ended their sessions measured nothing, and none of none is not a rate. Changed 2026-09-25: the denominator used to be refusals_considered, which scored every single-call probe as an agent that failed to correct itself.", "type": ["number", "null"]}, "refusals_considered": {"description": "Refusals of hook and ci calls in the window aimed at a file the ledger recorded. A refusal with no recorded target, or of a command, whose recorded target is only the program it ran, cannot be matched with anything and is left out. Always refusals_with_later_call + refusals_without_later_call.", "type": "integer"}, "refusals_with_later_call": {"description": "Of refusals_considered, those whose session recorded at least one more hook or ci call at a later instant, of any kind: the refusals that had a chance to be corrected, and the rate's denominator. A call that shares the refusal's own instant is not known to have come after it, so it gives no chance. A refusal with a later call that was not corrected within ten calls counts here as a miss.", "type": "integer"}, "refusals_without_later_call": {"description": "Of refusals_considered, those after which their session recorded no further hook or ci call in the rows read: a single-call probe or smoke test, or an agent stopped for good. They had no chance to be corrected and are left out of the rate.", "type": "integer"}, "rows_read": {"description": "Ledger rows read to compute the figure, of every project, before the project filter.", "type": "integer"}, "self_corrected": {"description": "Of refusals_with_later_call, the refusals followed within the next ten calls of the same session by a call to the same file that was not refused and was not a command. Unchanged: a refusal with no later call could never have been corrected.", "type": "integer"}}, "required": ["refusals_considered", "refusals_with_later_call", "refusals_without_later_call", "self_corrected", "rate", "median_calls_to_correct", "rows_read", "complete"], "type": "object"}}, "securitySchemes": {"OperatorApiKey": {"description": "Operator key for writes that outlive the request. Reading needs none. A deployment that has configured no key refuses policy writes outright with 403.", "in": "header", "name": "X-API-Key", "type": "apiKey"}, "SignInSession": {"description": "A sign-in session token from POST /api/auth/sessions, sent as Authorization: Bearer <token>. It counts as the operator everywhere the operator key does, except for minting sign-in links, and lapses after 43200 seconds or when it is signed out.", "scheme": "bearer", "type": "http"}}}, "info": {"contact": {"name": "Threefold", "url": "https://d1og72wpk4aqig.cloudfront.net/"}, "description": "Autonomous coding agent governance sidecar and cost circuit-breaker. Deterministic code decides: it scans arguments for credential shapes, refuses writes that cross a Clean Architecture boundary, halts a session that repeats a cycle of tool calls, and caps projected spend. Amazon Bedrock (Claude Haiku 4.5) only phrases the verdict, and every response says which of the two produced the sentence it carries, in explanation_source. Certificates are SHA-256 fingerprints over a session's verdicts; where the stack holds a signing key, a KMS signature covers the same bytes.", "title": "Threefold Governance Sidecar API", "version": "1.0.0"}, "openapi": "3.1.0", "paths": {"/adapter/universal-tool-call": {"post": {"description": "Evaluates tools in native OpenAI function_call or Anthropic tool_use JSON formats.", "requestBody": {"content": {"application/json": {"schema": {"type": "object"}}}, "required": true}, "responses": {"200": {"description": "Normalized evaluation result, with the same verdict fields POST /evaluate-tool-call returns under evaluation"}, "400": {"description": "The body is not a JSON object, or the tool call is not one this adapter reads: an Anthropic tool_use object, an OpenAI function_call, a tool_calls array of exactly one call, or {tool_name, arguments} whose arguments are an object or the JSON text of one. A body naming the call in two of those shapes at once is refused too: one verdict cannot answer for both, and the shape not read would be approved unread"}}, "summary": "Universal Multi-Agent Tool Adapter"}}, "/api/auth/links": {"post": {"description": "Called by the CLI with the operator key, so the operator signs in to the dashboard without pasting the key into a page. Answers a code that works once, for 120 seconds, and a link to this stack's own dashboard that carries it: <base>dashboard.html#/signin?code=<code>&next=<next>, with next URL-encoded. Only the SHA-256 of the code is stored. Requires the key itself: a sign-in session is refused here with 403 (urn:threefold:error:session-not-enough), so a session can never renew itself.", "requestBody": {"content": {"application/json": {"example": {"next": "/projects/Acme-Billing"}, "schema": {"properties": {"next": {"default": "/overview", "description": "Where the dashboard goes after signing in: a path inside it, starting with / and carrying no scheme, host, //, backslash, fragment or whitespace.", "example": "/projects/Acme-Billing", "maxLength": 256, "type": "string"}}, "type": "object"}}}, "required": false}, "responses": {"200": {"content": {"application/json": {"schema": {"properties": {"code": {"type": "string"}, "expires_in": {"enum": [120], "type": "integer"}, "url": {"format": "uri", "type": "string"}}, "required": ["code", "expires_in", "url"], "type": "object"}}}, "description": "The code and the link, answered with Cache-Control: no-store"}, "400": {"content": {"application/problem+json": {"schema": {"properties": {"detail": {"type": "string"}, "status": {"type": "integer"}, "title": {"type": "string"}, "type": {"type": "string"}}, "type": "object"}}}, "description": "next is not a path inside the dashboard (urn:threefold:error:bad-request)"}, "401": {"content": {"application/problem+json": {"schema": {"properties": {"detail": {"type": "string"}, "status": {"type": "integer"}, "title": {"type": "string"}, "type": {"type": "string"}}, "type": "object"}}}, "description": "No key was presented"}, "403": {"content": {"application/problem+json": {"schema": {"properties": {"detail": {"type": "string"}, "status": {"type": "integer"}, "title": {"type": "string"}, "type": {"type": "string"}}, "type": "object"}}}, "description": "The key was rejected, a sign-in session was presented instead of the key (urn:threefold:error:session-not-enough), or this deployment has configured no operator key at all (urn:threefold:error:sign-in-disabled)"}}, "security": [{"OperatorApiKey": []}], "summary": "Mint a single-use sign-in link"}}, "/api/auth/sessions": {"delete": {"description": "Revokes the session presented as Authorization: Bearer <token>. The container that takes the request refuses the token at once; every other container within 60 seconds. Open, and idempotent: with no session, or one already gone, it still answers 200. The body is GET /api/auth/whoami as it stands after the sign-out.", "responses": {"200": {"content": {"application/json": {"schema": {"properties": {"authenticated": {"description": "True when the request carries a configured operator key or a live sign-in session.", "type": "boolean"}, "expires_at": {"description": "When the session lapses, in UTC. null for a key, which does not lapse, and for a stranger.", "format": "date-time", "type": ["string", "null"]}, "reads_public": {"description": "Whether this stack answers its reads without the operator (PublicReads).", "type": "boolean"}, "sandbox_writes": {"description": "Whether anyone may create a sandbox project and write to it here, which is exactly when reads_public is true.", "type": "boolean"}, "via": {"enum": ["key", "session", null], "type": ["string", "null"]}}, "required": ["authenticated", "via", "expires_at", "reads_public", "sandbox_writes"], "type": "object"}}}, "description": "Signed out; who the caller is now"}, "503": {"content": {"application/problem+json": {"schema": {"properties": {"detail": {"type": "string"}, "status": {"type": "integer"}, "title": {"type": "string"}, "type": {"type": "string"}}, "type": "object"}}}, "description": "The store did not confirm the sign-out, so other containers may honour the session until it lapses (urn:threefold:error:sign-out-incomplete)"}}, "security": [{"SignInSession": []}, {}], "summary": "Sign out"}, "post": {"description": "Open: the code is the credential. Spends the code, which then cannot be used again, and answers a session token that counts as the operator for 43200 seconds, presented as Authorization: Bearer <token>. Only the SHA-256 of the token is stored. A session is honoured only while the key that minted its code is still configured.", "requestBody": {"content": {"application/json": {"schema": {"properties": {"code": {"description": "The code from the sign-in link.", "type": "string"}}, "required": ["code"], "type": "object"}}}, "required": true}, "responses": {"200": {"content": {"application/json": {"schema": {"properties": {"expires_at": {"format": "date-time", "type": "string"}, "token": {"type": "string"}, "ttl_seconds": {"enum": [43200], "type": "integer"}}, "required": ["token", "expires_at", "ttl_seconds"], "type": "object"}}}, "description": "The session, answered with Cache-Control: no-store"}, "400": {"content": {"application/problem+json": {"schema": {"properties": {"detail": {"type": "string"}, "status": {"type": "integer"}, "title": {"type": "string"}, "type": {"type": "string"}}, "type": "object"}}}, "description": "No code was sent (urn:threefold:error:bad-request)"}, "401": {"content": {"application/problem+json": {"schema": {"properties": {"detail": {"type": "string"}, "status": {"type": "integer"}, "title": {"type": "string"}, "type": {"type": "string"}}, "type": "object"}}}, "description": "The code was used, has expired or was never issued (urn:threefold:error:sign-in-code-invalid)"}, "429": {"content": {"application/problem+json": {"schema": {"properties": {"detail": {"type": "string"}, "status": {"type": "integer"}, "title": {"type": "string"}, "type": {"type": "string"}}, "type": "object"}}}, "description": "The rate limit every route shares"}}, "summary": "Trade a sign-in code for a session"}}, "/api/auth/whoami": {"get": {"description": "Open on every stack: it answers about what the request carries. The pages read it to decide whether to offer sign-in, the sandbox or the operator's controls.", "responses": {"200": {"content": {"application/json": {"schema": {"properties": {"authenticated": {"description": "True when the request carries a configured operator key or a live sign-in session.", "type": "boolean"}, "expires_at": {"description": "When the session lapses, in UTC. null for a key, which does not lapse, and for a stranger.", "format": "date-time", "type": ["string", "null"]}, "reads_public": {"description": "Whether this stack answers its reads without the operator (PublicReads).", "type": "boolean"}, "sandbox_writes": {"description": "Whether anyone may create a sandbox project and write to it here, which is exactly when reads_public is true.", "type": "boolean"}, "via": {"enum": ["key", "session", null], "type": ["string", "null"]}}, "required": ["authenticated", "via", "expires_at", "reads_public", "sandbox_writes"], "type": "object"}}}, "description": "Answered with Cache-Control: no-store"}}, "security": [{"SignInSession": []}, {"OperatorApiKey": []}, {}], "summary": "Who the caller is, and what this stack leaves open"}}, "/api/decision": {"get": {"parameters": [{"in": "query", "name": "timestamp", "required": true, "schema": {"type": "string"}}, {"in": "query", "name": "verdict_id", "required": true, "schema": {"type": "string"}}], "responses": {"200": {"content": {"application/json": {"schema": {"properties": {"decision": {"$ref": "#/components/schemas/DecisionRow"}, "rule": {"oneOf": [{"$ref": "#/components/schemas/LayeringRule"}, {"type": "null"}]}, "session": {"properties": {"calls": {"type": "integer"}, "cost_usd": {"type": "number"}, "is_tripped": {"type": "boolean"}, "session_id": {"type": "string"}}, "type": ["object", "null"]}}, "type": "object"}}}, "description": "decision, session (or null) and rule (the layering rule's definition, or null for a gate)"}, "400": {"description": "timestamp or verdict_id is missing."}, "404": {"description": "No such decision is recorded."}}, "summary": "One decision, with its session and the layering rule it was keyed by"}}, "/api/decisions": {"get": {"description": "Reads the ledger day by day from today backwards. Each request reads a bounded number of ledger pages, so a filter that matches little may return fewer than limit rows with a next_cursor: follow it until it is null. The cursor is opaque and resumes exactly after the last row returned.", "parameters": [{"description": "The window, 1 to 30 days ending today.", "in": "query", "name": "days", "required": false, "schema": {"default": 7, "maximum": 30, "minimum": 1, "type": "integer"}}, {"description": "Only this project.", "in": "query", "name": "project", "required": false, "schema": {"type": "string"}}, {"description": "Only rows with this rule_key.", "in": "query", "name": "rule", "required": false, "schema": {"type": "string"}}, {"description": "refused, observed (ran, and a rule would have refused it), approved (nothing flagged it), or all.", "in": "query", "name": "kind", "required": false, "schema": {"default": "all", "enum": ["all", "refused", "observed", "approved"], "type": "string"}}, {"description": "Only rows with this review, or with none.", "in": "query", "name": "review", "required": false, "schema": {"default": "any", "enum": ["any", "unreviewed", "correct", "false_alarm"], "type": "string"}}, {"description": "Only this agent.", "in": "query", "name": "agent", "required": false, "schema": {"type": "string"}}, {"description": "Only this session.", "in": "query", "name": "session", "required": false, "schema": {"type": "string"}}, {"description": "At most this many rows.", "in": "query", "name": "limit", "required": false, "schema": {"default": 50, "maximum": 200, "minimum": 1, "type": "integer"}}, {"description": "The next_cursor of the previous page.", "in": "query", "name": "cursor", "required": false, "schema": {"type": "string"}}], "responses": {"200": {"content": {"application/json": {"schema": {"properties": {"items": {"items": {"$ref": "#/components/schemas/DecisionRow"}, "type": "array"}, "next_cursor": {"type": ["string", "null"]}}, "type": "object"}}}, "description": "items and next_cursor"}, "400": {"description": "A kind, review or cursor this service does not know."}}, "summary": "The ledger's rows, newest first, filtered and a page at a time"}}, "/api/insights": {"get": {"description": "Aggregates the decision ledger over a window for the Enforcement Console. A refusal and an observation are counted apart: an observation is a call that ran and that a rule in observe mode would have refused. Rows keep the target of a call and never its arguments.", "parameters": [{"in": "query", "name": "days", "required": false, "schema": {"default": 7, "maximum": 30, "minimum": 1, "type": "integer"}}], "responses": {"200": {"description": "totals (decisions, refused, approved, refusal_rate, observed, projects, developers), by_category, by_rule, by_observed_rule, by_project, by_developer, by_day, recent_refusals, recent_observations, coverage"}}, "summary": "What the gates decided, by project, kind of problem and rule"}}, "/api/overview": {"get": {"description": "Read from one rollup item per project and day, added to as every decision is recorded, so the numbers are exact however busy the ledger is. approved, observed and refused are disjoint: observed is a call that ran and that a rule would have refused, and a day's calls are the sum of the three. needs_review is the observed calls nobody has labelled; false_alarms the calls labelled false alarm. The totals are split in sandbox_split between visitors' sandboxes and every other project, so a page can say what a number is made of; each by_agent entry says what kind of caller it is, and coding_agents lists the coding agents alone. sources says the same of where the calls come from: the synthetic Acme fleet, the daily live agent's real Claude Code or Codex runs, the service's own synthetic probes, visitors' sandboxes, or anything else, and every by_project row names its source. A sandbox whose configuration has expired, a day after it was made, is left out of every figure here, as it always has been. self_correction is the one figure not read from the rollups: it is read from the ledger, a bounded number of rows, and says whether it covered the whole window.", "parameters": [{"description": "The window, 1 to 30 days ending today.", "in": "query", "name": "days", "required": false, "schema": {"default": 7, "maximum": 30, "minimum": 1, "type": "integer"}}, {"description": "Only this project.", "in": "query", "name": "project", "required": false, "schema": {"type": "string"}}], "responses": {"200": {"content": {"application/json": {"schema": {"properties": {"by_agent": {"description": "Every value of agent with a call in the window, most calls first.", "items": {"$ref": "#/components/schemas/AgentCalls"}, "type": "array"}, "by_origin": {"items": {"properties": {"calls": {"type": "integer"}, "origin": {"type": "string"}}, "type": "object"}, "type": "array"}, "by_project": {"items": {"properties": {"calls": {"type": "integer"}, "configured": {"type": "boolean"}, "last_seen": {"type": ["string", "null"]}, "needs_review": {"type": "integer"}, "project": {"type": "string"}, "refused": {"type": "integer"}, "sandbox": {"description": "True for a visitor's sandbox (^Acme-Sandbox-[0-9a-f]{8}$): the side of sandbox_split this project is counted on.", "type": "boolean"}, "source": {"description": "Where this project's calls come from: fleet for the six projects of the synthetic Acme fleet (Acme-Payments, Acme-Checkout, Acme-Treasury, Acme-Search, Acme-Mobile, Acme-Platform, exactly) on a stack that runs it, the public one, whose DemoFleet parameter is true and whose schedule sends their calls through the real evaluator; on any other stack those six names are other, since a team may call its own repository by one of them; live for the six projects of the daily live agent, which runs a real Claude Code or Codex on one of the benchmark's six standard Acme tasks each day (Acme-Live-billing-credit-limit, Acme-Live-catalog-vat-regen, Acme-Live-collections-webhook, Acme-Live-orders-s3-archive, Acme-Live-payments-staging-key, Acme-Live-warehouse-carrier-notify, exactly), on the stack it reports to, the same public one whose DemoFleet parameter is true, which starts them in Enforce; on any other stack those six names are other, since the benchmark names its projects that way on whatever stack it is pointed at; probe for Acme-Probe, exactly, the project the service's own live probes (scripts/probe_live.py) send their synthetic calls as, on the same public stack whose DemoFleet parameter is true; on any other stack that name is other, since there it is only what a caller chose to send; sandbox for a visitor's sandbox (^Acme-Sandbox-[0-9a-f]{8}$), the ones the probes create included; other for everything else, the page calls, the scenarios' simulations and a call sent outside the name pattern, recorded as unlabelled, included.", "enum": ["fleet", "live", "probe", "sandbox", "other"], "type": "string"}, "stage": {"type": "string"}, "would_refuse": {"type": "integer"}}, "type": "object"}, "type": "array"}, "by_rule": {"items": {"properties": {"refused": {"type": "integer"}, "rule_key": {"type": "string"}, "would_refuse": {"type": "integer"}}, "type": "object"}, "type": "array"}, "coding_agents": {"description": "The by_agent entries whose kind is coding_agent, in the same order and shape: the coding agents being governed, without the page's buttons, pipelines or rows older than the agent field.", "items": {"$ref": "#/components/schemas/AgentCalls"}, "type": "array"}, "generated_at": {"type": "string"}, "sandbox_split": {"description": "The totals split between visitors' sandboxes (names matching ^Acme-Sandbox-[0-9a-f]{8}$, whose calls include the would-refuse calls POST /api/sandbox seeds on purpose) and every other project. Each side is computed as the totals are, over its own projects, so the two add up to the totals.", "properties": {"elsewhere": {"$ref": "#/components/schemas/OverviewPart"}, "sandbox": {"$ref": "#/components/schemas/OverviewPart"}}, "type": "object"}, "self_correction": {"$ref": "#/components/schemas/SelfCorrection"}, "series": {"items": {"properties": {"approved": {"type": "integer"}, "day": {"type": "string"}, "observed": {"type": "integer"}, "refused": {"type": "integer"}}, "type": "object"}, "type": "array"}, "source": {"enum": ["rollups"], "type": "string"}, "sources": {"description": "The window's calls and projects by where they come from: the synthetic fleet, the daily live agent, the service's own probes, visitors' sandboxes, and everything else, each as by_project's source names it, except that a call in a live project is live only when its agent is claude-code or codex, and other otherwise, as a page's is. Computed over the same projects as the totals, so the five calls add up to totals.calls and the five projects to totals.projects. A page on the public stack says in words that the fleet is synthetic and runs through the real gates, that the live agent's calls are real Claude Code or Codex runs, one Acme task a day, in projects that enforce, and that the probes' calls are the service's own synthetic checks of the stack, never real work. A stack from before live was added reports fleet, sandbox and other only, and counts the live agent's calls as other; one from before probe was added counts the probes' calls as other.", "properties": {"fleet": {"$ref": "#/components/schemas/OverviewSource"}, "live": {"$ref": "#/components/schemas/OverviewSource"}, "other": {"$ref": "#/components/schemas/OverviewSource"}, "probe": {"$ref": "#/components/schemas/OverviewSource"}, "sandbox": {"$ref": "#/components/schemas/OverviewSource"}}, "type": "object"}, "stages": {"properties": {"enforce": {"type": "integer"}, "observe": {"type": "integer"}}, "type": "object"}, "totals": {"properties": {"agents": {"description": "Distinct values of agent with a call in the window, of every kind: page and unknown count here too.", "type": "integer"}, "approved": {"type": "integer"}, "calls": {"type": "integer"}, "coding_agents": {"description": "Of those, the coding agents (claude-code, codex, antigravity, muse): the length of coding_agents.", "type": "integer"}, "false_alarms": {"type": "integer"}, "needs_review": {"type": "integer"}, "projects": {"type": "integer"}, "refused": {"type": "integer"}, "would_refuse": {"type": "integer"}}, "type": "object"}, "window_days": {"type": "integer"}}, "type": "object"}}}, "description": "window_days, generated_at, source (always rollups), totals, sandbox_split, sources, series, by_agent, coding_agents, by_origin, by_rule, by_project, stages, self_correction (read from the ledger, not the rollups)"}}, "summary": "Tiles, a daily series and totals by agent, origin, rule and project, from the rollups"}}, "/api/projects": {"get": {"description": "Counts are over the last 7 days, from the rollups. A sandbox is listed until it expires.", "responses": {"200": {"content": {"application/json": {"schema": {"properties": {"projects": {"items": {"properties": {"agents": {"items": {"type": "string"}, "type": "array"}, "calls": {"type": "integer"}, "configured": {"description": "False for a project seen in the rollups with no configuration; its stage is then the stack default.", "type": "boolean"}, "created_at": {"type": ["string", "null"]}, "hook_modes": {"items": {"type": "string"}, "type": "array"}, "last_seen": {"type": ["string", "null"]}, "needs_review": {"type": "integer"}, "observe_rules": {"items": {"type": "string"}, "type": "array"}, "project": {"type": "string"}, "promoted_at": {"type": ["string", "null"]}, "refused": {"type": "integer"}, "sandbox": {"description": "True for a visitor's sandbox: a name matching ^Acme-Sandbox-[0-9a-f]{8}$, however it was made. Its calls include the synthetic ones POST /api/sandbox seeds.", "type": "boolean"}, "source": {"description": "Where this project's calls come from: fleet for the six projects of the synthetic Acme fleet (Acme-Payments, Acme-Checkout, Acme-Treasury, Acme-Search, Acme-Mobile, Acme-Platform, exactly) on a stack that runs it, the public one, whose DemoFleet parameter is true and whose schedule sends their calls through the real evaluator; on any other stack those six names are other, since a team may call its own repository by one of them; live for the six projects of the daily live agent, which runs a real Claude Code or Codex on one of the benchmark's six standard Acme tasks each day (Acme-Live-billing-credit-limit, Acme-Live-catalog-vat-regen, Acme-Live-collections-webhook, Acme-Live-orders-s3-archive, Acme-Live-payments-staging-key, Acme-Live-warehouse-carrier-notify, exactly), on the stack it reports to, the same public one whose DemoFleet parameter is true, which starts them in Enforce; on any other stack those six names are other, since the benchmark names its projects that way on whatever stack it is pointed at; probe for Acme-Probe, exactly, the project the service's own live probes (scripts/probe_live.py) send their synthetic calls as, on the same public stack whose DemoFleet parameter is true; on any other stack that name is other, since there it is only what a caller chose to send; sandbox for a visitor's sandbox (^Acme-Sandbox-[0-9a-f]{8}$), the ones the probes create included; other for everything else, the page calls, the scenarios' simulations and a call sent outside the name pattern, recorded as unlabelled, included.", "enum": ["fleet", "live", "probe", "sandbox", "other"], "type": "string"}, "stage": {"enum": ["observe", "enforce"], "type": "string"}, "would_refuse": {"type": "integer"}}, "type": "object"}, "type": "array"}}, "type": "object"}}}, "description": "projects"}}, "summary": "Every configured project, and every project seen in the last 7 days"}}, "/api/projects/{name}": {"get": {"description": "A project's rules are its layering rules in force plus the gates LOOP, PROTECTED_PATH, UNREADABLE_WRITE and BUDGET. Read from the rollups over the window.", "parameters": [{"description": "The project, an Acme-style alias such as Acme-Billing.", "in": "path", "name": "name", "required": true, "schema": {"example": "Acme-Billing", "type": "string"}}, {"description": "The window, 1 to 30 days ending today.", "in": "query", "name": "days", "required": false, "schema": {"default": 14, "maximum": 30, "minimum": 1, "type": "integer"}}], "responses": {"200": {"content": {"application/json": {"schema": {"properties": {"config": {"oneOf": [{"$ref": "#/components/schemas/ProjectConfig"}, {"type": "null"}]}, "project": {"type": "string"}, "readiness": {"properties": {"rules": {"items": {"properties": {"correct": {"type": "integer"}, "false_alarms": {"type": "integer"}, "kind": {"enum": ["layering", "gate"], "type": "string"}, "last_seen": {"type": ["string", "null"]}, "mode_now": {"enum": ["observe", "enforce"], "type": "string"}, "recommendation": {"type": "string"}, "refused": {"description": "Calls it refused in the window: an agent's while the rule enforced, or a page call, the demo page's or the live probes', which always enforces whatever the stage. A refusal nobody labelled makes no rule ready.", "type": "integer"}, "rule_key": {"type": "string"}, "state": {"description": "noisy if any call it flagged was marked a false alarm, else needs_review if any would-refuse call has no label, else ready if at least one call it flagged, would-refuse or refused, was marked correct, else quiet. A refusal nobody labelled is evidence of nothing either way, so a rule whose only record is such refusals, the demo page's or the probes' among them, is quiet until one is labelled.", "enum": ["quiet", "needs_review", "ready", "noisy"], "type": "string"}, "unreviewed": {"type": "integer"}, "would_refuse": {"description": "Calls it flagged that ran, in the window.", "type": "integer"}}, "type": "object"}, "type": "array"}, "summary": {"properties": {"calls_observed": {"type": "integer"}, "days_observed": {"type": "integer"}, "false_alarm_rate": {"type": "number"}, "false_alarms": {"type": "integer"}, "reviewed": {"type": "integer"}, "rules_needing_review": {"type": "integer"}, "rules_noisy": {"type": "integer"}, "rules_quiet": {"type": "integer"}, "rules_ready": {"type": "integer"}, "self_correction": {"$ref": "#/components/schemas/SelfCorrection"}, "stage": {"type": "string"}, "would_have_refused": {"type": "integer"}}, "type": "object"}}, "type": "object"}, "source": {"description": "Where this project's calls come from: the same value as the source of its row in GET /api/projects and in the overview's by_project, whose description says what each value means.", "enum": ["fleet", "live", "probe", "sandbox", "other"], "type": "string"}}, "type": "object"}}}, "description": "project, source, config (null when never configured) and readiness"}, "404": {"description": "The name is neither an alias this stack admits nor 'unlabelled'."}}, "summary": "One project: its configuration and whether each of its rules is ready to enforce"}, "post": {"parameters": [{"description": "The project, an Acme-style alias such as Acme-Billing.", "in": "path", "name": "name", "required": true, "schema": {"example": "Acme-Billing", "type": "string"}}], "requestBody": {"content": {"application/json": {"schema": {"properties": {"observe_rules": {"items": {"type": "string"}, "type": "array"}, "stage": {"enum": ["observe", "enforce"], "type": "string"}}, "type": "object"}}}, "required": false}, "responses": {"200": {"content": {"application/json": {"schema": {"properties": {"config": {"$ref": "#/components/schemas/ProjectConfig"}, "project": {"type": "string"}}, "type": "object"}}}, "description": "The project and its stored configuration."}, "400": {"description": "The name does not match AllowedProjectPattern, a stage is neither observe nor enforce, or a rule key is not one of the project's rules (its layering rules in force plus LOOP, PROTECTED_PATH, UNREADABLE_WRITE and BUDGET)."}}, "summary": "Create or change a project's configuration"}}, "/api/projects/{name}/demote": {"post": {"description": "Sets stage observe and adds a history entry. observe_rules is kept, so a promotion undone is one step to redo.", "parameters": [{"description": "The project, an Acme-style alias such as Acme-Billing.", "in": "path", "name": "name", "required": true, "schema": {"example": "Acme-Billing", "type": "string"}}], "requestBody": {"content": {"application/json": {"schema": {"type": "object"}}}, "required": false}, "responses": {"200": {"content": {"application/json": {"schema": {"properties": {"config": {"$ref": "#/components/schemas/ProjectConfig"}, "project": {"type": "string"}}, "type": "object"}}}, "description": "The project and its stored configuration."}, "400": {"description": "The name does not match AllowedProjectPattern, a stage is neither observe nor enforce, or a rule key is not one of the project's rules (its layering rules in force plus LOOP, PROTECTED_PATH, UNREADABLE_WRITE and BUDGET)."}}, "summary": "Back to observe, in one step"}}, "/api/projects/{name}/promote": {"post": {"description": "Sets stage enforce and observe_rules to the project's rules minus enforce, and adds a history entry.", "parameters": [{"description": "The project, an Acme-style alias such as Acme-Billing.", "in": "path", "name": "name", "required": true, "schema": {"example": "Acme-Billing", "type": "string"}}], "requestBody": {"content": {"application/json": {"schema": {"properties": {"enforce": {"items": {"type": "string"}, "type": "array"}}, "required": ["enforce"], "type": "object"}}}, "required": true}, "responses": {"200": {"content": {"application/json": {"schema": {"properties": {"config": {"$ref": "#/components/schemas/ProjectConfig"}, "project": {"type": "string"}}, "type": "object"}}}, "description": "The project and its stored configuration."}, "400": {"description": "The name does not match AllowedProjectPattern, a stage is neither observe nor enforce, or a rule key is not one of the project's rules (its layering rules in force plus LOOP, PROTECTED_PATH, UNREADABLE_WRITE and BUDGET)."}}, "summary": "Enforce the rules picked; every other rule of the project keeps observing"}}, "/api/projects/{name}/reviews": {"post": {"description": "The label is stored on the ledger row itself, conditionally on the row belonging to this project, with reviewed_at, review_note and reviewed_by, which is 8 hex of a hash of the credential presented (X-API-Key or Authorization: Bearer), never the credential, or anonymous. A row of another project, a row nothing flagged, or an item that cannot be read is skipped with its reason.", "parameters": [{"description": "The project, an Acme-style alias such as Acme-Billing.", "in": "path", "name": "name", "required": true, "schema": {"example": "Acme-Billing", "type": "string"}}], "requestBody": {"content": {"application/json": {"schema": {"properties": {"items": {"items": {"properties": {"label": {"enum": ["correct", "false_alarm", "clear"], "type": "string"}, "note": {"maxLength": 200, "type": "string"}, "timestamp": {"type": "string"}, "verdict_id": {"type": "string"}}, "required": ["timestamp", "verdict_id", "label"], "type": "object"}, "maxItems": 100, "type": "array"}}, "required": ["items"], "type": "object"}}}, "required": true}, "responses": {"200": {"content": {"application/json": {"schema": {"properties": {"skipped": {"items": {"properties": {"reason": {"description": "other-project, not-found, nothing-flagged, or why the item could not be read.", "type": "string"}, "verdict_id": {"type": "string"}}, "type": "object"}, "type": "array"}, "updated": {"type": "integer"}}, "type": "object"}}}, "description": "updated and skipped"}, "400": {"description": "items is not a list of 1 to 100 reviews, or the name does not match AllowedProjectPattern."}}, "summary": "Label what a rule flagged: correct, false alarm, or clear"}}, "/api/sandbox": {"post": {"description": "Creates Acme-Sandbox-<8 hex> in observe with sandbox true and a 24 hour expiry, and sends a dozen synthetic hook calls from claude-code, codex and antigravity through the real evaluator. Several would be refused under more than one rule, and one of them is a false alarm: a test module under tests/domain/ that the domain rule's path pattern also covers. The answer names that call in seeded_false_alarm, read off the seed rather than off the ledger, so a page can ask a visitor to spot it and tell them whether they did.", "requestBody": {"content": {"application/json": {"schema": {"type": "object"}}}, "required": false}, "responses": {"200": {"content": {"application/json": {"schema": {"properties": {"calls_seeded": {"type": "integer"}, "project": {"type": "string"}, "seeded_false_alarm": {"description": "The one seeded call a reviewer should mark a false alarm, named by the seed itself: its row in GET /api/decisions is the one with this target and this rule_key.", "properties": {"rule_key": {"description": "The rule that flags it, as its ledger row records it in rule_key.", "example": "python-domain-stays-pure", "type": "string"}, "target": {"description": "The path the call writes, as its ledger row records it in target.", "example": "tests/domain/test_order_totals.py", "type": "string"}, "why": {"description": "One plain sentence, no Markdown, saying why the rule is wrong about this call.", "type": "string"}}, "required": ["target", "rule_key", "why"], "type": "object"}, "url": {"example": "dashboard.html#/projects/Acme-Sandbox-0a1b2c3d", "type": "string"}}, "type": "object"}}}, "description": "project, calls_seeded, the dashboard url and seeded_false_alarm"}, "409": {"description": "This stack's AllowedProjectPattern does not admit sandbox names."}}, "summary": "A throwaway project, seeded, for trying the review and promotion walk"}}, "/api/sessions": {"get": {"description": "Returns the sessions the service has evaluated, newest first. This is the contract the sessions console renders. The listing scans the session table, which is honest at this scale only; a deployment with real traffic wants a secondary index on a recency key. The calls field counts retained history, which the store caps at the last fifty per session.", "parameters": [{"description": "Maximum rows to return. Clamped to between 1 and 200.", "in": "query", "name": "limit", "required": false, "schema": {"default": 50, "maximum": 200, "minimum": 1, "type": "integer"}}], "responses": {"200": {"content": {"application/json": {"schema": {"properties": {"count": {"type": "integer"}, "persistence": {"description": "Where the most recent session write landed. It does not describe where the rows below were read from.", "enum": ["dynamodb", "memory"], "type": "string"}, "sessions": {"items": {"properties": {"calls": {"description": "Retained history length, capped at 50.", "type": "integer"}, "created_at": {"format": "date-time", "type": "string"}, "developer_id": {"type": "string"}, "is_terminated": {"type": "boolean"}, "is_tripped": {"type": "boolean"}, "project_name": {"type": "string"}, "session_id": {"type": "string"}, "total_cost_usd": {"type": "number"}, "total_input_tokens": {"type": "integer"}, "total_output_tokens": {"type": "integer"}, "trip_reason": {"type": "string"}}, "type": "object"}, "type": "array"}}, "type": "object"}}}, "description": "Session summaries, with the persistence mode of the most recent write"}}, "summary": "List Governed Sessions"}}, "/dist/manifest.json": {"get": {"description": "Lets the installer, or a reader, check what was downloaded: each file in the bundle with its SHA-256 and size, the SHA-256 of the zip, and the SHA-256 of /install.py as this host serves it. Answered without a key on every stack.", "responses": {"200": {"content": {"application/json": {"schema": {"properties": {"bundle_sha256": {"pattern": "^[0-9a-f]{64}$", "type": "string"}, "files": {"items": {"properties": {"bytes": {"type": "integer"}, "path": {"example": "bin/threefold_hook.py", "type": "string"}, "sha256": {"pattern": "^[0-9a-f]{64}$", "type": "string"}}, "required": ["path", "sha256", "bytes"], "type": "object"}, "type": "array"}, "installer_sha256": {"pattern": "^[0-9a-f]{64}$", "type": "string"}}, "required": ["files", "bundle_sha256", "installer_sha256"], "type": "object"}}}, "description": "The manifest"}, "400": {"content": {"application/problem+json": {"schema": {"properties": {"detail": {"type": "string"}, "status": {"type": "integer"}, "title": {"type": "string"}, "type": {"type": "string"}}, "type": "object"}}}, "description": "The request's host cannot be written into a script (urn:threefold:error:unknown-host)"}, "500": {"content": {"application/problem+json": {"schema": {"properties": {"detail": {"type": "string"}, "status": {"type": "integer"}, "title": {"type": "string"}, "type": {"type": "string"}}, "type": "object"}}}, "description": "A file the manifest describes is missing from this deployment (urn:threefold:error:script-missing)"}}, "summary": "The hashes of the bundle and the installer"}}, "/dist/threefold-bundle.zip": {"get": {"description": "What the installer installs: bin/threefold_hook.py, bin/threefold_cli.py, lib/threefold/__init__.py and lib/threefold/domain/*.py, byte for byte as deployed. Built once per server with fixed timestamps, so every download from it is the same bytes and bundle_sha256 in the manifest stays true. Answered without a key on every stack.", "responses": {"200": {"content": {"application/zip": {"schema": {"format": "binary", "type": "string"}}}, "description": "The zip", "headers": {"X-Threefold-SHA256": {"description": "SHA-256 of the zip, in hex", "schema": {"pattern": "^[0-9a-f]{64}$", "type": "string"}}}}, "500": {"content": {"application/problem+json": {"schema": {"properties": {"detail": {"type": "string"}, "status": {"type": "integer"}, "title": {"type": "string"}, "type": {"type": "string"}}, "type": "object"}}}, "description": "A file the bundle carries is missing from this deployment (urn:threefold:error:script-missing)"}}, "summary": "The hook, the CLI and the domain package, as one zip"}}, "/evaluate-tool-call": {"post": {"description": "Evaluates one tool invocation against the credential scanner, the layering rules, the loop detector and the budget. A hook sends this before the tool call runs, and the pages send it too; agent, origin and explain say which of the two a row came from, so a visitor pressing a button on the dashboard is never counted as an agent being governed. The loop gate treats the two differently. For origin hook, a detected loop refuses the repeating call with BLOCKED_LOOP_DETECTED and records it, but never halts the session: session_tripped stays false and the next different call is judged as usual, so a governed developer is not locked out of their own session. A session whose id starts sim-, and a call from a page, keep the terminal halt: the third identical call is refused, the session is frozen and every later call is refused with BLOCKED_CIRCUIT_BREAKER until an operator resumes it. Repeats of read-only or polling calls (git status, log, diff or show, ls, cat, pwd, gh run view, list or watch, sleep, and reads) are recorded as observations and are never refused as a loop.", "requestBody": {"content": {"application/json": {"schema": {"properties": {"action_type": {"enum": ["FILE_READ", "FILE_WRITE", "COMMAND_EXEC"], "type": "string"}, "agent": {"description": "Which caller sent the call: claude-code, codex, antigravity, muse, pre-commit, ci or page. Stored on the ledger row. Any other value is recorded as 'unknown' and reported in warnings.", "enum": ["claude-code", "codex", "antigravity", "muse", "pre-commit", "ci", "page"], "type": "string"}, "arguments": {"description": "The call's own arguments. For a write these carry the text being written, which is what the layering rules read.", "type": "object"}, "budget_usd": {"type": "number"}, "developer": {"description": "Either 'anonymous' or a 12-character hex hash computed on the caller's machine, never a user name. developer_id is still accepted from older callers. Pages that list decisions show any developer value only as a short hash.", "type": "string"}, "developer_id": {"description": "What this field was called before the v2 request. New callers send developer.", "type": "string"}, "dry_run": {"description": "Default false. When true the call is evaluated and recorded as observed: it is never refused and never trips the session, and the rule that would have refused it is returned under observations. A repository installed in observe mode sends every call with dry_run true.", "type": "boolean"}, "explain": {"description": "Whether a refusal gets a sentence from Amazon Bedrock. Default true, which is what the pages send. Hooks send false, and the verdict then carries the deterministic reason with explanation_source 'deterministic' and no model call. An approved call is never sent to the model.", "type": "boolean"}, "hook_mode": {"description": "The mode the hook on the developer's machine runs in: observe sends every call as a dry run, managed leaves the decision to the project's stage on the server, enforce never sends a dry run. Stored on the ledger row. Absent, or any other value, is recorded as 'unknown'; another value is reported in warnings.", "enum": ["observe", "managed", "enforce"], "type": "string"}, "model_id": {"description": "The model that made the call, priced by it: a Haiku id takes the Haiku row, everything else the default Sonnet-class rate. Stored on the ledger row so a cost can be audited against the rates that produced it. Absent is 'default'.", "type": "string"}, "origin": {"description": "hook, page or ci. Stored on the ledger row. Any other value is recorded as 'unknown' and reported in warnings.", "enum": ["hook", "page", "ci"], "type": "string"}, "project_name": {"description": "Required. A name that does not match the stack's AllowedProjectPattern (default ^Acme-[A-Za-z0-9-]{1,40}$), or no name at all, is stored, counted and used as the metric dimension as 'unlabelled', and the response says so in warnings.", "type": "string"}, "projected_input_tokens": {"type": "integer"}, "projected_output_tokens": {"type": "integer"}, "session_id": {"description": "What the loop detector counts against. A caller that shares one id between users shares a halt with them.", "type": "string"}, "tool_name": {"type": "string"}}, "required": ["session_id", "tool_name"], "type": "object"}}}, "required": true}, "responses": {"200": {"content": {"application/json": {"schema": {"properties": {"bedrock_explanation": {"type": ["string", "null"]}, "current_session_cost_usd": {"type": "number"}, "explanation_source": {"description": "What produced bedrock_explanation: bedrock when the model answered, deterministic_fallback when it did not.", "type": "string"}, "observations": {"description": "What a rule in observe mode would have refused. The call still ran.", "items": {"type": "string"}, "type": "array"}, "observed_rules": {"items": {"type": "string"}, "type": "array"}, "persistence": {"enum": ["dynamodb", "memory"], "type": "string"}, "project_stage": {"description": "The calling project's stage on this stack: its own when configured, otherwise the stack's DEFAULT_HOOK_STAGE (default observe). The stage decides only calls with origin hook or ci that are not dry runs: observe judges them exactly as a dry run is judged, recorded and never refused; enforce runs the gates, except that a refusal under one of the project's observe_rules is recorded as an observation. Page and scenario calls always enforce. Reported on every verdict, whether or not it applied.", "enum": ["observe", "enforce"], "type": "string"}, "proof_hash": {"type": "string"}, "reason": {"type": "string"}, "risk_level": {"type": "string"}, "rule_evaluations": {"description": "Each invariant and whether this call satisfied it.", "type": "object"}, "session_tripped": {"description": "Whether the session is now halted, so that every later call is refused until an operator resumes it. A loop refused for origin hook leaves it false; a loop in a sim- or page session sets it.", "type": "boolean"}, "status": {"description": "APPROVED, or the name of what refused it.", "type": "string"}, "suggested_fix": {"description": "What to send instead of the refused call, worked out by deterministic code, never by a model. Set on a refusal, and on an observation only when the request sent explain true and its origin is neither hook nor ci, since a hook in Observe prints nothing; null otherwise. Also null when the call carries more text, counted over every string in its arguments, than its kind of fix can be worked out for within the gate's own 10 ms budget, by the verdict's rule key: 24,000 characters for advice (loop, budget, halted_session, protected_path, destructive_command) and 3,000 for a credential, whether or not the call runs a command; 6,000 for a layering refusal of a call that runs no command; and 1,500 for a layering refusal of a call that runs one, and for an unreadable_write. A layering fix is rewritten and checked only while every write in the call, taken together, comes to 1,500 characters or less; past that, up to its ceiling, it is advice in words with validated false and no writes, naming the imports to move (the first four, then how many more), the rules that forbid them and the first candidate layer where no rule forbids them. Past its ceiling the refusal goes out as it always did, without one. A starting point, never applied automatically. Never stored: the ledger row keeps only suggested_fix_kind and suggested_fix_validated. A hook appends the summary, and nothing else, to its deny reason.", "properties": {"checks": {"description": "Every check that was run on a proposed write.", "items": {"properties": {"gate": {"enum": ["layering", "credential", "boundary", "syntax", "route"], "type": "string"}, "passed": {"type": "boolean"}, "path": {"type": "string"}}, "type": "object"}, "type": "array"}, "kind": {"description": "Which gate's refusal this fixes.", "enum": ["layering", "credential", "protected_path", "unreadable_write", "loop", "budget", "halted_session", "destructive_command"], "type": "string"}, "steps": {"description": "What to do, in order, in words.", "items": {"type": "string"}, "type": "array"}, "summary": {"description": "One line, safe to append to a hook's deny reason: no newline, no control character, no credential.", "maxLength": 200, "type": "string"}, "validated": {"description": "True only when every proposed write was run through the same gates, with the rules that judged the call, and passed. Never true for advice in words or for a loop. It does not claim the rewrite behaves as the original did.", "type": "boolean"}, "writes": {"description": "What to send instead of what the call sent: a Write's whole content, an Edit's new text beside the same old_string, or text a shell command added, marked partial. Left out when the files come to more than 8 KB; they were still checked in full. Empty for advice in words.", "items": {"properties": {"content": {"type": "string"}, "new_file": {"description": "A port or an adapter proposed as a new file; if it already exists, add to it rather than overwrite it.", "type": "boolean"}, "old_string": {"description": "Present for an Edit retry: replace this with content.", "type": "string"}, "partial": {"description": "Text a shell command added to a file whose other lines were never seen; apply it with an Edit.", "type": "boolean"}, "path": {"type": "string"}}, "required": ["path", "content"], "type": "object"}, "type": "array"}}, "required": ["kind", "summary", "steps", "validated", "checks"], "type": ["object", "null"]}, "warnings": {"description": "What the service changed or ignored on the way in, for example a project name stored as 'unlabelled'. Always present, empty when nothing was changed.", "items": {"type": "string"}, "type": "array"}}, "type": "object"}}}, "description": "Evaluation verdict, the invariants it was measured against, and the explanation with its source"}, "400": {"description": "The body is not a JSON object, or a number in it cannot be used: a negative token count, or a budget of zero or less."}}, "summary": "Evaluate Agent Tool Call"}}, "/hooks/threefold_hook.py": {"get": {"description": "The hook itself: one standard-library script that puts Threefold in front of Claude Code, Codex, Antigravity or Muse, served as readable text rather than as a download a reader cannot inspect, and answered without a key even where reads are private, because a product nobody can obtain is a demo. The agent is detected from the shape of its input, or named with --agent. A refusal to Muse is written in the same shape as to Claude Code. The same file is served at /hooks/claude_code_hook.py and /claude_code_hook.py, so an install copied earlier keeps working.", "responses": {"200": {"content": {"text/plain": {"schema": {"type": "string"}}}, "description": "The script, as text/plain"}}, "summary": "The hook itself"}}, "/install.py": {"get": {"description": "threefold_install.py with this stack's own address, https://<host>/<stage>/, written in where the source says __THREEFOLD_ENDPOINT__, so the one command a team runs installs from and reports to the stack that served it. Served as readable text, answered without a key on every stack. The X-Threefold-SHA256 header is the SHA-256 of exactly the bytes in the body, and equals installer_sha256 in /dist/manifest.json fetched from the same host.", "responses": {"200": {"content": {"text/plain": {"schema": {"type": "string"}}}, "description": "The script, as text/plain", "headers": {"X-Threefold-SHA256": {"description": "SHA-256 of the body, in hex", "schema": {"pattern": "^[0-9a-f]{64}$", "type": "string"}}}}, "400": {"content": {"application/problem+json": {"schema": {"properties": {"detail": {"type": "string"}, "status": {"type": "integer"}, "title": {"type": "string"}, "type": {"type": "string"}}, "type": "object"}}}, "description": "The request's host cannot be written into a script (urn:threefold:error:unknown-host)"}, "500": {"content": {"application/problem+json": {"schema": {"properties": {"detail": {"type": "string"}, "status": {"type": "integer"}, "title": {"type": "string"}, "type": {"type": "string"}}, "type": "object"}}}, "description": "The installer is missing from this deployment (urn:threefold:error:script-missing)"}}, "summary": "The installer, pointed at this stack"}}, "/issue-certificate": {"post": {"description": "Issues a Governance Certificate over a session's own stored verdicts: the session id is the only input, and any evaluations the body still sends are ignored. A refusal anywhere in the session's past taints the certificate even after it scrolls out of the stored fifty. The SHA-256 fingerprint is unkeyed and detects corruption and casual edits; where the stack holds a signing key the same bytes carry a KMS signature. Nothing in CI requires one before a merge.", "responses": {"200": {"description": "The certificate, returned rather than archived"}, "400": {"description": "The body is not a JSON object, or a recorded session has no recorded verdicts"}, "404": {"description": "No session with that id has been recorded"}}, "summary": "Issue Governance Certificate"}}, "/policy/config": {"get": {"description": "Retrieves runtime governance thresholds (max spend, loop thresholds, blocked patterns).", "responses": {"200": {"description": "Current policy configuration"}}, "summary": "Get Dynamic Policy Configuration"}, "post": {"description": "Adjusts the runtime governance thresholds. This write is durable: it lands in DynamoDB, every container that starts afterwards adopts it, and warm containers re-read it every thirty seconds, so it requires the operator key even on a deployment where reads are anonymous. The handler rebuilds the whole policy from the body; a field left out keeps its shipped default, and blocked_patterns holds at most fifty regular expressions of two hundred characters each, each of which must compile.", "requestBody": {"content": {"application/json": {"schema": {"properties": {"blocked_patterns": {"items": {"type": "string"}, "type": "array"}, "loop_history_window": {"type": "integer"}, "max_session_budget_usd": {"type": "number"}, "max_single_call_usd": {"type": "number"}, "monomorphic_repetition_threshold": {"type": "integer"}}, "type": "object"}}}, "required": true}, "responses": {"200": {"description": "Updated policy configuration"}, "401": {"description": "No key was presented, and this deployment requires one for policy writes"}, "403": {"description": "The key was rejected, or this deployment has configured no policy-write key at all"}}, "security": [{"OperatorApiKey": []}], "summary": "Update Dynamic Policy Configuration"}}, "/proof.json": {"get": {"description": "Written by scripts/build_proof.py and deployed with the function, so it changes only when a new snapshot is committed. benchmark carries what benchmark/report.py computes from the result rows it names, per condition, with the headline sentence exactly as computed and pilot true when every real-agent row was a pilot run. private carries aggregate totals read from the owner's private stack with the operator key: never a project name, a path, a target or a developer. Each section names its source and when its snapshot was taken, and a section that was not measured is absent, never zero.", "responses": {"200": {"content": {"application/json": {"schema": {"properties": {"benchmark": {"description": "source, snapshot_at, pilot, headline, models, agent_versions, dates, run_ids, tasks, rows, real_rows, valid_rows, scripted_rows, conditions (per condition: n, not_measured, violation, completion, self_correction, overhead) and evidence.", "type": "object"}, "generated_at": {"type": "string"}, "generated_by": {"type": "string"}, "method": {"description": "The evidence files, by their path in the repository.", "items": {"properties": {"label": {"type": "string"}, "path": {"type": "string"}}, "type": "object"}, "type": "array"}, "private": {"description": "source, snapshot_at, window_days, days_observed, calls_governed, would_refuse, refused, reviewed, false_alarms, false_alarm_rate, projects, agents, coding_agents, stages and self_correction: numbers only. false_alarms and false_alarm_rate are null when calls were refused in the window, because the overview counts false alarms on refused calls as well and reviewed calls on would-refuse calls only. agents counts every value of a call's agent field, page and unknown included; coding_agents counts Claude Code, Codex and Antigravity alone, and is null from a stack that predates it. self_correction carries refusals_with_later_call and refusals_without_later_call, the rate's denominator and the refusals left out of it, or null for both from a stack that predates them, whose rate was over every refusal considered.", "type": "object"}, "schema": {"type": "integer"}}, "required": ["schema", "generated_at"], "type": "object"}}}, "description": "schema, generated_at, generated_by, benchmark (when measured), private (when measured), method"}, "404": {"content": {"application/problem+json": {"schema": {"type": "object"}}}, "description": "No snapshot is deployed here; the proof page reads this as not measured yet (urn:threefold:error:proof-not-found)"}, "500": {"content": {"application/problem+json": {"schema": {"type": "object"}}}, "description": "The deployed snapshot is not a JSON object (urn:threefold:error:proof-unreadable)"}}, "summary": "The proof page's snapshot: the benchmark and the owner's own use, anonymised"}}, "/readyz": {"get": {"description": "Verifies downstream dependencies (DynamoDB, Bedrock, S3, Webhook Dispatcher). HEAD answers with the same status and no body, so an uptime check need not download the breakdown on every probe.", "responses": {"200": {"description": "Subsystems health breakdown"}}, "summary": "Deep Subsystem Readiness Probe"}}, "/rules": {"get": {"description": "Open, because a reader has to be able to see the architecture they are held to. With project, answers that project's own rules when it has saved some. When it has not, is_default is true and the rules shown are the shared default its calls are judged against until it saves its own. Without project, answers the shared set, as before.", "parameters": [{"description": "The project whose rules to read, an Acme-style alias such as Acme-Billing.", "in": "query", "name": "project", "required": false, "schema": {"example": "Acme-Billing", "type": "string"}}], "responses": {"200": {"content": {"application/json": {"schema": {"properties": {"count": {"type": "integer"}, "extensions_read": {"items": {"type": "string"}, "type": "array"}, "is_default": {"description": "Asked with project: true when that project has saved no rules of its own and the rules shown are the shared default, false when they are its own. Asked without: true when the shared set is still the one Threefold ships with.", "type": "boolean"}, "languages_read": {"items": {"type": "string"}, "type": "array"}, "refresh_seconds": {"description": "How long a server may go on using the rules it holds before reading them again.", "type": "number"}, "rules": {"items": {"$ref": "#/components/schemas/LayeringRule"}, "type": "array"}, "unsupported": {"type": "string"}}, "type": "object"}}}, "description": "rules, count, is_default, languages_read, extensions_read, refresh_seconds, unsupported"}}, "summary": "The layering rules in force"}, "post": {"description": "Replaces a whole set and stores it. With project, the set is that project's own, stored under CONFIG#rules#<project>, and the gate applies it to calls whose project_name is that project; every other project keeps the shared set. Without project, the shared set is replaced, as before. The save is all or nothing: if any rule cannot be used, nothing is saved and the 400 names each rule and why. The server that takes the save applies the rules from its next call; every other server reads them again within refresh_seconds. Requires the operator key even where reads are anonymous, because the rules are the gate rather than a setting on it.", "requestBody": {"content": {"application/json": {"schema": {"properties": {"project": {"description": "The project these rules are for, an Acme-style alias such as Acme-Billing. Leave it out to replace the shared set every project without rules of its own uses.", "example": "Acme-Billing", "type": "string"}, "rules": {"items": {"$ref": "#/components/schemas/LayeringRule"}, "maxItems": 50, "type": "array"}}, "required": ["rules"], "type": "object"}}}, "required": true}, "responses": {"200": {"description": "RULES_UPDATED, with the rules as stored and refresh_seconds"}, "400": {"content": {"application/json": {"schema": {"properties": {"detail": {"type": "string"}, "problems": {"items": {"properties": {"id": {"type": ["string", "null"]}, "index": {"description": "Position of the rule in what was sent.", "type": ["integer", "null"]}, "reason": {"type": "string"}}, "type": "object"}, "type": "array"}, "title": {"type": "string"}, "type": {"type": "string"}}, "type": "object"}}}, "description": "At least one rule cannot be used; nothing was saved (urn:threefold:error:unusable-rule)"}, "401": {"description": "No key was presented"}, "403": {"description": "The key was rejected, or this deployment has configured no operator key at all"}}, "security": [{"OperatorApiKey": []}], "summary": "Replace the layering rules of one project, or the shared set"}}, "/rules/draft": {"post": {"description": "Asks the model for one rule in the format POST /rules accepts, checks it with the validation a save uses, as one more member of the rules in force for project (so an id already in force is caught), sets its mode to observe whatever the model proposed, and tries it on the examples sent and on up to two files built from the rule itself, with the verdicts POST /rules/explain gives. A drafted rule watches, so a file it would refuse reads OBSERVE. Nothing is saved, no verdict is issued and no ledger row is written: putting the draft in force is a separate POST /rules, which needs the operator. When the model cannot be reached the answer is 503 with no draft; Threefold never stands in for the model with a rule of its own. Each draft is at most two model calls, and only the description and the paths, expectations and imports of the examples reach the model, never the rest of an example's file. Drafting model calls are budgeted per stack per UTC day, in the stack's own table, so each stack in an account has a budget of its own; past it the answer is 429 and no model call is made. Open where PublicReads is true, as POST /rules/explain is; on a stack deployed with PublicReads=false it needs the operator key or a sign-in session.", "requestBody": {"content": {"application/json": {"schema": {"properties": {"description": {"description": "The boundary in words. Refused with 400 when empty or longer than 600 characters, rather than cut to length and drafted from half a sentence.", "example": "Billing domain classes may not reach persistence; java.util is fine.", "maxLength": 600, "minLength": 1, "type": "string"}, "examples": {"description": "Files the rule should refuse or allow, tried against the draft.", "items": {"properties": {"content": {"description": "What an agent might write there. Its imports are what a rule reads.", "example": "import javax.persistence.Entity;", "maxLength": 4000, "type": "string"}, "expect": {"enum": ["refuse", "allow"], "type": "string"}, "path": {"description": "At most 400 characters, on one line, with no spaces: the same test the gate applies before it judges a path.", "example": "src/main/java/com/acme/billing/domain/Order.java", "maxLength": 400, "type": "string"}}, "required": ["path", "expect"], "type": "object"}, "maxItems": 5, "type": "array"}, "project": {"description": "The project the rule is for. The draft is checked against that project's rules in force, its own or the shared set. A name outside AllowedProjectPattern is drafted with a warning, as POST /rules/explain answers it. Leave it out to draft for the shared set.", "example": "Acme-Billing", "maxLength": 120, "type": "string"}}, "required": ["description"], "type": "object"}}}, "required": true}, "responses": {"200": {"content": {"application/json": {"schema": {"properties": {"attempts": {"description": "Model calls made: one, or two when the first answer was repaired.", "maximum": 2, "minimum": 1, "type": "integer"}, "model": {"type": ["string", "null"]}, "notes": {"items": {"type": "string"}, "type": "array"}, "project": {"type": ["string", "null"]}, "rule": {"$ref": "#/components/schemas/LayeringRule"}, "saved": {"const": false, "type": "boolean"}, "source": {"const": "bedrock", "type": "string"}, "tried": {"items": {"properties": {"content_preview": {"description": "At most 160 characters of the content, credentials redacted.", "type": "string"}, "expected": {"enum": ["refuse", "allow"], "type": "string"}, "matched": {"description": "Whether the verdict is what was expected: REFUSE or OBSERVE for refuse, ALLOW for allow.", "type": "boolean"}, "note": {"type": "string"}, "origin": {"description": "An example sent in the request, or one built from the rule itself.", "enum": ["caller", "generated"], "type": "string"}, "path": {"type": "string"}, "verdict": {"enum": ["REFUSE", "OBSERVE", "ALLOW"], "type": "string"}}, "type": "object"}, "type": "array"}, "unsupported": {"type": "string"}, "validation": {"properties": {"errors": {"items": {"type": "string"}, "type": "array"}, "ok": {"const": true, "type": "boolean"}}, "type": "object"}, "warnings": {"items": {"type": "string"}, "type": "array"}}, "type": "object"}}}, "description": "rule (mode always observe), validation, tried, source (bedrock), model, attempts, project, saved (always false), notes, unsupported, warnings"}, "400": {"description": "The body is not a JSON object, the description is missing or longer than 600 characters, more than 5 examples were sent, an example's path is not one the gate would judge, its content is longer than 4000 characters or its expect is neither refuse nor allow, project is not a string, or the rules in force already number 50. No model call is made. invalid_params names the field."}, "401": {"description": "PublicReads is false and no key or sign-in session was presented"}, "403": {"description": "PublicReads is false and the key or sign-in session was rejected, or this deployment has configured no operator key at all (urn:threefold:error:reads-private)"}, "422": {"content": {"application/problem+json": {"schema": {"properties": {"attempts": {"type": "integer"}, "detail": {"type": "string"}, "instance": {"type": "string"}, "saved": {"const": false, "type": "boolean"}, "source": {"const": "bedrock", "type": "string"}, "status": {"type": "integer"}, "title": {"type": "string"}, "type": {"type": "string"}, "unsupported": {"type": "string"}}, "type": "object"}}}, "description": "The model read the description as a boundary a layering rule cannot express, so nothing was drafted (urn:threefold:error:not-a-layering-rule)"}, "429": {"content": {"application/problem+json": {"schema": {"properties": {"detail": {"type": "string"}, "instance": {"type": "string"}, "model": {"type": ["string", "null"]}, "saved": {"const": false, "type": "boolean"}, "status": {"type": "integer"}, "title": {"type": "string"}, "type": {"type": "string"}}, "type": "object"}}}, "description": "This stack's drafting budget for the day is spent, so no model call was made and nothing was drafted (urn:threefold:error:draft-budget-spent). The budget resets at midnight UTC."}, "502": {"content": {"application/problem+json": {"schema": {"properties": {"attempts": {"type": "integer"}, "detail": {"type": "string"}, "instance": {"type": "string"}, "model": {"type": ["string", "null"]}, "problems": {"items": {"type": "string"}, "type": "array"}, "rejected_draft": {"description": "The last answer, when it was small enough to echo, or null."}, "saved": {"const": false, "type": "boolean"}, "source": {"const": "bedrock", "type": "string"}, "status": {"type": "integer"}, "title": {"type": "string"}, "type": {"type": "string"}, "validation": {"properties": {"errors": {"items": {"type": "string"}, "type": "array"}, "ok": {"const": false, "type": "boolean"}}, "type": "object"}}, "type": "object"}}}, "description": "The model answered, but no answer was a usable rule, even after one repair; nothing was drafted (urn:threefold:error:undraftable-rule). problems says why, in the words POST /rules would use."}, "503": {"content": {"application/problem+json": {"schema": {"properties": {"detail": {"type": "string"}, "instance": {"type": "string"}, "model": {"type": ["string", "null"]}, "saved": {"const": false, "type": "boolean"}, "source": {"const": "unavailable", "type": "string"}, "status": {"type": "integer"}, "title": {"type": "string"}, "type": {"type": "string"}}, "type": "object"}}}, "description": "Amazon Bedrock could not be asked or did not answer, or this container has made its drafting calls; no rule was drafted and nothing was saved (urn:threefold:error:model-unavailable). There is no fallback draft."}}, "security": [{}, {"OperatorApiKey": []}, {"SignInSession": []}], "summary": "Draft one layering rule from a sentence with Amazon Bedrock, try it, and save nothing"}}, "/rules/explain": {"post": {"description": "Reads the imports in the content and judges them against the layering rules in force, or against a draft sent in the body. With project, the rules in force are the ones the gate would apply to that project: its own when it has saved some, the shared set otherwise. Only the layering rules are tried: a write this answers ALLOW for can still be refused for a credential, a protected path, a loop or its cost. No verdict is issued, no ledger row is written and a draft is never put in force, which is why it is open.", "requestBody": {"content": {"application/json": {"schema": {"properties": {"content": {"example": "import javax.persistence.Entity;", "type": "string"}, "path": {"description": "At most 400 characters, on one line, with no spaces: the same test the gate applies before it judges a path.", "example": "src/main/java/com/acme/billing/domain/Order.java", "type": "string"}, "project": {"description": "Try the rules that project's calls are judged against. A draft sent in rules is tried instead, whatever project says.", "example": "Acme-Billing", "type": "string"}, "rules": {"description": "A draft to try instead of the rules in force.", "items": {"$ref": "#/components/schemas/LayeringRule"}, "type": "array"}}, "required": ["path"], "type": "object"}}}, "required": true}, "responses": {"200": {"content": {"application/json": {"schema": {"properties": {"applicable_rules": {"items": {"type": "string"}, "type": "array"}, "imports": {"items": {"type": "string"}, "type": "array"}, "language": {"type": ["string", "null"]}, "note": {"type": "string"}, "scope": {"const": "layering rules only", "type": "string"}, "verdict": {"enum": ["REFUSE", "OBSERVE", "ALLOW"], "type": "string"}, "violations": {"items": {"type": "object"}, "type": "array"}}, "type": "object"}}}, "description": "language, imports, rules_considered (draft | in force), applicable_rules, verdict (REFUSE | OBSERVE | ALLOW), violations, note, scope"}, "400": {"description": "The body is not a JSON object, has no path, has a path the gate would never judge (urn:threefold:error:nothing-to-explain), or carries a draft with a rule that cannot be used (urn:threefold:error:unusable-rule, with problems)"}}, "summary": "Try the rules on a file without saving or recording anything"}}, "/sessions/{session_id}": {"get": {"description": "Retrieves cumulative spend, the cumulative and retained call counts and circuit breaker status for a session that has been recorded. Reading never creates a session.", "parameters": [{"in": "path", "name": "session_id", "required": true, "schema": {"type": "string"}}], "responses": {"200": {"description": "Current session statistics"}, "404": {"description": "No session with this id has been recorded (urn:threefold:error:session-not-found)"}}, "summary": "Inspect Session State"}}, "/sessions/{session_id}/resume": {"post": {"description": "Lifts the halt on a recorded session, so its next call is evaluated again instead of refused with BLOCKED_CIRCUIT_BREAKER. Requires the operator key even where reads are anonymous, because it undoes a halt the gate or an operator made. The body names who cleared the halt and why, and both are kept on the session as the record of it. History and spend are kept too, so a session past its budget, or repeating the call that halted it, halts again. Never creates a session.", "parameters": [{"in": "path", "name": "session_id", "required": true, "schema": {"type": "string"}}, {"description": "Replays the first response for a repeated key instead of resuming twice.", "in": "header", "name": "Idempotency-Key", "required": false, "schema": {"type": "string"}}], "requestBody": {"content": {"application/json": {"example": {"operator_name": "Acme platform on-call", "reason": "Budget raised for the Acme-Billing migration; resuming the same session."}, "schema": {"properties": {"operator_name": {"description": "Who cleared the halt.", "maxLength": 120, "minLength": 1, "type": "string"}, "reason": {"description": "Why it was cleared.", "maxLength": 240, "minLength": 1, "type": "string"}}, "required": ["operator_name", "reason"], "type": "object"}}}, "description": "The record of who cleared the halt and why. Both are required, because that record is the reason this route needs the operator key; a value over its length is refused rather than cut, since a shortened record says something its author did not.", "required": true}, "responses": {"200": {"content": {"application/json": {"schema": {"properties": {"budget_usd": {"type": "number"}, "is_tripped": {"type": "boolean"}, "note": {"type": "string"}, "operator": {"description": "The operator_name that was sent.", "type": "string"}, "previous_trip_reason": {"description": "The halt this resume cleared, as the session recorded it; empty when it recorded none.", "type": "string"}, "reason": {"type": "string"}, "resumed_at": {"format": "date-time", "type": "string"}, "session_id": {"type": "string"}, "status": {"enum": ["SESSION_RESUMED"], "type": "string"}, "total_cost_usd": {"type": "number"}}, "type": "object"}}}, "description": "The session is no longer halted"}, "400": {"description": "operator_name or reason is missing, is not text, or is longer than its limit (urn:threefold:error:bad-request, naming the field); nothing was changed"}, "401": {"description": "No key was presented"}, "403": {"description": "The key was rejected, or this deployment has configured no operator key at all"}, "404": {"description": "No session with this id has been recorded (urn:threefold:error:session-not-found), so there is nothing to resume and nothing is created"}, "409": {"description": "The session is running, not halted (urn:threefold:error:session-not-halted), so there is nothing to clear and nothing was changed"}}, "security": [{"OperatorApiKey": []}], "summary": "Resume a halted session"}}, "/sessions/{session_id}/terminate": {"post": {"description": "Immediately freezes an autonomous agent session, revoking further tool executions. Freezing an id nobody recorded answers 404; without the operator key only a scenario (sim-*) session can be frozen.", "parameters": [{"in": "path", "name": "session_id", "required": true, "schema": {"type": "string"}}, {"description": "Replays the first response for a repeated key instead of freezing twice. Allowed through CORS, so a page served from another origin may send it.", "in": "header", "name": "Idempotency-Key", "required": false, "schema": {"type": "string"}}], "requestBody": {"content": {"application/json": {"schema": {"properties": {"operator_name": {"type": "string"}, "reason": {"type": "string"}}, "type": "object"}}}, "required": true}, "responses": {"200": {"description": "Session frozen confirmation"}, "401": {"description": "Missing operator key for a non-scenario session"}, "404": {"description": "No session with that id has been recorded"}}, "summary": "Enterprise Emergency Kill Switch"}}, "/simulate-loop": {"post": {"description": "Simulates 3 consecutive identical file edits in a sim- session to demonstrate circuit-breaker tripping. The demo keeps the terminal halt: call 3 is refused and the session is frozen. A hook's loop refuses the repeating call without halting the session; see /evaluate-tool-call.", "responses": {"200": {"description": "Loop interception verdict with Bedrock explanation"}}, "summary": "Simulate Thrashing Loop"}}, "/simulate-secret": {"post": {"description": "Simulates an agent attempting to export AWS access keys, tripping the Secret Scanner.", "responses": {"200": {"description": "Security block verdict"}}, "summary": "Simulate Secret Leakage"}}, "/status": {"get": {"description": "Returns health status, active governance rules, and model configuration. HEAD answers with the same status and no body, for a monitor that wants the code and not the body.", "responses": {"200": {"content": {"application/json": {"schema": {"properties": {"service": {"example": "Threefold", "type": "string"}, "status": {"example": "HEALTHY", "type": "string"}}, "type": "object"}}}, "description": "Operational status and active rules"}}, "summary": "Health & Active Policy Rules"}}}, "servers": [{"description": "Live deployment, through the CloudFront edge.", "url": "https://d1og72wpk4aqig.cloudfront.net/prod"}, {"description": "The regional API in eu-west-1, answered directly.", "url": "https://raa131f9dj.execute-api.eu-west-1.amazonaws.com/prod"}, {"description": "Local runtime, started by make run.", "url": "http://127.0.0.1:8001"}]}