Five stages, and you can check every one.
Everything hiy does runs on one five-stage cycle — the Honest Loop. Your material goes in, answers come out with the passage they came from, and the questions it couldn't answer come back to you, where answering one teaches it permanently.
Sources
What you actually made — writing, talks, notes, a CV.
Cited answer
Grounded in a passage your visitor can open and read for themselves.
Honest gap
No material, no answer — and it shows you the searches it ran first.
Teach
You answer the question once, in your own words.
Reindex
It uses your answer from then on, and cites you for it.
And back to sources — the gap you answered is now one of them.
Sources
Your material goes in. Nothing else does.
Pages, posts, docs, and the lists you keep — that is the whole well. It answers from material you added, and nothing else: no general-knowledge filler, no confident guess dressed as an answer. And from the first source, a plain reading of where your twin stands tells you the single most useful thing to add next.
- Answers drawn only from material you added
- Test privately before anyone sees it
- Guidance that keeps going after launch
Nothing crosses this edge that you did not add.
Every window in the index was cut from your material, or from a summary built only out of it — and a summary window claims no source at all. An answer can only be grounded in a window that is in the index. Consecutive windows share a band of words, so a passage is never split away from what came before it.
Cited answer
Every answer carries the passage it stands on.
Every grounded answer carries the passage it came from. Open the citation and you read the actual source text — not a title, the evidence — and it links back to where it lives on your own page.
- Passage-level citations a visitor can open
- Links back to where it lives on your page
- Grounded in what you actually made
“Week one is reading. I ask for six months of notes because the pattern is nearly always already written down.”
Honest gap
Two refusals, and neither one guesses.
Two refusals, both deliberate. Off your topics, it declines in your tone — you say what you cover and how firmly everything else is turned away. And when your own material runs out, it admits the gap and shows how many searches it ran before giving up, instead of inventing an answer. No plan can remove this.
- Strict, balanced, or loose — your call
- Declines trivia, coding help, homework
- Gaps admitted, not papered over
Write me a python script
Teach
The gap becomes a question in your queue.
The question it couldn't answer doesn't vanish — it queues for you, with the conversation that raised it. Answer it one time and the gap closes permanently; your twin uses your words from then on.
- Unanswered questions queue for you
- One answer teaches it permanently
Knowledge gaps
- gapDo you take on pre-revenue founders?
- gapHow long does a typical engagement run?
“Pre-revenue is most of my work — usually at the positioning stage.”
Reindex
It gets better because it was used.
Your answer becomes a cited source like any other, and the numbers show it: the topics your audience repeats, and how much your twin could answer from your own material — coverage that climbs as gaps close.
- Most-asked topics and recent conversations
- Coverage — answered from your material
- A daily or weekly summary by email
The answer you typed joins the same index.
A correction is a source row like a page or a transcript: chunked, embedded and retrievable on the next question, with no separate store for taught answers and no special case in retrieval.
The same five stages, in the words the code uses.
The loop above is what a visitor experiences. Underneath it there is one path through six objects, and they have names — the retrieval index, the grounding check, the knowledge gap. The path is drawn stage by stage in the docs, where you can follow it against the code rather than take the claim.
How one question becomes an answerWhat holds the whole thing up
| Object | Belongs to | What one is |
|---|---|---|
| Organisationorganizations | — | One per account, made with it. It is the billing subject and the owner of a twin's signing secret. A team agent's conversations carry no per-person trail: api/twin/conversation refuses a transcript for an internal twin outright, so an admin sees the topics and the gaps and not who asked. |
| Twintwins | an organisation — org_id | One per slug. Its variant — public_personal, internal or support — decides which model answers, how long an answer may run, and how firmly it declines. |
| Sourcesources | an organisation — org_id; twins subscribe via twin_sources | One per thing you added: a page, a file, an interview, a correction you wrote. Its type records which. It belongs to the organisation, and one source can feed several twins — each still builds its own index. |
| Chunkchunks | a twin — twin_id | A 180-word window of that text, overlapping the one before it by 30 words. Its origin is source or summary, and a summary chunk's source_id is null — which is how it is kept from claiming a source it never had. |
| Answermessages | a conversation — session_id | One reply. It may search the index up to 3 times, rewording as it goes, before it either answers or says it has no material. |
| Citationmessages.cited_sources | an answer | Built from the passages that answer actually retrieved: at most 3, one per source, none below a 0.25 cosine-similarity floor. Nothing else can be added to the list later. |
Every row is a table or a column, not a metaphor for one. The chunk window and its overlap are read out of lib/rag/chunk.ts when this page is built, so they cannot drift from the chunker.
One invariant, stated flat: who may talk to a twin is decided in one pure function, and it fails closed. An audience value nobody taught it about denies rather than defaulting to public, and every denial returns 404 rather than 403 — because a 403 confirms the twin exists, which for a company's internal twin leaks the one bit an attacker wants.
lib/twin-access.ts
The limits, with the names they have in the code.
Not a claim about how fast or how good — those would be numbers nobody measured. These are the bounds the request actually enforces, each one readable in the file beside it. The error body a caller gets at each bound is enumerated in the API reference.
| Name in the code | Limit | Where it lives |
|---|---|---|
| message | 1–2000 characters, per request | api/chat/route.ts |
| history | 20 turns, 4000 characters each | api/chat/route.ts |
| slug | 1–64 characters | api/chat/route.ts |
| VISITOR_DAILY_PER_TWIN | 100 messages a day, per visitor IP per agent | api/chat/route.ts |
| rateLimitDistributed("chat:<ip>") | 20 requests per 60 seconds, per IP | api/chat/route.ts |
| maxOutputTokens | 400 tokens — the support variant's answer ceiling | lib/agent-kinds.ts |
| DEMO_TOKEN_LIFETIME_SECONDS | 600 seconds (10 minutes) — the grant minted for the Support Agent demo | lib/demo-support.ts |
At the monthly allowance the agent does not go quiet: checkAndCountMessage declines the answer and the widget says it is at its limit while still offering the hand-to-a-person form. The per-visitor daily bound is checked before that counter on purpose, so traffic rejected as abuse never debits the creator who was being attacked.
And when a conversation should become a person.
Optionally let visitors leave their details so you can reply personally — after a few messages, or exactly when your twin couldn't answer. Off by default; always dismissible; a People list with what they asked about, CSV export, and delete anyone any time.
How it worksWant me to follow up personally?
In your People list
And then it starts again.
The gap you answered is a source now, so the next visitor gets a cited answer where the last one got an apology. That is the whole engine.
Sources
What you actually made — writing, talks, notes, a CV.
Cited answer
Grounded in a passage your visitor can open and read for themselves.
Honest gap
No material, no answer — and it shows you the searches it ran first.
Teach
You answer the question once, in your own words.
Reindex
It uses your answer from then on, and cites you for it.
And back to sources — the gap you answered is now one of them.