Who gets the question
One check opens one question, for every rule that asked. Each of those rules’ owners can answer it, the question shows all of them, and the first answer wins: the others then see it decided (who answered, and how), and a late answer gets409.
When the ask comes from an agent acting for a person (the key is theirs, see Evidence)
and that person may answer it themselves (they own the rule or are a workspace admin), the
question goes to them first (
routed: "on_behalf_of"); the rule’s owner can still answer it.
The question shows on Home (/app) as a card: what the agent wants to do, in plain words; the rule and the
owner’s own words; who asked; Approve / Deny and an optional reason.
Who may answer, and the authority recorded with the answer:
Anyone else gets
403. Agent keys can’t answer their own asks.
The approval record
Every answer, and every question nobody answered in time, is stored against the check:
The check row also stores the deciding rule’s owner and the map version at check time. The check log shows the
answer under Answer. Each person’s answer is also kept as a precedent on the rule (action, decision, reason,
owner), one per rule that asked; three consistent answers on one kind of action propose a refined rule
(rule versions).
Waiting for the answer
A/v1/check answer with result: "ask" carries:
GET <decision_url>?wait=25&payload_sha256=<hash> holds up to 30 seconds and answers status (open, approve,
deny, timeout), the approver, the reason, and proceed: true only for an approval of that exact payload. Send
ask_wait (seconds) with the check to say how long you’ll wait; the question times out then (default one hour).
- MCP guard: waits by itself (
--ask-wait, default 120 s) and forwards the call only on a matching approval. See Stop, ask, nudge. - MCP server:
check_actionreturnscheck_id; pollget_decision. - Coding-agent hooks (Claude Code, Codex, Cursor): wait by themselves (
--ask-wait, default 120 s, with the host’s hook timeout set to match) and let the call run only on a matching approval. See Use Claros from a coding agent. - Browser hooks (
getclaros-browserfor Playwright,claros-browserfor browser-use): the held click waits (askWait/ask_wait, default 120 s; 0 = stop at once) and is replayed only on an approval of the hash Claros took of that action; otherwise it is a stop.
Where asks reach you
Each person who may answer a question gets it where they already are, as one readable line, and answers with one tap, without opening the app or signing in on their phone.
Each person can pick their own under Where asks reach you on Home (
PUT /api/notify/me {"channel": "notch" | "slack" | "email" | null}); a choice that isn’t available falls back to the default. A Slack DM that can’t be
delivered (the person isn’t in that Slack) falls back to email. The workspace webhook below still gets every question.
One-tap links. Every button is a link made for one person and one question: signed by the server, bound to that
person, the question and the exact action (payload_sha256), and valid until the question times out. It opens a small
page with the ask and two buttons (Approve / Deny, or the question’s own labels such as Fine / Not
fine). Opening the link changes nothing (mail scanners open links); the tap does. The answer is recorded as that
person, with the authority they have at that moment (someone who left the workspace can’t answer). A link
answers once: after any answer, every link to that question says who decided it. In Slack the DM is edited to say how
it ended (“Approved by …”).
Email is sent at most once per person every 10 minutes (CLAROS_EMAIL_EVERY): anything else that comes up in
that window arrives together in the next message. Each person can set quiet hours ("quiet": {"from": "22:00", "to": "07:00", "tz": "Europe/Berlin"}); email waits until they end. Slack’s own do-not-disturb silences DMs.
Escalation. A question still open 15 minutes after it was asked also goes to the admins (rule owner
→ admin), on their own channels and on their Home. An admin changes the delay with
PUT /api/notify/settings {"escalate_minutes": 5}. Escalation never extends the agent’s wait: the question still
times out at expires_at.
Setting it up (server): an email provider, either RESEND_API_KEY or CLAROS_SMTP_URL
(smtp[s]://user:pass@host:port), plus CLAROS_EMAIL_FROM (a sender on a domain your provider verified). Without
them email is off and GET /api/notify/settings says what’s missing. Slack DMs need the Slack connector’s bot token
to have chat:write and users:read.email; no interactivity URL is needed.
Webhook
An optional webhook per workspace. An admin sets it under Notifications on Home (orPUT /api/questions/webhook {"url": "…"}), sends a test message (POST /api/questions/webhook/test) and sees how
the last delivery went (GET /api/questions/webhook → {set, host, last: {ok, status | error, at}}). Each new
question is POSTed as Slack-compatible JSON ({"text": …}). The URL is never read back, only its host.
The server only posts to public addresses, checked when the URL is saved and again on every send (so a name that is
later pointed inside your network is caught):
httpsonly (CLAROS_ALLOW_HTTP_WEBHOOK=1allowshttp, for local development);- every address the host resolves to must be public: no private, loopback, link-local (cloud metadata such as
169.254.169.254), CGNAT (100.64.0.0/10), multicast, reserved or unspecified address, IPv4 or IPv6, including IPv6 forms that carry an IPv4 address; - the request goes to the address that was checked (TLS still verifies the hostname), and redirects are not followed (a redirect counts as a failed delivery).
Proposals
GET /api/proposals lists rule refinements drafted from answers, and rules answered both ways. Admins see
all of them; a rule’s owner sees their own rules’; other members see none.
Answers become precedents
- Never asked twice. An ask whose exact action (same payload hash) someone authorised already approved or
denied, under the same rule versions, is answered from that decision: no second question. The answer carries
from_precedent(who decided, when, how) and is logged like any other check. A rule change expires it; a workspace can also set a time limit (PUT /api/workspace/learning {"repeat_ttl_hours": …}). - Past decisions inform new checks. For each rule it reads, the judge sees up to 3 of that rule’s most similar decided cases in the workspace (owners’ answers and override reports), redacted with the record.
- Consistent answers propose a rule version. Three answers the same way propose a refined rule that starts in watch (Rule versions).