Skip to content

Documentation

Finding the change somebody is asking about.

An investigation starts with a sentence, not a filter. These four turn one into the other — and then let you keep the filter.

Asking in plain language

"Who changed prices last week?" is a question; resourceType=catalog.price&from=… is a filter. The assistant turns the first into the second and then gets out of the way: what you read is the events themselves, with their ids on them, fetched by the same tenant-scoped query builder the rest of the dashboard uses.

The distinction matters more than it sounds. A model that summarises your audit trail is a model that can be wrong about your audit trail, and a plausible summary of events that did not happen is worse than no search at all. So the model never sees a record, never writes SQL, never picks a tenant and cannot write anything. It chooses a filter, and the filter is shown to you before the results are.
It produces a filter, not an answerThe output is the same shape the search form produces. Anything the model cannot express as a filter, it declines rather than approximates.
Your scope still appliesThe filter is run through the same tenant and account limits as a typed search. A narrowed account cannot ask its way past its own boundary.
Reads are recorded like any otherA search the assistant ran is a search, and it appears in Who read the trail with the filter it used.
It is optionalNo key configured means no assistant, and every other part of the dashboard works exactly as it did. The weekly digest beside it is plain arithmetic over your own last four weeks, so it needs no model at all.
The model can be one of yoursSet Assistant__Provider to OpenAiCompatible and Assistant__Endpoint to any endpoint speaking that shape — Ollama, vLLM, LM Studio, Azure OpenAI, OpenAI itself. A deployment that may not send a question to a third party can run the question box against a model on its own network instead of doing without one.

Configure a provider key in Assistant__ApiKey to turn it on. Your events are not used for training and the prompt carries your question and the list of resource types in your tenant — never the records themselves. With Provider set to OpenAiCompatible the endpoint may name only a base URL; /chat/completions is appended for you.

Check it before anybody uses it

A question box that cannot reach its model says "the assistant could not be reached" and nothing more — it has to, because naming the reason on the page would hand your configuration to whoever typed the question. That leaves four failures looking identical: a revoked key, a model name the provider has retired, an endpoint in the other shape, an organisation key with no workspace. reldavi assistant-check is where the reason lives. It asks five questions — two ordinary, one Turkish, two trying to talk the model out of its job — prints the filter chosen for each, and on a failure prints the endpoint’s own words verbatim.

reldavi assistant-check --key "$RELDAVI_ASSISTANT_KEY" --provider openai --model gpt-4o

It exits non-zero only when the model could not be reached. A refusal is not a failure: a model declining to turn "show me everything" into a filter has done the thing it is there for. Add --clickhouse and --tenant to ask against your own resource types rather than the built-in sample.

Searching inside a change

Every filter in every audit product narrows by metadata: which record, which person, which day. None of them let you ask about the change itself — and the change is where the questions start. "A discount went above forty per cent." "A status moved from approved back to draft." "Somebody's salary field was touched at all."

Those are the sentences an investigation begins with, and today they end at a spreadsheet export and somebody's afternoon. Reldavi stores the change set as JSON on the row, so a condition on a named property is one predicate in the same query as everything else.
Look inside the change Look inside the change Property Discount Comparison is more than Side the new value Value 40 3 records Order A-1042 Discount 10 45 Order A-1180 Discount 0 60 Order A-1204 Discount 15 55 Every other filter asks which record. This one asks what happened to it.
A property, a side, a comparisonFive operators: changed at all, is exactly, contains, is more than, is less than. Against the new value or the old one. Deliberately a small vocabulary rather than an expression language — anything richer is a query language reaching into customer data, which is a surface to build once there is a reason.
Two conditions make a transitionOld value is "Approved" and new value is "Draft" is the question "what got reverted", and the query API takes a list so both run at once.
Numbers stay numbersA comparison reads the value as one, and a property holding "pending" is not a property holding zero — it simply does not match, rather than being counted as smaller than every threshold.
A comparison that cannot run is droppedAsk for "more than quite a lot" and the condition is left out rather than emitted as something that silently matches everything. The results are the results of the conditions that could actually run.
The property name never reaches the statementIt arrives from a form and is passed as a parameter, which is the one injection point a query builder with no string concatenation could still have had.

It is the most expensive condition here, because it reads a JSON column rather than an indexed one — so narrow by date or resource type alongside it where you can. The interface says so too, on the filter itself, rather than letting somebody discover it on a slow page.

Alerting on a search

An audit log somebody has to remember to read is an audit log nobody reads. Any filter you can search with becomes a standing rule: it runs every few minutes and sends what it matched to the same channels the anomaly digest uses.

Somebody was made an administratoriam.user · updated — the change most likely to matter and least likely to be noticed.
A service account touched payrollhr.payslip · by svc-payroll — automation doing something nobody asked it to.
Anything was deleted from billingbilling.invoice · deleted — the events an auditor asks about first.
This is what turns a record into a control. A rule that fires is something a SOC 2 auditor will accept as a detective control, which means it appears in your compliance documentation rather than only in ours.
A rule has to narrow to somethingOne with no condition matches every event you record. The form refuses it, which is better than discovering it at three in the morning.
The window is "since last time"Not "the last five minutes". A sweep that runs late, or a process that restarts, must not step over the events in between.
Late arrivals are waited forEvents are buffered and batched, so a window ends a few minutes behind the clock. Running right up to now would skip events that had not landed yet.
You are not told the same thing twiceAn identical alert is not resent. One that repeats every sweep until somebody acts is one people mute, and a muted channel is worse than none.

A rule is usually a search you have just run. Narrow the audit trail until it shows the thing you care about, then use Alert me about this — the filter carries over and you name it. The dates do not carry over: a search is about a window, a rule is about a kind of event and runs forever.

The rule’s name comes first in the payload, because the person it reaches is reading a phone and needs to know what happened before they need to know which records. The shape is flat JSON rather than any vendor’s message format, so a three-line workflow can turn it into whatever Slack, Teams or PagerDuty wants.

{
  "kind": "alert-rule",
  "rule": "Somebody was made an administrator",
  "matchCount": 1,
  "windowFrom": "2026-09-20T09:00:00Z",
  "windowTo":   "2026-09-20T09:05:00Z",
  "matches": [{
    "occurredAt":       "2026-09-20T09:02:11Z",
    "action":           "updated",
    "resourceType":     "iam.user",
    "resourceId":       "USR-9",
    "actorDisplayName": "Ayşe Yıldırım"
  }]
}

Asking for an alert

The assistant turns a question into a filter and runs it once. The same filter can become a standing rule — the thing you actually wanted when you asked. "Tell me if anyone outside finance changes an invoice over fifty thousand" is a sentence; what watches for it from then on is a filter.

This is the most defensible thing a language model does in this product, and the reason is the sequence. The model runs once, at design time. It proposes a filter, you read the filter, you sign it off. From that moment the thing that fires is a filter and no model is consulted again — not on the next batch, not in six months, not at three in the morning.
From a sentence to a standing rule From a sentence to a standing rule What somebody asked no model
“Tell me if anyone outside finance changes an invoice.”
What the model proposed model, once resource type billing.invoice action updated actor not in finance dates dropped You check these before saving. You are the one signing them off. What runs from now on no model
A filter, evaluated on every batch. Fires into Slack, Teams or your own endpoint.
The model runs at design time, not at decision time. An alert you cannot defend is an alert you will mute.
You confirm the filter, not the sentenceThe compiled fields are shown before anything is saved, and they are editable. What somebody meant is not enforceable; what the filter names is.
The dates do not carryA search is about a window; a rule is about a kind of event and runs forever. Carrying "last Tuesday" into something permanent is how an alert quietly stops matching anything.
The rule remembers the sentenceSix months later somebody asks why this alert exists. "Because on 20 September somebody asked to be told if anyone outside finance changed an invoice" is an answer. A set of filter fields is not.
A rule that narrows nothing is refusedIt would match every event you record. That is the difference between a form that says no and a pager at three in the morning.

And if you have no model key configured, none of this is missing — the rule form is there, and Alert me about this on any search fills it in the same way. The assistant is a faster way to reach the same form, not a second mechanism.

What to read next

Stuck on something?

The schema, the SDK sources and the full architecture notes are open. If something is unclear, that is a bug in our documentation. Get in touch.