Skip to the walkthrough
Nivara Desk

Support · answered, clarified, or handed over

It knows what it

Nivara Desk answers what its help centre covers, asks when a request is ambiguous, and hands the rest to a person with what it already found. It cannot report that it answered something it did not.

Signs in as a seeded demo admin. Nothing to create, nothing to pay for.

Turn cb765689bea1

answered

“What column headers does the contact importer expect?”

top score
2.277
margin
0.127
sensitive
0.000015
AnswerClarifyEscalate
placement answer · ruling answer

3.49s · 1206 tokens · $0.00026 modelled

Station 01 — the wire

A visitor asks, and the browser watches it happen.

The widget is one script tag on the tenant’s own site, rendered inside a shadow root so the host page’s stylesheet cannot reach in and its own cannot leak out. It mints an anonymous session judged on the page’s origin — no email, no password, no sign-up wall in front of a question.

POST /widget/turns/stream

  1. event: status{ "state": "connecting" }
  2. event: status{ "state": "working" }
  3. event: retrieval{ "top": "DOC-015#1", "score": 2.277, "margin": 0.127 }
  4. event: gate{ "placement": "answer", "ruling": "answer" }
  5. event: token"The importer looks for the required headers"
  6. event: token" email, name, and company."
  7. event: done{ "outcome": "answered", "latency_ms": 3490 }
The support widget open on a page, showing the visitor's question about contact importer column headers, the answer beneath it, and the conversation marked resolved.
The same exchange, in the widget a visitor sees.

Station 02 — the gate

Three outcomes, and it commits to one.

The gate decides on free signals first — the top retrieval score, the margin to the next chunk, whether the question is sensitive — and only spends a second model call on the genuinely uncertain few. Here is the same instrument on a question it refused.

The question

“How long is the refund window on an annual plan?”

top score
1.623
margin
0.005
sensitive
0.000753

The ruling

AnswerClarifyEscalate
placement uncertain · ruling escalate

Retrieval was not weak — it scored 1.62. But the margin to the next chunk was 0.005: nothing in the corpus distinguished itself. And the question moves money. It declined rather than reached.

Prose the model writes outside a tool call is never posted to a customer. That is the guarantee the whole layer is built around: the only path to a customer’s inbox is a tool call carrying retrieved content, so the system cannot claim an answer it did not give.

93.6%

595 of 600 cases

Correct disposition — answered, clarified or escalated as it should have been.

eval/harness_results.md

0

across the adversarial suite

Successful prompt injections.

injection/ · nivara-ai

94.2%

against a 92.9% dense baseline

Retrieval recall@1, after the hybrid path earned its place.

eval/retrieval_ablation.md

Station 03 — the hand-over

A person picks it up where it stopped.

An escalation arrives as an ordinary ticket carrying an internal note with what the model already found, so the agent starts where it stopped rather than from the top. The queue is filtered, cursor-paginated and live — and deliberately does not rearrange itself under a reader mid-scroll.

A resolved ticket in the agent dashboard: the customer's question about importer column headers, the answer posted by Automation, and the state, priority and assignee controls above it.
The widget question, as a real ticket on the staff queue. The same ticket.
The agent dashboard's ticket queue, filtered by state, priority, source and assignee, listing tickets newest first.
The queue it landed in.

Station 04 — the score

And a number the system cannot tune.

Deflection is read from the API’s own analytics, over a window whose start is a committed constant, using the API’s definition verbatim. The AI layer never holds the credential that reads it, so the thing being measured cannot quote its own score. Where the live rate and the offline one disagree, the gap is published beside them rather than subtracted.

The analytics surface: deflection, resolution and SLA-breach rates each shown above the ticket count they were computed from, with median and 90th-percentile response times, broken down by priority and source.
Read live from the deployment. These figures move with real traffic.

Isolation

A forgotten filter cannot leak another tenant’s rows.

Isolation is a property of the database rather than a discipline in application code. Every tenant-scoped table has Postgres row-level security enabled and forced, with a policy predicated on a transaction-local setting, so the rows simply are not returned.

01 of 1

  • Row-level security, forced

    Every tenant-scoped table has RLS enabled and forced. The application connects as a role that is neither superuser nor table owner, so it cannot bypass a policy even by mistake.

  • The tenant is never a client value

    It is read from the auth token or the channel context and armed transaction-locally. Nothing a caller sends can widen what a request can see.

  • 404, not 403

    A row another tenant owns returns not-found rather than forbidden, because a 403 confirms the record exists and turns the API into a probe.

  • Two credentials, not one

    The token that answers tickets and the token that reads analytics are separate grants, so the credential on the request path cannot read the numbers its own work is scored by.

  • Injection resistance by contract

    Sensitive tools expose no granted branch, so a prompt-injection has nothing to steer the system toward. Zero successful injections across the adversarial suite.

  • An audit trail with attribution

    Append-only, with the actor enforced by database trigger rather than supplied by the code path being audited. An absent actor raises rather than defaulting.

Install

One script tag, and it answers on your own site.

The widget mounts inside a shadow root, so the host page’s stylesheet cannot reach in and its own cannot leak out. Sessions are judged on the requesting origin against that tenant’s allowlist. This is the real file the demo host loads — view its source and you will find these three lines.

index.html

<script
  src="https://nivara-web-nextjs.vercel.app/widget/widget.js"
  data-tenant-id="YOUR_TENANT_ID"
  defer
></script>

  • Widget

    shadow root

    An anonymous session from a tenant's own site, origin-checked against that tenant's allowlist.

  • Slack

    Events API

    A message in a connected channel opens a ticket, and the reply comes back to the thread it started in.

  • REST + OpenAPI

    generated types

    The whole surface is documented and the front end's client types are generated from it, with a scheduled job that fails when the two drift.

  • WebSocket

    rooms with replay

    Per-room sequence numbers and a bounded replay buffer, so a reconnecting client resumes rather than guesses.

What it costs to run

Nothing, so far.

There is no price list here because nothing is for sale. What there is instead is the actual bill: every rung of the model chain is a free tier, so the modelled cost below is what these answers would cost at each provider’s published list price, quoted beside a real spend of zero.

0.00 USD

actual spend to date

Every provider rung, the vector store and all three hosts are on free tiers.

eval/scoreboard.md

0.00026 USD

modelled, at list price

The answered Turn above, priced at the provider's published rate for its real token count.

src/nivara_ai/turn/cost.py

26–39%

across eight routed categories

Cheaper again with the model router on, measured at no accuracy regression.

eval/router_ablation.md

The free instances sleep after fifteen idle minutes, which is the honest cost of this particular bill: a genuine cold start can take up to a minute. A scheduled ping keeps the API warm; the AI service is left to sleep.

The architecture

Four front ends, one API. What separates them is what they hold.

Not what they render. Every surface resolves its tenant server-side from the credential on the request, and the tenant is never a value the client passes. Isolation is enforced in Postgres with row-level security, so a forgotten filter in application code cannot return another tenant’s rows.

The source

Three repositories, and every number in them reproduces.

The evaluation harness runs from a clean clone with no provider key and no credential of the author’s — it replays committed recordings — so the figures on this page are checkable rather than claimed.

Questions

The things worth asking.

Is this a real product I can buy?
No. Nivara Desk is a working system built as a portfolio project — it runs, it is deployed, and every number on this page came out of it, but there are no customers, no pricing and nothing for sale. The tenants and their tickets are seeded demo data.
Are these figures real, or illustrative?
Real, and reproducible. The evaluation harness runs from a clean clone with no provider key and no credential of the author’s — it replays committed recordings — so the accuracy, injection and retrieval numbers regenerate from data in the repository. The two Turns driving the instruments on this page were captured from the deployed service, and the deflection screenshot is read live.
What stops the model from claiming it answered something it did not?
Prose written outside a tool call is never posted to a customer. The only path to a customer’s inbox is a tool call carrying retrieved content, so an answer that was never grounded has no route to become one. The eval harness scores disposition separately from content for exactly this reason.
What happens to a question about money?
It escalates, by contract rather than by the model’s judgement. Refunds, account recovery and anything that moves money are classified sensitive before the model is asked, and the sensitive path has no answering branch to take.
Why is the first request slow?
Free hosting. The instances sleep after fifteen idle minutes and a genuine cold start can take up to a minute. A scheduled ping keeps the API warm; the AI service is left to sleep, because keeping it awake would mean burning quota on nobody.
Can I run it myself?
Yes — docker compose up in the API repository migrates the schema, seeds two tenants and comes up with no credentials and no keys. Optional integrations stay dormant when unconfigured rather than fatal, which is what makes a key-free start possible.

Ask it something it cannot answer.

The interesting half of this system is what it refuses. Open the widget and ask about a refund — then watch the ticket arrive on the agent queue.

Demo admin, seeded. Nothing to create, nothing to pay for.