Review
A Brain has one of four admission levels. The level is an owner decision: the
Brain’s policy decides each write, never the caller. Human writes land, with
one exception: a source snapshot that would change a
value a person or another source supports waits in Review below L4, whoever
sent it. The ladder governs agent writes. A write made with a management key
(nsk_) is a person’s: it acts as the person who minted the key, and is
recorded against the key’s id so it stays distinguishable from that person’s
console writes.
| Level | Name | Agent writes that land automatically |
|---|---|---|
| L1 | Human approval only | None; every agent write stages for review |
| L2 | Auto-approve trivial | No-op and formatting-only edits |
| L3 | Auto-approve new and non-conflicting | Writes admitted by the consequence rules (the default) |
| L4 | Auto-approve all | Everything, including destructive deletes |
The API keeps the historical mode literals for compatibility: reviewed is
L1 and open is L3. The new literals are trivial (L2) and unrestricted
(L4). Legacy propose write-policy values resolve to L1 and direct values
resolve to L3; responses use the level’s canonical literal. mode is a
property of the Brain, never of the
caller.
rcp_019fWhat the read consumedcurl -X PATCH "$API/v1/brains/tenant:acme" \ -H "authorization: Bearer $NICIA_KEY" \ -H "content-type: application/json" \ -d '{ "mode": "reviewed" }'Keyed API-created Brains default to L3 (open), so governed routine writes do
not require a person on every change. Console-created Brains default to L1
(reviewed), so new knowledge appears in Review while trust is being built.
Choose a looser or tighter level as the Brain’s role changes.
What changes
Section titled “What changes”Nothing about how you write. The same call returns a different status:
{ "data": { "records": [{ "id": "acme", "kind": "company" }], "change": { "id": "chg_44a1e0", "status": "staged", "createdAt": "2026-08-14T10:59:41Z" } }}Branch on change.status, always:
| Status | Meaning |
|---|---|
applied |
It landed. Readable now. |
staged |
Waiting for a decision. Not readable yet. |
rejected |
The Brain declined it. The request was fine; the content was not. |
A rejection is a 200. The request was well-formed and the Brain made a
decision — that is not an HTTP error.
A write only ever returns one of those three. A change you read back from
GET /changes can also carry superseded (a selection, below, replaced it with
a successor, or its target moved on before it was reviewed). The ?status=
filter on /changes still only selects the three admission outcomes above.
Reads tell you the same thing from the other side: every /context response
carries staged, so an empty answer distinguishes “nothing known” from “waiting
on you.”
Settling the queue
Section titled “Settling the queue”curl "$API/v1/brains/tenant:acme/changes?status=staged" \ -H "authorization: Bearer $NICIA_KEY"{ "data": { "changes": [ { "id": "chg_44a1e0", "status": "staged", "author": { "kind": "key", "id": "key_7f3a1c", "name": "support-agent" }, "records": [ { "id": "bob-smith", "kind": "person", "before": { "role": "Head of Platform" }, "after": { "role": "VP Platform" }, "evidence": [ { "source": "zendesk:ticket:8821", "quote": "Bob Smith, now VP Platform, confirmed…" } ] } ], "createdAt": "2026-08-14T10:55:12Z" } ], "nextCursor": "eyJrIjoi…" }}Every staged change carries the diff and the passage it came from, so a reviewer
is deciding about evidence rather than about a model’s assertion. A source
snapshot’s change also lists the documents it serves
or retires ({ source, id, action }), apart from its records.
Deciding a change is two steps on the lifecycle verbs. Approve binds the
exact candidate you reviewed; publish applies it. Both need the review
capability. Approve’s body may be omitted, except for a change that carries a
standing answer: see below.
curl -X POST "$API/v1/brains/tenant:acme/changes/chg_44a1e0/lifecycle/approve" \ -H "authorization: Bearer $NICIA_KEY"
curl -X POST "$API/v1/brains/tenant:acme/changes/chg_44a1e0/lifecycle/publish" \ -H "authorization: Bearer $NICIA_KEY" \ -H "content-type: application/json" \ -H "idempotency-key: publish-chg_44a1e0" \ -d '{}'Publishing again under the same Idempotency-Key returns the committed outcome
instead of applying twice.
Standing answers and the regression check
Section titled “Standing answers and the regression check”A change that proposes a standing answer is approved normally only when its
regression check passed for that exact revision. Its outcome is on the change
(evaluationState), and an edit after the check makes a pass stale. Any other
finished outcome needs a recorded reason, which is kept with the reviewer:
curl -X POST "$API/v1/brains/tenant:acme/changes/chg_44a1e0/lifecycle/approve" \ -H "authorization: Bearer $NICIA_KEY" \ -H "content-type: application/json" \ -d '{"evaluationOverrideReason": "The judge misread the refund window; checked by hand."}'Without one, approve answers 400 validation. A check still running answers
409 conflict and cannot be overridden. An answer published over an override
is never reported verified; its verification keeps the outcome and your
reason. This gate governs Review only. Whether an agent’s write enters Review at
all is the Brain’s admission level (above), and an answer
an agent writes directly is served as not_evaluated.
Publishing never overwrites a newer write. Every record a change was staged
against is re-checked when it publishes, and if any of them was edited, created
or deleted since staging, nothing is applied: publish answers 409 conflict
naming each record that moved, and the change’s lifecycle becomes blocked,
with blockedReason: "candidate_base_moved" and blockedRefreshAction: "restage_against_current_state". Approving it again cannot help — have the
producer write the change again against the current records, then review that
one. A blocked change can still be rejected or cancelled. Rejecting takes a reason, which is required — a
rejection is never silent — and which GET /changes?status=rejected reports back:
curl -X POST "$API/v1/brains/tenant:acme/changes/chg_44a1e0/lifecycle/reject" \ -H "authorization: Bearer $NICIA_KEY" \ -H "content-type: application/json" \ -d '{ "reason": "Title is from a signature block, not a statement." }'Approve part of a change by selecting operations. GET /changes/{changeId}/lifecycle lists the
change’s operations (one per record written or deleted, with the operations
each depends on), and
select partitions them:
curl -X POST "$API/v1/brains/tenant:acme/changes/chg_44a1e0/lifecycle/select" \ -H "authorization: Bearer $NICIA_KEY" \ -H "content-type: application/json" \ -d '{ "selectedOperationIds": ["…"], "omittedOperationIds": ["…"] }'The answer names a successorChangeId (what you selected) and a
remainderChangeId (what you set aside, still pending). The original change
is superseded and can never apply in full, so approve and publish the successor;
the remainder stays in the queue until someone decides it. Selection is per
record, not per field, and it does not edit values: when the substance is right
and the wording is not, reject with a reason and have the corrected value
written.
Undoing a change that has already landed is a console action today, not an operation on this API surface. When it runs, it either restores the content or refuses the whole revert up front — it never marks a change reverted while leaving the content in place.
Who approved what
Section titled “Who approved what”Attribution is derived from the key that made the call — never from a field somebody filled in. “Who approved this” will be answered by the Brain, not by your logging.
Reviewing without a person
Section titled “Reviewing without a person”Review is a checkpoint, not necessarily a human. A key with the review
capability can settle changes, so you can put your own evaluator in front of the
queue and only escalate what it is unsure about.
const changes = `${API}/v1/brains/tenant:acme/changes`;const auth = { authorization: `Bearer ${NICIA_KEY}` };
// One page of the queue; walk the rest with `data.nextCursor`.const { data } = await fetch(`${changes}?status=staged`, { headers: auth,}).then((response) => response.json());
for (const change of data.changes) { const verdict = await myEvaluator(change); if (verdict.confident) { const lifecycle = `${changes}/${change.id}/lifecycle`; await fetch(`${lifecycle}/approve`, { method: "POST", headers: auth }); const published = await fetch(`${lifecycle}/publish`, { method: "POST", headers: { ...auth, "content-type": "application/json", "idempotency-key": `publish-${change.id}`, }, body: "{}", }); if (!published.ok) { const { data: state } = await fetch(lifecycle, { headers: auth }).then( (response) => response.json(), ); if (state.lifecycle.blockedReason === "candidate_base_moved") { // A record it was staged against moved since staging. Re-run // whatever produced the change against the current records. await restage(change); } else { throw new Error(`publish ${change.id}: ${String(published.status)}`); } } } // leave the rest for a person}The Brain decides, not the caller
Section titled “The Brain decides, not the caller”mode sets the policy’s default outcome — it is not a per-write switch. A key
cannot ask for its writes to skip review, and no request body can either. The
Brain’s admission policy evaluates the write’s consequence, magnitude,
duplicates, and conflicts at the selected level.
“Wait” means the change stages until a human or a trusted service settles it;
“land” means it is admitted immediately and shows up in /changes. L1 keeps
all agent output in Review. L2 admits only trivial changes. L3 admits routine
new, non-conflicting writes while retaining consequence gates. L4 turns those
gates off, including the destructive-write hold, so use it only for a pipeline
you explicitly trust. Human writes land at every level, except a snapshot’s
change to a value a person or another source supports.
If you want a trusted pipeline writing straight in while agents are reviewed, give them separate Brains rather than caller-specific exceptions inside one. A Brain is cheap, its handle is yours to choose, and two Brains with different levels make the trust boundary explicit.
Notifications
Section titled “Notifications”Register a webhook so a staged change reaches your queue instead of waiting to be polled:
curl -X POST "$API/v1/brains/tenant:acme/webhooks" \ -H "authorization: Bearer $NICIA_KEY" \ -H "content-type: application/json" \ -d '{ "url": "https://ops.example/nicia", "events": ["change.staged"] }'- Receipts — proving what a read was shown.
- Handing out keys — who gets to write without review.