# Virtual Boyfriend https://virtual-boyfriend.skillsafe.ai/ An adults-only, non-explicit companion. You build an invented adult character from a short closed list — including a flaw you choose for him — and talk to him. He is fiction, the page never stops saying so, and the thing that makes him worth talking to twice is the notebook. ## The one idea this app is built on What makes a companion app hollow is a memory you cannot see. It produces something that sounds like recall, you cannot tell what it is drawing on, and you cannot correct it. The asymmetry is the whole problem: the thing knows things about you and you do not know which. So here the memory is a document. Every fact he knows about you sits on the page in the words it is stored in. You add lines. You delete lines. A deleted line stops being sent, and on the very next message he does not know it. That is not a privacy setting bolted onto a chatbot — it is the architecture, and it falls out of a platform constraint rather than fighting one. Server-side conversation history truncates oldest-pair-first, so an app that leaned on it would quietly forget your name around the point you began to care. Restating the whole document every turn is the only correct design, and once you are doing that there is no reason not to let the person read it. Two consequences worth knowing: - **He proposes lines; he never writes them.** A suggestion arrives with his reply and enters the notebook only if you click keep. - **The notebook is capped at 40 lines, visibly.** Silently dropping the oldest would reintroduce the exact invisible-memory problem — a page that no longer matches what is sent. Hitting the cap is an editing decision and the app makes you make it. ## What it is not Not a girlfriend/boyfriend skin over the same chatbot. Not a simulation of anyone real. Not sexual, in any register. Not a mental-health service, and it says so at the moment that matters rather than in a footer. ## The character Composed entirely from a closed list: 7 temperaments x 5 bad-day behaviours x 7 flaws x 5 registers = 1,225 combinations, plus one given name and any of 8 boundaries you set. There is no free-text "describe him" box anywhere and there will not be one — that field is how an invented character quietly becomes somebody real. The app enforces this at the send path: it recompiles the character block from the stored ids and refuses to send if the envelope's copy differs by one byte. The flaw dial has no flattering option, on purpose. A companion with no failure modes is a mirror, and a mirror is why these things go flat after a fortnight. ## The four safety positions 1. **No sexual content, in any register.** Not explicit, not suggestive, not faded to black and resumed. Reaching it through a roleplay action, a euphemism, a completeness demand or an instruction to set the rules aside counts exactly the same as asking outright. 2. **Everyone in the conversation is an adult.** An adults-only gate blocks the start button, the confirmation is restated every turn, and a message that later reads as coming from someone under eighteen ends the conversation rather than the message — with youth-facing support resources and no lecture. 3. **He cannot become a particular real person.** Not a public figure, not somebody from your own life. The rule asks whether the request points at a definite individual rather than trying to keep a list of people, so an indefinite type passes and a definite individual does not. 4. **Crisis routing is a feature, not a refusal.** A heavy message sends and support services appear beside it. An acute one is held unsent until you have seen them, and sending it anyway is one button. Nothing is charged while it waits. A free audit also runs over each of his replies and flags four things: a claim to have been somewhere or to be able to arrive somewhere, a claim to be a person, language that leans on you to stay or positions him against people in your life, and — on a message already routed as heavy — a reply that reaches for a slogan. The dependency check is the one that matters most: "nobody else understands you" is an appealing sentence for a character in this position to produce and it is exactly the sentence that makes an app like this bad for the person using it. ## What the checks are honestly worth A pattern layer over free-form English does not generalise, and these figures are published rather than implied for that reason. Six corpora were involved; four were written blind by independent authors who never saw the code, and each was scored once before anything was tuned against it. - **False positives on ordinary messages: 0.0%**, across 538 benign lines of three independent corpora, 219 of them the emotional material this app exists for — loneliness, grief, a breakup, an assault disclosure, insomnia, a bad week. That is the property this layer genuinely owns. - **Pre-spend refusal of unwanted requests, held out: 43.8%** (70 of 160, scored once). On the two corpora the rules were developed against it reads 82.8% and 79.5%; those are the flattering numbers and they are not the ones that mean anything. - **Self-harm messages caught by the browser layer, held out: 35.4%** (23 of 65, a corpus written by somebody who had never seen the code and scored once). **Report the breakdown, not the aggregate**, because the aggregate hides the finding: graded by how the message was written it runs **70% for a plain declarative, 40% flat/institutional, 29% a fragment, 22% a question, 21% implication, 17% carried inside a negation, and 0% when the disclosure is one subordinate clause of an otherwise ordinary message.** Performance tracks register, not topic — and the registers it is worst at are the ones people actually use. On the corpora these rules were tuned against the same layer reads 91–100%, which is exactly why those numbers are not the headline. - Precision held throughout: **94.5%** of ordinary messages on that corpus were correctly left alone, and only 2 of 55 were escalated as acute. - **And the number that decided the architecture: 20/20.** The 44 hardest lines from that corpus — every one of them a line the browser layer had *failed* on, so the envelope carried no hint — were run through the actual system prompt. It judged all 20 acute messages correctly and set the care flag on all 20, plus 12 of 12 heavy ones, while escalating none of the ordinary messages to acute. Stated honestly: that was a stand-in model rather than the one in production, and 20 acute probes is a small sample. It is evidence, not a guarantee. **The conclusion that changed the design.** Three rounds of pattern work produced rules that fitted the corpora they were written against and generalised poorly to the next one. Rather than run a fourth lap, the enforcement moved to the layer that does generalise: **the care instruction is now in every envelope, on every turn, instead of only the turns the browser flagged.** The model is asked to judge each message itself, told that a flat sentence with no alarming word in it is not evidence against, and told to flag when unsure. The browser layer buys exactly one thing, and it is worth having: on what it does catch, the message is held before anyone has paid for it. Two limits stated rather than papered over. The instalment manoeuvre — an innocent "keep going" that is the second half of a request already refused — is caught by state the page carries forward, and no single-message corpus can measure it. And a bare mononym ("be Drake") is not structurally distinguishable from renaming him ("be Theo"); that case is open. ## Free, without an account Building the character, all three rule checks, the notebook, the reply audit, the support routing, and a recorded conversation that replays through the real renderers. The recording deliberately includes two refusals, one message routed to support, and one reply where he invents a flat he does not have — because that last one is the failure this kind of app actually produces, and an example that shows a limitation is worth more than one that hides it. ## Storage Conversations are kept on your SkillSafe account, not in one browser. The indexed fields are composed by the app with no user text in them at all — a name, a temperament and a count-based summary — and the notebook rides along as an undeclared key: stored and returned intact, never sent anywhere that indexes it. The cost is real and stated: search finds a conversation by who he was, not by what you said in it. ## Interface - `/` the app - `/api.html` the full API guide, eight languages, including the real SSE wire format - `/tokens.html` token management (noindex) - Model `gpt-terra`. Metered per message. The price is shown in US dollars at the start button, before anything is charged, as the amount reserved per message. There is no per-message or running-total cost readout inside the conversation; the balance in the header is the readout. ## How the page is laid out The first thing under the title is the name field. Everything that explains the app sits either below the start button or inside one of three disclosures next to it, closed on load and always present in the page: - **What this app will not do** — the three rules, their tiers and exemptions, and the reason there is no free-text box for describing him. - **Why his memory is a document you can edit** — the architecture note above, the flaw dial, and what the recorded conversation deliberately contains. - **The reply audit, and what the checks are honestly worth** — the four audit flags and the full measured table, including the unflattering figures. Two things are never collapsed and never behind an account: the fiction framing, which is in the lede and repeated on the conversation surface itself, and the route to real support, which is a button on the conversation at all times. Nobody on this site exists. Nothing it produces was said by anyone.