Quorum: agent control plane

Quorum governs agents that you run on your own infrastructure. You register an agent with a declarative spec, a reviewer approves and seats a version, and your worker claims runs, calls Colrows tools with a short-lived run token, and submits a result. Colrows enforces the spec on every tool call, checks that the result's evidence came from the run, and records every run, tool call, and decision. This page is the reference for the spec, the worker protocol, run tokens, the Quorum tools, the output contract, and the admin API.

Quorum is off until an administrator enables it.

An organization administrator turns Quorum on in the Quorum settings (PUT /api/quorum/v1/settings with enabled: true). While it is off, nobody holds a Quorum role, no run starts, and workers get 403 QUORUM_DISABLED.

How it works

  1. Register. An author creates an agent with a unique agentKey, then writes a draft version: an agent spec that names its semantic scope, its allowed Colrows tools, its identity mode, its limits, its triggers, and its output contract.
  2. Approve. The author submits the draft. Colrows validates the spec and resolves every asset in its scope. A Quorum admin approves or rejects it; when the organization requires evaluation (the default), approval and seating need a passing evaluation at or above the spec's minimumPassRate.
  3. Seat. A Quorum admin seats an approved version. Only the seated version runs, and seating a new version retires the previous one.
  4. Run on your infrastructure. A trigger (on demand, a schedule, or an evaluation) queues a run. Your worker claims it, receives the spec, the input, and a run token, and calls the agent tools with that token. The Quorum guard checks each call against the spec.
  5. Check. The worker submits a result in the ANALYST_ANSWER contract. The Quorum check proves that every piece of evidence is real and was produced by this run. A result that fails the check fails the run.
  6. Audit. Runs, tool calls, worker events, check findings, and every registry change are recorded and readable through the admin API.

Concepts

TermMeaning
AgentA registered agent, identified by agentId (qa_…) and a unique agentKey of 3 to 64 lowercase letters, digits, and hyphens that starts and ends with a letter or digit. It has an owner, a list of invokers (users and groups who may start runs), and a status: ACTIVE, UNSEATED, or RETIRED.
VersionA numbered spec of an agent. Its state is DRAFT, IN_REVIEW, APPROVED, REJECTED, SEATED, or RETIRED. Submission records a specDigest (sha256: and the hex SHA-256 of the spec's canonical JSON).
Seat / unseatSeating puts an approved version in service. Unseating is the kill switch: no run of the agent can start until a version is seated again, run tokens stop working at once, queued runs end, and running runs are cancelled at their next heartbeat. Seating the already seated version of an unseated agent puts it back in service.
RunOne execution of a seated version (or of any version, for an evaluation), identified by runId (qr_…). Status is QUEUED, RUNNING, CANCEL_REQUESTED, CHECKING, SUCCEEDED, FAILED, CANCELLED, or EXPIRED.
ActorWho the run acts for: USER (the user who started an on-demand run, or the agent owner for a schedule) or AGENT (the agent's own service principal). The run reads data with the actor's access and masking policies. Workers see only the actor's type and display name.
WorkerYour software that claims and executes runs. It authenticates with a worker client that an admin creates and that serves a list of agents.
Run tokenA short-lived bearer token (cr_quorum_rt_…) bound to one run. The worker uses it to call agent tools. It carries only the scopes of the tools the spec allows.
Quorum guardThe server-side enforcement of the spec on every agent tool call made with a run token: allowed tools, semantic scope, raw SQL, and budgets.
Quorum checkThe server-side check of a submitted result: shape, and that its evidence comes from this run.

Run lifecycle

  • A trigger creates a QUEUED run. On-demand runs and schedules respect the agent's maxConcurrentRuns and maxRunsPerHour; evaluation runs do not.
  • A worker claims it: the run becomes RUNNING, a lease and a run token are issued, and the run's deadline is set to maxRunSeconds after the first claim.
  • complete moves the run to CHECKING, runs the Quorum check, and ends it SUCCEEDED or FAILED with code QUORUM_CHECK_FAILED.
  • fail with a retryable code, or a lease that expires, queues the run again while attempts remain; otherwise the run is FAILED. A run has at most 2 attempts.
  • Cancelling a queued run ends it at once. Cancelling a running run sets CANCEL_REQUESTED, revokes its run tokens, and the worker learns at its next heartbeat; the run then becomes CANCELLED.
  • A run past its deadline becomes EXPIRED with failure code MAX_RUN_SECONDS. A recovery task checks for expired leases and deadlines every minute.

Agent spec

A version's spec is JSON. Drafts are checked for structure; submission also checks that every datasource, metric, and verified query in scope is active in the organization and that each metric and verified query belongs to a datasource in scope.

{
  "specVersion": 1,
  "agentKey": "revenue-analyst",
  "displayName": "Revenue analyst",
  "purpose": "Explain week-over-week changes in net revenue.",
  "instructions": "Use verified queries. Cite the execution behind every finding.",
  "semanticScope": {
    "datasourceIds": ["sales-prod"],
    "metricIds": ["metric-revenue"],
    "verifiedQueryIds": ["vq-123"],
    "assetVersionPolicy": "FOLLOW_LATEST"
  },
  "tools": {
    "colrows": ["searchSemanticEntities", "getSemanticEntities", "executeSQL",
                "getSQLExecutionStatus", "getSQLExecutionResult",
                "getRunContext", "reportSemanticGap"],
    "external": []
  },
  "identity": { "mode": "ON_BEHALF_OF_USER", "fallback": "DENY" },
  "limits": { "maxRunSeconds": 300, "maxQueryExecutionsPerRun": 10 },
  "triggers": [
    { "kind": "ON_DEMAND" },
    { "kind": "SCHEDULE", "cron": "0 8 * * MON", "zoneId": "Europe/London",
      "runAsUserId": "<owner-user-id>", "input": { "question": "Summarize last week's revenue" } }
  ],
  "output": { "contract": "ANALYST_ANSWER", "contractVersion": 1 },
  "evaluation": { "suiteIds": ["<suite-id>"], "minimumPassRate": 0.9 }
}
FieldRules
specVersionMust be 1.
agentKeyMust equal the agent's key.
displayNameRequired, at most 120 characters.
purposeRequired, at most 2,000 characters.
instructionsOptional, at most 20,000 characters. Your worker receives it with the spec.
semanticScope.datasourceIdsAt least one. Every table, column, and dataset of a datasource in scope is in scope.
semanticScope.metricIds, verifiedQueryIdsOptional. Metrics and verified queries are in scope only when listed by ID. Each list holds at most 200 unique, non-blank IDs (as does datasourceIds).
semanticScope.assetVersionPolicyRequired: FOLLOW_LATEST, or PINNED to pin the metric and verified-query versions resolved at submission. A pinned agent can run only the pinned version of a verified query.
tools.colrowsAt least one tool, no repeats, from the tool catalog. The run token carries exactly the scopes these tools need.
tools.externalMust be empty; external tools are not supported.
identity.modeRequired: ON_BEHALF_OF_USER (the run acts for a user, with that user's access) or SERVICE_PRINCIPAL (the run acts as the agent).
identity.fallbackRequired with ON_BEHALF_OF_USER: DENY or SERVICE_PRINCIPAL. It decides what a schedule without runAsUserId does.
limitsOptional; each value must be positive and no higher than the organization's maximum. Unset values use the organization default. See limits.
triggers1 to 20 triggers. ON_DEMAND takes no cron or zoneId; only a version with an ON_DEMAND trigger can be started on demand. SCHEDULE needs a five-field cron and an IANA zoneId. A schedule of a version that acts for users needs runAsUserId, which must be the agent owner, unless identity.fallback is SERVICE_PRINCIPAL. input is at most 32 KB.
outputRequired. The only registered contract is ANALYST_ANSWER version 1.
evaluationOptional. suiteIds needs at least one suite; minimumPassRate is from 0 to 1.

Schedule cron fields are minute hour day-of-month month day-of-week. Minutes and hours are numbers or comma lists (no ranges or steps), naming at most 96 times a day. A cron may restrict the day of month or the day of week, not both. Day of week accepts 0 to 7 (0 and 7 are Sunday) and MON to SUN; months accept 1 to 12 and JAN to DEC. Schedules follow the seated version: seating replaces the old version's schedules, and unseating disables them.

Limits

A spec's limits fall back to the organization's default limits, which fall back to the platform defaults. The organization's maximum limits (platform defaults unless set) cap every spec.

LimitPlatform defaultEnforced
maxRunSeconds600The run's deadline after its first claim. Leases and run tokens never outlast it.
maxToolCallsPerRun200Guard: further calls are denied with BUDGET_EXCEEDED.
maxQueryExecutionsPerRun40Guard: further executeSQL calls are denied with BUDGET_EXCEEDED.
maxRowsPerQuery5000Guard lowers limits.maxRows on executeSQL to this value.
maxConcurrentRuns4Runs in progress per agent, and executions in progress per run principal (429 RATE_LIMITED).
maxRunsPerHour120Runs created per agent in the last hour.
maxCostUnitsPerRun500Each query's estimated cost is charged before it runs; a query that would exceed the remaining budget is denied with BUDGET_EXCEEDED. Unknown cost is not charged.

Worker protocol

Your worker talks to the worker API at /api/quorum/v1/worker. Every route is POST with a JSON body, returns raw JSON (no envelope) with Cache-Control: no-store, and returns errors as {"code": "…"}.

Authenticate the worker

  1. A Quorum admin creates a worker client with POST /api/quorum/v1/worker-clients (see admin API), giving a name, the agentIds it serves (or ["*"] for every agent in the organization), and expiresAt in epoch milliseconds within one year. The response contains clientId and clientSecret. Copy the secret straight away: Colrows shows it only once.
  2. The worker exchanges the client ID and secret for an access token, the same way as any OAuth client. A worker client holds only quorum:worker and can request no other scope.
    curl -X POST https://cloud.colrows.com/api/oauth/token \
      -u "$WORKER_CLIENT_ID:$WORKER_CLIENT_SECRET" \
      -d grant_type=client_credentials \
      -d scope=quorum:worker
  3. Send the access_token (cr_oauth_cc_at_…) as a bearer token on every worker call. It lasts 15 minutes; request a new one before it expires. A worker token reaches only the worker API.

Worker clients don't appear in, and can't be revoked from, the general OAuth client settings. Revoke one with POST /api/quorum/v1/worker-clients/{clientId}/revoke.

POST /claims

Claims up to four due runs of agents the worker client serves, in its organization only.

POST /api/quorum/v1/worker/claims
Authorization: Bearer cr_oauth_cc_at_...

{ "workerId": "worker-eu-1", "agentKeys": ["revenue-analyst"], "contractVersion": 1 }

workerId is required, at most 200 characters; use a stable ID per worker process. agentKeys is optional and narrows the claim to those agents among the ones the client serves; keys it doesn't serve are ignored. contractVersion must be 1; any other value, or none, returns 409 CONTRACT_MISMATCH. An empty runs array means there is nothing to do.

{
  "runs": [
    {
      "runId": "qr_8f0c...",
      "agent": { "agentKey": "revenue-analyst", "version": 3, "specDigest": "sha256:..." },
      "spec": { /* the full agent spec of that version */ },
      "input": { "question": "Why did net revenue fall last week?" },
      "actor": { "type": "USER", "displayName": "Asha Rao" },
      "lease": { "leaseEpoch": 1, "leaseToken": "...", "leaseUntil": "2026-10-31T09:12:00Z" },
      "runToken": { "accessToken": "cr_quorum_rt_...", "expiresAt": "2026-10-31T09:25:00Z" },
      "endpoints": { "agentRest": "/api/agents/v1", "agentMcp": "/api/mcp/agents" },
      "limits": {
        "maxRunSeconds": 600, "maxToolCallsPerRun": 200, "maxQueryExecutionsPerRun": 40,
        "maxRowsPerQuery": 5000, "maxConcurrentRuns": 4, "maxRunsPerHour": 120,
        "maxCostUnitsPerRun": 500
      },
      "contractVersion": 1
    }
  ]
}

Times are ISO-8601 UTC strings. actor.type is USER or AGENT; the claim never contains the actor's email address, groups, or policies. Keep leaseEpoch and leaseToken: every later call for the run needs them with the same workerId.

POST /runs/{runId}/heartbeat

{ "workerId": "worker-eu-1", "leaseEpoch": 1, "leaseToken": "..." }
{
  "leaseUntil": "2026-10-31T09:14:00Z",
  "cancelled": false,
  "runToken": { "accessToken": "cr_quorum_rt_...", "expiresAt": "2026-10-31T09:29:00Z" }
}

Extends the lease to 120 seconds from now (never shortening it, and never past the run's deadline). When the current run token expires within 5 minutes, the response carries a new runToken; otherwise it carries none, and you keep using the current one. For a run that acts for a user, a token refresh first confirms the user again (still active, still allowed to use the agent, with current directory groups); if that fails, the run is cancelled with code ACTOR_REVOKED. When cancelled is true, the run is cancelled: stop work and make no more calls for it. Heartbeat well inside the 120-second lease, for example every 30 seconds.

POST /runs/{runId}/events

Optional progress events for monitoring. Up to 100 events per call.

{
  "workerId": "worker-eu-1", "leaseEpoch": 1, "leaseToken": "...",
  "events": [
    { "kind": "MODEL_CALL", "summary": "Planned the revenue breakdown",
      "inputTokens": 1830, "outputTokens": 412,
      "attributes": { "model": "your-model-name" }, "reportedAt": 1793438400000 }
  ]
}

kind is required, at most 64 characters. summary is at most 500 characters. attributes is a string-to-string map of at most 20 entries. inputTokens, outputTokens, and reportedAt are optional integers. The response is { "accepted": <count> }. Events are kept for the organization's telemetry retention period.

POST /runs/{runId}/complete

{
  "workerId": "worker-eu-1", "leaseEpoch": 1, "leaseToken": "...",
  "result": { /* an ANALYST_ANSWER v1 object */ }
}

Submits the result. result is required and must be at most 256 KB as canonical JSON, or the call fails with 413 RESULT_TOO_LARGE. Completion revokes the run's tokens, runs the Quorum check, and returns the outcome:

{
  "runId": "qr_8f0c...",
  "status": "SUCCEEDED",
  "quorumCheck": {
    "status": "PASSED",
    "findings": [
      { "rule": "FINDING_WITHOUT_EVIDENCE", "severity": "WARNING", "message": "findings[2] has no evidence" }
    ]
  }
}

Completion is idempotent: sending the same result with the same lease credentials again returns the stored outcome. A different result for a run that already has one returns 409 LEASE_OR_STATE_CONFLICT.

POST /runs/{runId}/fail

{
  "workerId": "worker-eu-1", "leaseEpoch": 1, "leaseToken": "...",
  "code": "MODEL_UNAVAILABLE", "message": "The model endpoint timed out"
}

code must match [A-Z][A-Z0-9_]{0,63}; message is at most 2,000 characters. The codes WORKER_ERROR, MODEL_UNAVAILABLE, NETWORK_ERROR, and TOOL_UNAVAILABLE are retryable: the run is queued again with a backoff that starts at 30 seconds and doubles, while attempts remain and the deadline has not passed. Any other code, or no attempts left, fails the run. The response is { "runId", "status", "quorumCheck": null }, where status is QUEUED or FAILED. The run's tokens are revoked either way.

POST /runs/{runId}/yield

{ "workerId": "worker-eu-1", "leaseEpoch": 1, "leaseToken": "..." }

Returns the run to the queue without using an attempt, for example when the worker is shutting down. The run can be claimed again after about 5 seconds. The response is { "runId", "status": "QUEUED", "quorumCheck": null }, and the run's tokens are revoked.

Worker errors

StatusCodeMeaning
400INVALID_REQUESTA required field is missing or a value is out of range.
403WORKER_SCOPE_DENIEDThe token does not belong to a worker client of this organization.
403QUORUM_DISABLEDQuorum is not enabled for the organization (returned by /claims).
404RUN_NOT_FOUNDNo such run in the worker client's organization.
409CONTRACT_MISMATCHThe claim's contractVersion is not 1.
409LEASE_OR_STATE_CONFLICTThe worker no longer owns the lease (wrong workerId, leaseEpoch, or leaseToken, or the lease expired), or the run changed state. Stop work on the run.
413RESULT_TOO_LARGEThe result is over 256 KB.
503WORKER_BACKEND_UNAVAILABLEA temporary server failure. Retry with backoff.

A missing or invalid token gets 401, and a token without quorum:worker gets 403, before the route runs; those responses use the standard Colrows error body, not {"code"}.

A worker loop

# Pseudo-code. BASE is your Colrows host, for example https://cloud.colrows.com
token = client_credentials_token(scope="quorum:worker")      # refresh before 15 minutes
loop:
  runs = POST BASE/api/quorum/v1/worker/claims
           { workerId, contractVersion: 1 }
  if runs is empty: sleep a few seconds; continue
  for each run (in parallel):
    lease    = run.lease
    runToken = run.runToken.accessToken
    start a heartbeat every 30 s:
      hb = POST .../runs/{runId}/heartbeat { workerId, leaseEpoch, leaseToken }
      if hb.cancelled: stop the run
      if hb.runToken: runToken = hb.runToken.accessToken
    try:
      # Your agent: follow run.spec.instructions, call tools with the run token
      #   REST: BASE + run.endpoints.agentRest,  MCP: BASE + run.endpoints.agentMcp
      result = run_agent(run.spec, run.input, runToken)
      POST .../runs/{runId}/complete { workerId, leaseEpoch, leaseToken, result }
    except model or network failure:
      POST .../runs/{runId}/fail { workerId, leaseEpoch, leaseToken,
                                   code: "MODEL_UNAVAILABLE", message }
    on 409 LEASE_OR_STATE_CONFLICT: abandon the run

Run tokens and scopes

A run token (cr_quorum_rt_…) is issued at claim and refreshed by heartbeat. It lasts 15 minutes, or until the run's deadline if sooner. It works only while its run is RUNNING and its agent is in service, and Colrows revokes it when the run completes, fails, yields, is cancelled, or the agent is unseated. Only its SHA-256 digest is stored.

  • It is accepted only by the agent interface: REST under /api/agents/v1 and MCP at POST /api/mcp/agents. Every other route refuses it.
  • It acts as the run's actor: the user the run acts for, with the directory groups resolved for the run, or the agent's service principal. Data access and masking policies apply as for that principal; the token never acts as an administrator.
  • Its scopes are exactly the scopes of the tools in tools.colrows, plus quorum:run. It never carries data:sql, semantic:read-write, access-control:read, or access-control:read-write.
  • On agent MCP, tools/list adds getRunContext and reportSemanticGap for a run token.

Tool catalog

The tools a spec can allow, and the scopes each one puts in the run token. Arguments and results are in the MCP Tool Reference.

ToolScopes
listDatasources, getJoinGraph, resolveValuessemantic:read
searchSemanticEntities, getSemanticEntities, searchSqlResolutions, findSqlResolutionMatchsemantic:read, semantic:sql
validateSQLsql:execute
compileSQL, executeSQL, getSQLExecutionStatus, getSQLExecutionResult, cancelSQLExecutiondata:query
askDocumentsdocuments:read
getRunContext, reportSemanticGapquorum:run

The Quorum guard

The guard runs on every agent tool call made with a run token, on REST and MCP alike. It adds to scope and policy checks; it replaces neither.

  • Allowed tools. A tool not in tools.colrows is denied, including getRunContext and reportSemanticGap. Every call, including a denied one, counts against maxToolCallsPerRun.
  • Datasource scope. searchSqlResolutions, findSqlResolutionMatch, getJoinGraph, resolveValues, and compileSQL need a datasourceId in scope. searchSemanticEntities without datasourceIds is narrowed to the datasources in scope; with them, each must be in scope.
  • Queries. executeSQL runs only verified queries listed in verifiedQueryIds (at the pinned version for a PINNED scope); raw SQL is denied. validateSQL accepts a listed verified query, or SQL on a datasource in scope.
  • Executions. getSQLExecutionStatus, getSQLExecutionResult, and cancelSQLExecution work only on executions this run created.
  • Result filtering. listDatasources and searchSemanticEntities results drop out-of-scope items. getSemanticEntities moves out-of-scope entities to unavailable, which gives no reason. Metrics and verified queries count as in scope only when listed by ID.
  • Budgets. See limits.

A guard denial is 403 ACCESS_DENIED with the reason in quorumDenial on REST, and in _meta["colrows/quorumDenial"] on MCP (next to _meta["colrows/errorCode"]):

{
  "type": "about:blank",
  "title": "ACCESS_DENIED",
  "status": 403,
  "detail": "Quorum agents run only verified queries",
  "code": "ACCESS_DENIED",
  "requestId": "req-123",
  "retryable": false,
  "quorumDenial": "RAW_SQL_DENIED"
}
quorumDenialMeaning
TOOL_NOT_ALLOWEDThe spec does not allow the tool.
OUT_OF_SCOPEA datasource, verified query, verified-query version, or execution is outside the run's scope.
RAW_SQL_DENIEDexecuteSQL was called with raw SQL.
BUDGET_EXCEEDEDThe run used its tool calls, query executions, or cost units.

Once the run is no longer RUNNING, its run token is refused with 401.

Quorum tools

Two tools exist only for run tokens. Both are available on agent REST and agent MCP with the same input and output, and both must be listed in tools.colrows. Unlike other agent REST routes, their REST response is the result object itself, not wrapped in data and meta. Errors use application/problem+json as described in the MCP Tool Reference.

getRunContext

GET /api/agents/v1/quorum/run
Authorization: Bearer cr_quorum_rt_...

Takes no arguments. Returns the run's agent, scope, allowed tools, limits, and remaining budget:

{
  "runId": "qr_8f0c...",
  "agent": { "agentKey": "revenue-analyst", "version": 3 },
  "actor": { "type": "USER" },
  "scope": {
    "datasourceIds": ["sales-prod"],
    "metricIds": ["metric-revenue"],
    "verifiedQueryIds": ["vq-123"],
    "assetVersionPolicy": "FOLLOW_LATEST"
  },
  "tools": ["executeSQL", "getRunContext", "getSQLExecutionResult", "..."],
  "limits": { "maxRunSeconds": 300, "maxToolCallsPerRun": 200, "...": "..." },
  "remaining": { "toolCalls": 187, "queryExecutions": 8, "costUnits": 455 },
  "deadline": "2026-10-31T09:15:00Z",
  "requestId": "req-123",
  "contractVersion": "1"
}

A remaining value is null when that limit is not set.

reportSemanticGap

POST /api/agents/v1/quorum/gaps
Authorization: Bearer cr_quorum_rt_...
Content-Type: application/json

{ "term": "net retention", "kind": "MISSING_METRIC", "context": "Asked for net retention by cohort" }

Reports a term or concept the semantic layer could not resolve. term is required, 1 to 200 characters. kind is required: UNKNOWN_TERM, AMBIGUOUS_TERM, MISSING_METRIC, or MISSING_RELATIONSHIP. context is optional text of at most 1,000 characters that explains why the term was needed; it is not stored. Other fields are rejected.

{ "gapId": "qg_3b1e...", "occurrences": 4, "inquiryOpened": true, "requestId": "req-123", "contractVersion": "1" }

Colrows keeps one gap per normalized term (lowercase, single spaces) in the organization and counts its occurrences. A reported gap opens a Needs Attention inquiry for curators at once. The guard also records candidate gaps on its own: a searchSemanticEntities call that finds nothing, or a compileSQL result of NEEDS_CLARIFICATION. A candidate gap opens an inquiry only after it occurs in 3 runs.

Output contract: ANALYST_ANSWER v1

{
  "contract": "ANALYST_ANSWER",
  "contractVersion": 1,
  "status": "ANSWERED",
  "answer": "Net revenue fell 8% week over week, mostly in EMEA.",
  "findings": [
    {
      "text": "EMEA accounts for 6.1 of the 8 points.",
      "evidence": [
        { "kind": "QUERY_EXECUTION", "executionId": "<executionId from executeSQL>" },
        { "kind": "DOCUMENT_CITATION", "documentId": "<documentId>", "versionId": "<versionId>" }
      ]
    }
  ],
  "clarification": null,
  "chart": null
}
FieldRules
contract, contractVersion"ANALYST_ANSWER" and 1.
statusANSWERED, NEEDS_CLARIFICATION, or UNABLE_TO_ANSWER.
answerText. Required when status is ANSWERED.
clarificationText. Required when status is NEEDS_CLARIFICATION.
findingsArray of at most 100. Each finding needs text, and may have evidence: an array of at most 50 items.
evidence[].kindQUERY_EXECUTION with executionId, or DOCUMENT_CITATION with documentId and versionId.
chartAn object or null.

No other top-level fields are allowed.

Quorum check rules

The check runs when the worker completes the run. Any ERROR finding fails the check, and the run ends FAILED with code QUORUM_CHECK_FAILED; the result is kept for review. WARNING findings don't fail the run. If the result fails SCHEMA, the evidence rules are not evaluated.

RuleSeverityFails when
SCHEMAERRORThe result does not match the contract above.
EXECUTION_NOT_FROM_RUNERRORA QUERY_EXECUTION references an execution that no successful executeSQL call of this run created.
EXECUTION_NOT_SUCCEEDEDERRORThe referenced execution did not succeed.
CITATION_NOT_FROM_RUNERRORA DOCUMENT_CITATION names a document version that no askDocuments answer of this run cited.
UNKNOWN_EVIDENCEERRORAn evidence item has an unknown kind.
ANSWER_WITHOUT_EVIDENCEERRORAn ANSWERED result has no finding with evidence.
FINDING_WITHOUT_EVIDENCEWARNINGA finding has no evidence.
COMPILED_QUERYWARNINGThe run called compileSQL and the evidence uses an execution that is not a verified query.

The check proves that the evidence is real and was produced by this run. It cannot prove that the answer text follows from the evidence; evaluation suites measure that.

Admin API

User session only.

The admin API under /api/quorum/v1 is for the Colrows UI and administration. It needs a signed-in user's session token; OAuth client, MCP, worker, and run tokens are refused. These routes are not agent tools and are not available on agent MCP.

Responses use the standard Colrows REST envelope, with the data in payload. Draft edits, review, and access changes accept an optional expectedRevision in the body (submission takes it as a query parameter) and fail with 409 when the record changed.

Who can do what

Quorum roleGrants
VIEWERAgents, versions, run metadata, tool-call telemetry, worker events, evaluation results, and gaps. Not run results.
AUTHORVIEWER, plus create agents, edit and submit drafts, manage evaluation suites, and start evaluations.
ADMINAUTHOR, plus review, seat, unseat, worker clients, role assignments, and every run result.

An organization administrator holds every Quorum role. Other users hold a role when their user ID, or an active user group they belong to, is assigned that role or a higher one. Roles are resolved on each request, and nobody holds one while Quorum is disabled. Separately, an agent's owner and invokers decide who can start its runs, and only the user who started a run, the user it acts for, the agent owner, or a Quorum admin can read its result.

Routes

RoutePurposeAccess
GET, PUT /settingsQuorum settings other than roles: enabled, requireEvaluation (default true), captureInputs, unseatOnBrokenScope, telemetryRetentionDays (7 to 400, default 90), defaultLimits, maximumLimitsOrganization administrator, also while Quorum is disabled
GET, PUT /rolesRole assignments: each with a role, userIds, and groupIdsADMIN
GET /audit-events?agentId=Audit events of one agent, or of the organization without agentIdVIEWER / ADMIN
GET /agents, POST /agentsList agents; create one with agentKey, displayName, optional ownerUserId (defaults to you), and invokersVIEWER / AUTHOR
GET /agents/{agentId}Agent detail with its versions and schedulesVIEWER
PUT /agents/{agentId}/accessChange the owner and invokersOwner or ADMIN
POST /agents/{agentId}/client/rotateReplace the agent's dedicated client, which ends its run tokensADMIN
POST /agents/{agentId}/versionsCreate a draft from a full spec, or fromVersion to copy an earlier specAUTHOR
GET, PUT /agents/{agentId}/versions/{version}Read a version; update a draftVIEWER / AUTHOR
POST /agents/{agentId}/versions/{version}/submitValidate and submit a draft for reviewAUTHOR
POST /agents/{agentId}/versions/{version}/reviewdecision APPROVE or REJECT, optional note (at most 2,000 characters)ADMIN
POST /agents/{agentId}/versions/{version}/seatSeat an approved versionADMIN
POST /agents/{agentId}/unseatKill switch, with a required reason (at most 2,000 characters)ADMIN
POST /agents/{agentId}/runsStart an on-demand run with { "input": {…} } (at most 32 KB). An optional Idempotency-Key header (1 to 200 characters) returns the same run for the same input; a different input with the same key gets 409. Too many runs gets 429.Owner, invoker, or ADMIN
GET /runs?agentId=, GET /runs/{runId}Run metadata, newest first, without resultsVIEWER
GET /runs/{runId}/resultThe result and its Quorum checkStarter, actor, owner, or ADMIN
POST /runs/{runId}/cancelCancel a runStarter, owner, or ADMIN
GET /runs/{runId}/tool-callsTool-call telemetry of a runVIEWER
GET /runs/{runId}/eventsEvents the worker reportedVIEWER
GET /gapsSemantic gaps, most recently seen firstVIEWER
GET, POST /evaluation-suites; GET, PUT /evaluation-suites/{suiteId}Evaluation suitesVIEWER to read, AUTHOR to write
POST /agents/{agentId}/versions/{version}/evaluationsEvaluate a version against the suites its spec namesAUTHOR
GET /agents/{agentId}/evaluations, GET /evaluations/{evaluationRunId}Evaluation runs and their scoresVIEWER
GET, POST /worker-clientsList worker clients; create one with name, agentIds (agent IDs, or only "*"), and expiresAt. The secret is returned only in the create response.ADMIN
POST /worker-clients/{clientId}/revokeRevoke a worker clientADMIN