Skip to content
Beta. This surface may change before GA; breaking changes are dated in the changelog.

Ship a context layer in an afternoon

This page follows one fictional company all the way through, because the shape of the problem matters more than any single endpoint. If you would rather run the calls than read the argument, Build an agent company on Nicia is the same journey as a worked tutorial, with the limits named.

Cadence sells an AI customer-success copilot to B2B SaaS teams. Every one of their customers has their own accounts, contacts, renewals and support history, and the copilot is only as good as what it knows about that customer. Cadence already built the obvious thing: a records table in Postgres, a RAG index over uploaded documents, and a prompt assembler that stuffs both into a context window.

It works. It is also the part of the product they cannot staff. Every new customer needs isolation they hand-roll, every answer needs provenance they do not have, and every change an agent makes lands in production with nobody able to say what changed or put it back.

They do not need Nicia to replace their agent. They need it to be the layer underneath it.

Provisioning is a pure function of Cadence’s own tenant id. There is no id to store and no create-versus-update decision to make.

Terminal window
curl -X PUT "$API/v1/brains/tenant:acme" \
-H "authorization: Bearer $NICIA_KEY" \
-H "content-type: application/json" \
-d '{ "name": "Acme", "schema": "nicia-core" }'

Call it on every request if you like — the same handle always resolves to the same Brain, 201 the first time and 200 after. Cadence’s provisioning code is one line in the signup path, and their reconciliation job is deleted.

The second call puts something in it:

Terminal window
curl -X PATCH "$API/v1/brains/tenant:acme/records/bob-smith" \
-H "authorization: Bearer $NICIA_KEY" \
-H "content-type: application/json" \
-d '{
"kind": "person",
"fields": { "name": "Bob Smith", "role": "VP Platform" },
"links": { "works_at": ["acme"] }
}'

That is the whole on-ramp. A Brain exists, it holds a typed record, and the copilot can ask it a question.

Terminal window
curl -X POST "$API/v1/brains/tenant:acme/context" \
-H "authorization: Bearer $NICIA_KEY" \
-H "content-type: application/json" \
-d '{ "prompt": "What should I know before the renewal call?", "maxTokens": 2000 }'

The answer comes back as assembled text plus citations, and every citation names the evidence it came from. Cadence’s prompt assembler goes away. So does the argument about whether the copilot made something up, because the receipt says what it was shown.

Cadence’s data is the common shape: structured records with unstructured components. They hold the records, and they hold the documents those records were derived from. Both belong in the Brain, and the relationship between them is the point.

Send the documents your records came from:

Terminal window
curl -X POST "$API/v1/brains/tenant:acme/documents" \
-H "authorization: Bearer $NICIA_KEY" \
-H "content-type: application/json" \
-d '{
"documents": [
{ "id": "zendesk:ticket:8821",
"title": "Okta redirect loop",
"text": "Customer reports a redirect loop after switching to Okta. Resolved same day." }
]
}'

Then write records that cite them. A record that names its source is a record whose Derivation panel can answer “why does the system believe this” — in the workspace and through the API. That is the thing Cadence could not build and could not skip.

Migrating a customer is a loop over records, not a project. Writes are idempotent by address, so a half-finished migration is resumed by running it again.

Cadence’s customers all have the same shape, because Cadence decided the shape. Register that vocabulary once, then stamp it on every Brain:

Terminal window
curl -X PUT "$API/v1/brains/tenant:acme" \
-H "authorization: Bearer $NICIA_KEY" \
-H "content-type: application/json" \
-d '{ "name": "Acme", "schema": "cadence-customer" }'

An organization’s own registered schema is nameable by slug exactly like a built-in. See How schemas work for registering one, and Custom schemas for extending it per customer when one of them needs a field the others do not.

Two reads keep a fleet honest as that vocabulary moves:

  • GET /v1/schemas/{slug}/brains — which Brains are on this schema.
  • GET /v1/brains/{handle}/schema-lineage — which baseline this Brain forked from, and whether that baseline has moved since. unrecorded means no lineage was recorded; it does not mean no drift.

Both take an organization key rather than a Brain key, because both answer questions about the fleet rather than about one customer.

Cadence’s copilot runs per customer, and a credential scoped to one Brain is the difference between a bug and an incident. Mint a Brain key per customer and hand it to the agent process that serves them — see Handing out keys.

  • The prompt assembler, replaced by /context.
  • The per-customer isolation code, replaced by one Brain per tenant id.
  • The reconciliation job, because provisioning is idempotent on their own id.
  • The “what did the agent change” question, answered by Review and by change history.

What they kept is the part that is actually their product: the copilot.