Decisions, rules and beliefs
Most of what a team knows is not a fact about a customer. It is a choice somebody made, a rule that follows from it, and a belief both lean on. Those live in docs, chats and heads, and they go stale without anyone noticing.
Every Brain on the default nicia-base vocabulary already declares kinds for
them. Your agents write them from the material the team has, the Brain keeps
track of what each one rests on, and when something underneath moves, every
record that depended on it says so on its next read, to every agent that reads
it. Nothing here needs a schema of your own. A Brain created with
nicia-core or a custom schema chose its own
vocabulary and does not get these kinds.
The kinds
Section titled “The kinds”| Kind | Holds | status values |
|---|---|---|
decision |
Something that was decided, and by whom | active, superseded, withdrawn |
rule |
How the team works because of a decision | active, superseded, withdrawn |
hypothesis |
A belief a decision or rule assumes; read it as “belief” | open, supported, refuted, abandoned |
All three also declare area and owner (strings). A decision adds origin
(deliberate, correction or recurring), decided_on (a date) and
decided_by, written as person:<slug>, model:<provider>/<model>@<version>
or system:<name>; a plain name such as Maya Okafor is written as
person:maya-okafor and reported as decided_by_normalized. A rule adds applies_to (a list), effective_on and
review_by. A hypothesis adds review_by. A review_by date raises a
date.reached event two weeks before it, so a rule can ask to be looked at on
a schedule. The prose of a record is its body, and its display name is its
label: name a record for what it says, never for the role of whoever owns it.
None of the three declares consequence. Agent writes to them land unless they
would change a value a person set, like any other record; a team that wants
every change looked at chooses the
approval-only level.
Four links relate them. A link is written in links, like any other.
| Link | From → to | Rests on it? |
|---|---|---|
based_on |
rule → decision |
yes |
exception_to |
rule → rule |
yes |
assumes |
decision or rule → hypothesis |
yes |
replaces |
decision or rule → decision or rule |
no |
“Rests on it” is the point of the first three: the record at the other end is a
ground of this one. A citation is a ground as well, so a record’s grounds
are its citations and its outgoing based_on, exception_to and assumes
links. replaces records a reversal and is not a ground. A link to a record
the Brain does not hold yet is a 400, so write the target first or send both
in one POST /batch.
When something underneath moves
Section titled “When something underneath moves”Validity is worked out when a record is read and never stored. Every decision,
rule and belief is served with validity:
current: nothing the Brain monitors for this record has moved since it was last stood behind. A record that rests on nothing iscurrentwith nothing monitored.needs_review: something it rests on moved.reasonssays what.monitoring_incomplete: the Brain cannot say. The reason names why, for example that a cited external source has gone unchecked for 30 days, or that the walk below hit a bound.
What counts as a move is defined by the kind. Changing a record’s body, a
citation, or one of its grounds links always counts, and so does deleting it.
Of the fields, only the ones the kind monitors count: for these kinds,
status changing into superseded or withdrawn (refuted or abandoned
for a belief), and applies_to on a rule. Editing owner or area reopens
nothing.
Reopening is transitive, to three links. A rule based on a decision that assumes a belief reopens when the belief is refuted. A reason says how far the change travelled:
input_changedwhen the record it rests on changed itself;upstream_changedwhen something further along the chain did.
reasons lists every move since the record was last stood behind, so a record
that was reopened twice carries both: match a reason by its recordId rather
than by position. Each carries path, the links from this record to what moved, and change,
saying what moved: a field (with the value it changed to, for an enum), the
body, a pin, a link, or a deleted record. A chain deeper than three
links, or with more than 200 records behind it, reads monitoring_incomplete
naming the bound rather than current. /context serves a reopened record
with its text and at most three reasons, plus reasonsOmitted;
GET /records/{id} and POST /query return all of them.
{ "validity": { "state": "needs_review", "reasons": [ { "kind": "upstream_changed", "recordId": "buyers-prefer-annual", "path": [ { "edge": "based_on", "recordId": "annual-billing-only" }, { "edge": "assumes", "recordId": "buyers-prefer-annual" } ], "change": { "kind": "field", "field": "status", "to": "refuted" } } ] }}current is not a claim about the world. It says the monitored scope did not
move, and validity.monitored and validity.observedThrough say what that
scope was and up to where in the Brain’s log it was checked.
Reversing a decision is one change
Section titled “Reversing a decision is one change”A decision that is reversed is a new decision linked to the old one with
replaces, and the old one is marked superseded. Fixing what was recorded, a
typo or a wrong fact, is an ordinary update in place. The difference matters
because an in-place rewrite still reopens what rests on the record, but loses
the fact that one decision replaced another.
The Brain refuses a half-written reversal. A batch that adds replaces to an
active record without setting that record’s status to superseded is
rejected, and the reason names the write that is missing. Send both together:
curl -X POST "$API/v1/brains/tenant:acme/batch" \ -H "authorization: Bearer $NICIA_KEY" \ -H "content-type: application/json" \ -d '{ "records": [ { "id": "annual-and-monthly-billing", "kind": "decision", "label": "Bill annually and monthly", "fields": { "status": "active", "origin": "deliberate", "area": "pricing" }, "links": { "replaces": ["annual-billing-only"] } }, { "id": "annual-billing-only", "kind": "decision", "fields": { "status": "superseded" } } ] }'A target that is already superseded or withdrawn is accepted. When a
proposal waits in Review, the same rule applies at
publication: a selection that would publish replaces without the target’s
status change is refused with the same reason. Statements
keep their own model, where an accepted supersedes_statement link makes the
older one superseded; they do not use replaces.
The next read of every rule that rests on the old decision says
needs_review, with the reason above.
Still holds, and confirmed
Section titled “Still holds, and confirmed”A reopened record is not wrong, only unchecked. A person looks at what moved
and either revises it, withdraws it, or says it still holds, with
POST /v1/brains/{handle}/records/{recordId}/affirm. Send the
observedThrough position of the validity you read, and optionally ifMatch
and a note of 500 characters or fewer. An affirmation:
- is a statement about the record’s current version and changes no value. It is not support for what the record says, needs no evidence, and may rest on knowledge from outside the Brain;
- clears
needs_reviewuntil something it rests on moves again, and puts nothing on/events; - is
409with the record’s currentvalidityinerror.detailsif a move landed after the position you read, or a cited document was served at a new version since; - is
403for a key held by an agent or a service and for an OAuth agent session. Only a person with thereviewcapability can say it, and it is not offered over MCP. A management key (nsk_) acts as the person who minted it, so a service that holds one can send it on that person’s behalf.
Served decisions, rules and beliefs also carry confirmed: true when a person
stands behind this version, by affirming it, approving it in Review, or
creating it, and nothing it says has changed since. Any later change to its
body, a grounds link or a grounds field drops it, a person’s change included. A
label edit changes nothing. When a change dropped it, lastConfirmed names who
confirmed it last, when, and how. /context tells the reading agent to treat an
unconfirmed rule as context, not policy, because it is one an agent wrote and no
person has yet agreed with.
Cite a source where it lives
Section titled “Cite a source where it lives”Evidence on these records may point at a document in the Brain or at a source outside it. Cite in place when the source has an address your team controls (a Drive file, a Notion page, a GitHub file, a URL), and save a document to the Brain only when it has none.
{ "kind": "decision", "label": "Weekly release train", "fields": { "status": "active", "origin": "deliberate", "area": "engineering" }, "evidence": [ { "uri": "https://example.com/memos/release-train", "quote": "We ship every Thursday.", "versionToken": "rev-41" } ]}A citation belongs to the version of the record that carried it. A later write
that changes the record and does not cite the source again leaves the new
version without the citation, so send evidence again on a write that should
keep it, as the status change in a reversal does for the decision it supersedes.
The Brain keeps the quote, so a receipt shows the words that were cited. It
does not hold the source, cannot check the quote against it, and never fetches
it. Reads serve the citation with lastObserved, the last report of what the
source said.
An agent that re-reads a cited source reports what it found with
POST /v1/brains/{handle}/citations/observations, per quote it checked:
present or absent in the version it read. Only a reported absent reopens
the citing records, with support_at_risk, and it stays until a person says
the record still holds or the record is cited again with a quote that is
present in the version read. A present report refreshes lastObserved but
never clears an outstanding absence, because version tokens cannot be ordered.
A citation nobody has observed for 30 days reads monitoring_incomplete with
bound: "unobserved", naming the source. The MCP tool is
brain_report_observations.
Who was served it
Section titled “Who was served it”GET /receipts/{receiptId}/now covers every record a
receipt served that carries validity, not only standing answers. A record
whose grounds moved, or whose own cited evidence moved, after the receipt’s
read reports grounds_moved in answers[], even if a person affirmed it
afterwards, and adds affirmedSince with who and when. That is how you tell
which agents acted on a rule before it reopened.