Skip to main content
“Nothing in black”, “not that network”, “don’t use that argument”: what one person wants, refuses and is, in a form the agent’s tools can use. The memory derives the customer’s signals from what they were shown, what they saw, what they engaged with, what they said they want or refuse and what they said they are, and hands them back to the tools as a constraints block, once per turn, with the context. A stated preference is a hard constraint; an inference is at most soft, and the customer can see it, correct it and delete it.

Turning it on

Signals are the space’s signals feature, off by default and turned on in the features document by the security role. With it off, an interaction item in the batch is refused, POST /v1/constraints answers 404 and a context read that asks for include: ["constraints"] gets the context without the block. Interactions arrive inside the turn record, when the space records turns, or as batch items when it does not.

Interactions

An interaction is what the person was shown or did, in eight kinds: The exposure has a canonical moment: delivered_at, when the list reached the customer, never when a tool returned the results or the model named them. It gets an id (a UUIDv7 the SDK mints at delivery) and each item has a position in the whole list; “see more” is the same list on the next page. An item counts as exposed when its position is among the visible ones, or up to the highest max_index_seen of a seen of the same exposure. method says how Niadra knew: bridge from the interface bridge, otel from a niadra.exposure span, or tool_result, inferred from what a tool returned, which counts more than the person saw and never serves attribution by line. shown keeps the values the item displayed for the fields its type tracks or lets be claimed, and it is against them that the state read later says what changed since the person saw it. Purchases, deliveries, returns and “kept it” are not interactions: they come from the lifecycle outcomes of the objects that record them, never from what an agent says. A preference is never inferred: source is stated (the person said it), tool_args (the SDK captured it from the arguments of a tool call) or correction (the person corrected an inference). Niadra never derives affinity or interest from an interaction with an object whose type or field is sensitive; for those it keeps only the type and a count. Raw interactions stay 45 days for measurement and then only as aggregates.

The constraints block

The block reaches the agent once per turn, with the context (include: ["constraints"]), or through POST /v1/constraints, for one customer and, optionally, rendered for one tool. It has a version (cv_ and a digest of the content, which the turn record cites) and: The block passes the verification gate of the pack of the same read: when the pack or the live turns withheld something until identity is verified further, the block says nothing of the customer. POST /v1/constraints, on its own, is a program read with its own receipt, and is not gated. What may enter: a hard constraint comes only from what the person said; an inference never becomes hard, and a negative inferred from behavior is at most soft. An attribute the person did not say (acquired, kept, returned_for_size, inferred) applies only when_asked: an inferred size applies only when the person asks for “my size”. The current utterance wins: when a hard constraint said now clashes with one said before, the block keeps the new one and reports the pair in conflicts; between a stated and an inferred one, the stated one stays. Both entries of a conflict stay in the block, so the turn record can cite either, and kept says which one stands. A clash between two company rules is kept by rank and returned to the company as a data issue. A session constraint lasts at most 24 hours, and a turn constraint never reaches the server.

Tool bindings

A tool’s bindings live in the space’s tool-bindings configuration document, of the integration role: one per tool and source (an empty sources applies to every source), with args, which argument carries which field (attr as type.field, param, transform, negation.param, ops); results, where the result carries objects of a type (path, type, namespace, id) and which key of each item holds which field (fields); and capabilities: overfetch (the tool returns more than asked, and the SDK filters the residual), relax_flag (where the result says the tool relaxed what it was asked), dry_run_param (the argument that makes a call change nothing, for the counterfactual) and mask_output (the SDK masks the fields the key may not read in the output). GET /v1/sdk/profile serves in tool_bindings the bindings of the calling source, without sources. The SDK uses them for a tool without a binding in code: it measures the block through it, runs the counterfactual through it and, when the code leaves mask_output unset, follows capabilities.mask_output; a binding written in code wins over the served one. POST /v1/constraints with tool answers the block already rendered for that tool in rendered, in advisory mode, through the calling source’s binding, without changing version; a tool the space does not bind for that source answers 422.

Rendering for one tool

The SDK renders the block for each tool through its bindings: which argument carries which attribute (attr, param, transform, negation.param, ops) and whether the tool overfetches. in and eq go to the parameter, not_in and ne to the negation parameter, comparisons only when ops lists them; a constraint no argument expresses is residual, filtered from the results by the SDK when the tool overfetches, and unenforced when it does not, which the rendering says. In advisory mode (the default), the call goes as it is and the SDK returns the suggestions; in apply mode, it adds a suggested parameter only when the call left it out and everything behind it may be injected (a stated hard constraint, of turn or session scope, with no conflict; a stated attribute), never overrides an argument the call set, never injects an inferred size or a persistent constraint. When the call sets a constraint’s own argument to a value the constraint refuses, the call’s value stands, and the pair is reported as a conflict. Sent is not applied: a tool may relax a filter on its own. After the call, the SDK reads the results and counts, over the hard constraints sent, results_checked, violations, unverifiable (the results that break none but lack the field of one) and relaxed. The measure is “sent, verifiable, violated”, never only “sent”; the counts go to the turn record and to context use (constraints, per source and agent). A tool decorated with @Niadra.tool(..., binding=...) in Python, or niadra.tool(name, fn, { binding }) in TypeScript, does that measurement itself, and one without a binding in code does it through the binding served in the profile; niadra.constraints.render in Python and renderConstraints() and honoredConstraints() in TypeScript expose the rendering and the count. The example of a bound tool’s counterfactual is in examples/tool_counterfactual.py and examples/tool-counterfactual.ts.

Beneficiaries and gifts

Every interaction carries for: self (the default), beneficiary:<id> (a person under the customer’s profile, such as a dependent) or gift. Signals are kept apart per for: what a person buys for a dependent never becomes their own taste, and a beneficiary’s block holds that beneficiary’s entries, because the size of whoever will wear it decides. A gift goes to a disposable bucket and never becomes an attribute of the customer. A delivery to another address is never taken as a beneficiary by itself: the block asks for_whom in ask. See Identity and verification.

Inferences and the reviewable profile

An inference is one thing the signals infer about the customer, with what it rests on: an affinity (soft_constraint when the block already carries it as a soft entry, affinity when not yet), a size inferred from outcomes (attribute) or an interest in an object (interest). The evidence is in counts: effective exposure (weighted by position, 1 / log2(pos + 1)), weighted signal, events, sessions and lift; an item shown often and never chosen becomes an implicit negative, soft. Derivation runs when the oldest pending item reaches 5 minutes or when the session has been quiet for 1 minute; a nightly pass adjusts the priors with what the space saw in 90 days, only from values seen by at least 10 people. What the customer said is not an inference and is not listed here. The customer can see, correct and delete each inference, through your team: GET /v1/profiles/{profile_id}/inferences lists each one with its origin, evidence, confidence, purposes of use and date; /correct replaces the inference with what the customer says, as a stated preference or attribute, and deletes it; DELETE leaves a tombstone, and the same evidence never infers it again. An objection to profiling (POST /v1/profiles/{profile_id}/traits/opt-out) stops derivation. In the Console, the profile’s inferences tab shows all of this, for the security role. A review request (POST /v1/profiles/{profile_id}/review-requests) takes to the controller’s data protection officer a subject’s request to review an automated decision, with the explanation built from receipts: their ids, kinds, surfaces, rules, versions and inputs, by reference, never personal content. The controller’s decision is recorded once (/resolve), and the review_request.created and review_request.resolved webhooks tell. Requests stay at least 395 days after resolution, as a legal obligation, and an erasure of the subject counts them in retained. See Privacy.

Experiments by element

The space can measure what each element of the block does: an experiment in the measurement document drops, per arm, the constraints block, the inferred soft signals, the inferred sizes or the state block, and compares what people did afterwards. Critical sources never go to a control arm, and the memory’s control group gets no block at all. Before an experiment, the tool counterfactual says whether the element changes what the tool returns, from recorded turns. See Outcomes and attribution.

Next steps

Object types and state

the fields and attribute families a preference names.

Outcomes and attribution

from the exposure to the order, through the exposure token.

Constraints block

the reference of POST /v1/constraints.

Retail agents

lists, sizes and “nothing in black” in a store.