Trust, stated as rows you can check.
No testimonials and no badge wall. This page is the record of how hiy is governed and built: the identity policy and its review, the surfaces no plan can remove, how tokens and limits fail, who processes which data, and what actually generates an answer. Each row links to the doc or policy that backs it — or says plainly that it is enforced in code.
Identity, and who reviews it
The identity policy is the rule the whole product rests on, and it is enforced by machinery — not stated in a FAQ.
You may only twin yourself, or a product you own.
The rule everywhere — the terms, the acceptable-use policy, the API, the confirmation checkbox — in those words. There is deliberately no “with their permission” carve-out: permission is something we cannot verify, and a rule with an unverifiable exception has no teeth.
The identity screen can approve; it can never reject.
Publishing runs a deterministic screen — no model call, so every decision is explainable. It auto-approves only when the twin's name matches the account's own identity and the sources actually talk about that person; everything else waits for a human. Turning someone down stays a human act, with a written reason the owner can act on.
By design, enforced in codeA decision is made about the exact name the reviewer read.
Approval records the name the human was looking at, and refuses to land if the twin was renamed while they read it — so a cleared name can never quietly become a different person's.
By design, enforced in codeThe Verified badge is derived, never stored.
It follows from the name that was cleared: rename an approved twin away from that name and the badge goes; rename back and it returns. There is nothing a rename can launder.
on failure
When the screen cannot clear a twin, the twin waits.
There is no timeout that publishes by default. The twin sits at
awaiting review— a 404 to every stranger — until a human decides, becauseREVIEW_ENFORCEDinlib/twin-access.tsis the flagpublicStateis defined to agree with. The queue it waits in is an admin-only route; it is not a public status surface and we do not describe it as one.By design, enforced in code
The screen can approve. It can never reject.
A deterministic screen clears a twin only when the name is the account’s own and the sources talk about that person. Everything else waits, and a decision to turn someone down is always a person’s.
The states a twin moves through — draft, awaiting review, live, and the two ways a decision can turn it down — are drawn in what happens when you publish.
What no plan can remove
De-branding is the only trust-adjacent thing paying changes, and it is marketing credit — never honesty. These surfaces are never removable, on any plan:
The AI label.
Every twin and agent is labeled as an AI wherever it answers, and says so itself when asked. De-branding swaps hiy's name for the category noun — “Sam's AI twin”, which discloses more, not less.
The honest gap, and its receipt.
When the material runs out it says so instead of inventing, and shows how many searches it ran before giving up. The count never hides, on any plan or setting — without it, “looked and found nothing” and “never looked” would be the same observation.
Citations behind grounded answers.
No plan can remove them. The creator chooses how they render — a quiet source line, open excerpts, or not shown to visitors — but that is the creator's editorial call over their own material; paying cannot make an answer look better-sourced than it is.
The report link.
Every public twin carries one. Reports land in an operator queue with real dispositions — not a mailbox nobody reads.
By design, enforced in codecannot
A hidden citation cannot be recovered from the page.
When a creator chooses not to show sources, the citations are removed from the response before it is sent —
citationsForVisitorinlib/rag/tools.tsfilters them out and drops the source id with them. Hiding them in the view would leave them in the wire for anyone who opened a network tab, so “hidden” is made to mean absent.By design, enforced in code
Token security
A Support or Team Agent has no public URL. Your users reach it through a short-lived token your own server mints — your site vouches for its visitors, so they never need hiy accounts. (In the app today this product is called a support twin; the machinery is the same.)
Signed with HMAC-SHA256 — deliberately not JWT.
There is exactly one algorithm and it is not negotiable: no algorithm header to confuse, no “alg: none”, no key discovery. A bearer credential for one agent for a few minutes does not need a framework built for federated identity.
Lifetime is capped, and the cap is ours.
Tokens default to fifteen minutes and are refused beyond
MAX_TOKEN_LIFETIME_SECONDS = 3600 s(one hour) — even when signed correctly. Lifetime is our policy, not the signer's.By design, enforced in codeAudience-bound.
A token minted for one agent never opens another, even inside the same organization. A valid signature alone is not enough.
By design, enforced in codeSigning secrets are derived, never stored.
Each organization's secret derives one-way from a single master, so there is no table of per-customer secrets waiting to leak — and holding one organization's secret tells you nothing about any other's.
By design, enforced in codeVerification fails closed, and explains nothing.
The signature is checked in constant time before the payload is even parsed, and a caller is never told which check failed — “bad signature” and “expired” together would tell an attacker which half to fix.
By design, enforced in codeWithout a token, the agent does not exist.
Asking for it returns the same “not found” a made-up address does — a 404, not a login wall that confirms there is something worth attacking. Team Agents answer outsiders the same way.
The three parts of a token, and the order the server checks them in, are drawn in how a token is built and checked.
Limits that fail closed
Wherever metering or entitlement can fail, it fails toward not serving — never toward serving unmetered, and never toward quietly honouring a plan that lapsed.
The message cap is checked and counted in one atomic step.
Concurrent conversations cannot race past it, and if the counter itself errors, the answer is declined rather than served unmetered.
By design, enforced in codeEntitlement is re-derived on every read.
The stored plan is treated as a cache of a decision. An expired trial serves the free plan even if the job that should have recorded the expiry never ran; a failed read serves the free plan rather than the paid feature set.
By design, enforced in codeHitting the cap is stated, never disguised.
A twin out of monthly messages tells visitors so and stops; an embedded Support Agent at its cap states the limit and offers the contact form — never a dead widget on your site, never an auth-shaped error.
on failure
When the billing cron fails, the plan degrades to Free.
The nightly job only tidies the
planlabel; it is never what entitles anyone.lib/billing/entitlement.tsderives the answer from the reason columns instead — a trial past its end date, or a Stripe status outside the entitled set, resolves to the default plan whether or not the job ever ran. A missed run therefore gives nobody a free paid plan, which is the failure the derivation exists to prevent.By design, enforced in codeAvailability, honestly.
No status page yet, and no availability percentages — we won't publish figures we aren't measuring against a public record. The service is provided as-is, and the trigger for a status page is written down: the first embedded customer with real traffic.
Who processes what
The data posture in the GDPR sense of the words — who answers for which data, who touches it, and for how long.
Processor for agent conversations; controller for account data.
When your Support or Team Agent answers your customers or colleagues, those conversations are your data: you are the controller, and hiy processes them on your behalf. For your own account — email, sources, settings — hiy is the controller.
Five subprocessors, named.
Vercel serves the application. Supabase hosts the database and authentication in the EU (eu-west-1). Anthropic and OpenAI run the models, and OpenAI also generates the search embeddings. Resend delivers notification email. Each processes data only to provide its service to us.
Kept while your account exists, then really gone.
Delete an agent, a person, or your account and the corresponding data goes with it — deletion is deletion, not a hidden flag.
The privacy pointer survives de-branding.
Every agent conversation carries a “processed by hiy.ai · privacy” link in its chat chrome, in the same never-removable class as the AI label. Paying removes hiy's marketing credit from an embed; it never removes the pointer to who processes the conversation.
cannot
A Team Agent cannot tell an admin who asked.
There is no per-person attribution surface to turn off, because there is none to begin with:
api/twin/conversationanswers404for any twin whose variant isinternal, so a transcript for a team agent cannot be opened by anyone, owner included — and the People view drops attribution for those same twins. It cannot ask, either, which is the half that used to depend on a setting:allowCapture: falseon the team-agent policy is a ceiling over the creator's own follow-up switch, andmayCaptureinlib/agent-kinds.tscomposes the two everywhere a card could appear — so a colleague is never shown the field they would type their own name into, whatever the switch says. An admin sees the topics and the gaps and not who raised them. A colleague asking their team's twin a question is not building a record against themselves.By design, enforced in codeNo formal data-processing agreement document yet.
The posture on this page is the commitment, and the privacy policy is the governing text. A signable DPA is the written-down follow-up — if you need one to evaluate, say so and it moves up the list.
How it's engineered
The honest version of an AI-engine page. No diagrams of machinery that doesn't exist, and no benchmark charts: we haven't run our own evaluations yet, so none are cited. When numbers appear here they are ours, read from the code that enforces them.
Three prompt layers, and only one belongs to the creator.
Layer one is identical for every twin and agent on the platform — the honesty rules: say you're an AI, never invent, admit the uncovered half of a half-covered question. Layer two is identical per kind — a Support Agent never sells; a Team Agent reports and never decides. Layer three is the creator's: voice, scope, off-limits topics. No setting reaches layers one or two.
By design, enforced in codeRetrieval over a synthesized wiki — not a fine-tune.
Sources are rewritten into a structured wiki and indexed for search. Every answer is grounded in passages retrieved for that question, and a grounded answer can show the passage it came from.
Sources feed retrieval, never training.
Your material becomes an index to be searched at answer time. It trains no model — not ours, not a shared one, and never another creator's twin or agent.
Models are tiered by task — never by plan.
Where a person's voice is the product — an Expert Twin's chat, and the wiki synthesis behind a creator's twin — the stronger tier answers: today Claude Sonnet, with an OpenAI equivalent depending on configuration. Support and Team answers, suggested follow-ups and gap questions run a lighter tier — today Claude Haiku — because a password-reset answer needs speed and precision, not a voice. The promise is the routing, not the vendor; no plan buys a smarter model.
By design, enforced in codeCost discipline is fail-closed too.
Answer length is capped per kind of agent — a Support Agent's answer is capped at
400output tokens by its entry inlib/agent-kinds.ts— and an answer that searches its own knowledge gets a hard ceiling ofMAX_SEARCHES_PER_ANSWER = 3rounds. It answers with what it found rather than running an open-ended loop.By design, enforced in codecannot
hiy's own prose cannot be quoted to a visitor as a source.
Every indexed passage stores where it came from.
buildIndexChunksinlib/rag/chunk.tsstampsoriginas it writes —sourcefor a creator's own material,summaryfor hiy's synthesis of it — and everysummarypassage is dropped before the citations reach a stranger. The accepted consequence is real: an answer leaning on the wiki shows a visitor fewer citations, sometimes none. Showing more would mean presenting our sentences as the creator's.By design, enforced in codecannot
A Support Agent cannot offer you services.
Not an instruction it is asked to follow — a catalogue it is never given. The support variant carries
allowServices: falseinlib/agent-kinds.ts,usableCataloguefilters the services category out of both the prompt and the tool list before the answer starts, andhiddenBookingFieldinlib/records/lookup.tswithholds the sign-up link on any other bookable category — from the tool result as well as the card. The bound is worth stating exactly: what it cannot reach is the services catalogue and every booking link, not every list its owner chose to attach.By design, enforced in codecannot
The search receipt counts queries; it cannot claim results.
What the receipt reports is the list of searches the twin chose to run, in order — the queries, not a verdict on what was there. hiy never tells you a search “found nothing”, because the honest observation is that it looked and this is what it looked for.
on failure
When a reindex fails, the lease expires and the next run proceeds.
A rebuild takes a database-backed lease of
REINDEX_LEASE_SECONDS = 600 s(ten minutes) rather than a lock it must remember to release. A process that dies mid-rebuild therefore blocks nothing: the lease runs out and the next attempt takes it. The failure mode is a stale index that is visibly stale, never a knowledge base wedged shut by a crash.By design, enforced in code
Where a passage came from is a stored column with three states, and only one of them can be quoted to a stranger — drawn in why only your words are quoted. All of it is checkable in the live product too: ask a twin whether it's an AI, ask it something its material doesn't cover, open a citation.
Missing the row you came for? Ask us. If the honest answer is “not yet”, that is the answer you will get — in writing, so you can hold us to it.