Case Study
Compliance Work for Three AI Companion Laws
Reading three state AI companion laws from the enacted text, turning them into eleven build-ready requirements, and shipping ten in a week.
TL;DR
Three state AI companion laws apply to Duskglow: California's since January 2026, New York's since November 2025, Washington's from January 2027. In April I built the first compliance pass from summaries. In September I read all three laws from the enacted text, and four things I had built around were wrong, including a notice from a bill that died and a duty that applies only to known minors. The read produced eleven requirements, each with its statutory basis, as-built status, gap, owner and effort, ordered by the exposure live today. Ten shipped within a week, across the app, the server function, the database and three public pages. No page claims a compliance status, and a test keeps it that way.
The Problem
Duskglow is a journaling app with an AI companion, Luna, and three state laws reach it. California's companion chatbot law has been in force since January 1, 2026. New York's since November 5, 2025. Washington's takes effect on January 1, 2027. Each one applies to any operator with users in the state, and none has a size or revenue threshold, so a closed beta is covered the same way as a product with a million users.
I built the first compliance pass in April from summaries: law-firm client alerts, a legislative tracker, the two-page digests that come up when you search a bill number. That's what most small teams do, and I understand why. A statute looks like a lawyer's document. The summaries looked like requirements. The safety half of that work, the crisis detection and the adversarial testing, is in the Safety Architecture case study. This one is about what the laws ask for beyond that, and about reading them properly.
Five months later, with Washington's date three months out and a public launch after it, I read all three laws from the enacted text. Four things I had believed were wrong.
The New York notice I had been planning around, a bold, capitalised, sixteen-point statement that the companion “is unable to feel human emotion,” was never enacted. It came from a bill that died in the state Senate on January 7, 2026, and it still circulates in compliance guides as if it were law. The section that did pass asks for the substance, that the user is not talking to a human, with no formula. Washington's rules against manipulative engagement techniques, which I had read as applying to every user, apply when the operator knows the user is a minor. California requires every operator to disclose that companion chatbots may not be suitable for some minors, a notice I had never seen in a summary and had nowhere in the product. And California's first annual report, due July 1, 2027, covers calendar year 2026, which was already more than half over.
The earlier version of this case study said Duskglow's daily message cap was a Washington legal requirement. For adult users it isn't. The cap stays, for cost and for the product, but the sentence was wrong, and it came from a summary I never checked.
Reading them again was worth a day because of the stakes. California gives individuals a private right of action at $1,000 per violation. Washington treats a violation as a per-se unfair act under its consumer protection law, which opens the same door. New York enforces through the attorney general at up to $15,000 a day. For one person with no legal department, every missing notice is a claim that anyone with standing can bring.
Approach
Read the enacted text, and write down what you couldn't read.
I pulled the chaptered California text and the Washington session law from the legislatures' own sites. New York's official pages blocked an automated fetch, so I used a faithful reproduction of each section and recorded it as a lower-confidence source instead of pretending otherwise. Every statute got a row: what was read, how, and how sure I was. That table is still the first section of the record.
Then I went looking for what had died. The sixteen-point New York notice was the first find. A California amendment that would replace “knows the user is a minor” with a constructive-knowledge standard was still moving through the legislature, so I wrote down that the age check is sufficient under the law in force and becomes load-bearing the day that passes. Knowing what hadn't passed mattered as much as knowing what had, because the summaries don't tell the two apart.
One list, built to the strictest of the three.
Each requirement in the list has the same six fields: the statutory basis, the as-built status (met, partial, gap, or not triggered), the gap in a sentence, the owner, the effort, and whether it can be undone. The list is ordered by the exposure that is live today. California first, then Washington's January date, then effort.
Where the three laws ask for the same thing at different strengths, I built to the strictest so one mechanism serves all three. New York wants a not-human notice at the start of each interaction and allows once a day. Washington wants it at every interaction with no relief. So the line appears every time a conversation opens, and there is no day-tracker to get wrong. Both want a reminder every three hours of continued interaction. The clock measures the user's activity, not time since the page loaded, or a tab left open overnight would notify an empty room at three in the morning.
The list had eleven entries. The cheapest closed the only California gap with a live private right of action: one sentence about minors, on three surfaces, shipped the same day as the read. The largest was a public page that describes the crisis protocol, what Luna watches for, how the two layers of checking work, what she does, what she doesn't do, what is recorded, and the limits. Washington wants it on the website and inside the app, so it has its own route, and the app links to it from Settings and from the first screen.
The count that has to survive the deletion promise.
Washington requires the prior year's count of crisis referrals published each January. California wants it in the annual report. The obvious implementation is a query over the safety records: count the rows.
Three things were wrong with the rows. My own test runs write the same records as a real user, and the adversarial suite fires the crisis protocol on purpose, many times a run. The deletion right takes an account's records with it, so a count taken in January can be lower than the count on December 31. And one of the paths that shows crisis resources, the one where the AI model writes the reply itself, wrote no record at all.
So the count lives in a separate yearly tally. The database adds to it whenever a safety record is written for an account that isn't a Duskglow test account. It holds no account ID and no timestamp, and it stays when the account is deleted. I chose yearly buckets over monthly: the statutes want a year, and with a small user base a monthly number could point at a person. Each path keeps its own line, so a report can show them together or apart, which is a question I've left for an attorney rather than answered myself.
That changed a public promise. The privacy policy and the protocol page had said safety records are deleted with the account. They still are. The yearly total is not, and both pages now say so, with the reasoning in a note beside the sentence.
Describe the mechanism. Never claim the status.
No public page says Duskglow complies with any of these laws. The protocol page says what Luna watches for, what she does when she finds it, what is recorded, and where she fails. The privacy policy says what is collected and where it goes. A reader, or a regulator, judges the rest.
Three reasons. No attorney has reviewed any of this, and “compliant” is a legal conclusion I'm not qualified to draw. A description can be checked against the product, where a status can only be argued. And the statutes mostly ask for the description anyway: publish the protocol, show the notice, report the count.
The rule is enforced, not just written. Two tests in the app's suite read the published privacy policy and the protocol page, and they fail if either names one of the three statutes, or if the protocol page promises detection or claims compliance. Soon after I wrote them, I found a compliance claim of my own in a line on the homepage and took it out.
That rule has a second half. Categories are public, mechanics are not. The page says Luna's first check is a fixed list of clear phrases about suicide, self-harm, eating disorders and abuse. The phrases themselves stay private, because publishing them would tell someone how to word around the first layer. How I tested that layer from the user's side is its own post.
Architecture
The eleven requirements, where each one came from, and what was built for it. The dates are the day each one reached the live app.
| Requirement, basis, and the day it went live | What was built |
|---|---|
A not-human line at every conversation open NY, WA, CA · Sep 29-30 | A line in the thread each time a conversation opens; every disclosure surface now says “not a person” |
A three-hour reminder NY, WA · Sep 30 | A notice after three hours of continued activity, measured from the user's own turns |
The minors-suitability notice CA · Sep 29 | One sentence on the landing page, the first-run screen, the Terms, and later the privacy policy |
A published crisis protocol, on the website and in the app CA, WA · Oct 1 | The “How Luna Handles a Crisis” page on its own route, linked from Settings and the first screen, generated from one source file like the legal pages |
A durable yearly referral count CA, WA · Oct 3 | A yearly tally with no account ID, written by the database, test accounts left out, kept after deletion |
Detection coverage, and a record for every path WA · Oct 1-2 | Eating disorders added to the first check; a reply the model writes itself, and a message it refuses, now write a record too |
“Are you human?” WA · Oct 1 | Luna answers plainly that she is an AI when asked, with a test case in the suite |
Report readiness CA, WA · Oct 3 | A calendar and a one-page report template in the incident response plan, filled from the published page in its own words |
A versioned acceptance of the first-run disclosure Hygiene, not a statute · Oct 3 | The acceptance records which version was shown and when; new wording shows the screen again |
The privacy policy pass All three · Oct 5 | The count's two uses, the link to the protocol page, the minors sentence, and what the data export holds and leaves out |
A provable no-known-minors posture CA, WA · Open | The 18+ device check unchanged; the policy already commits to deleting an account that turns out to be a minor's; the internal procedure for that day is still unwritten |
Underneath the list, the data decisions from April still hold, and a few grew teeth. The age check runs on the device and keeps a yes or no, so no date of birth is ever stored. A safety record holds the account, the time and which check fired, and not the message, and records can be added or deleted but not edited. Luna's memory of past days comes from short readable summaries, while the conversations themselves are encrypted on the device before they are stored. The data export now includes everything the signed-in app can read and names what it leaves out. Deleting the account removes everything tied to it, and the two things that remain, the yearly total and a line saying a deletion happened, hold no account ID.
What I'd Do Differently
I'd read the statute in April. I built the first pass from summaries because a statute looked like a lawyer's job and a summary looked like mine. Reading the three laws took a day. The summary habit cost five months of building toward a version of the law that partly didn't exist, and one published sentence that was wrong.
I'd tie every public sentence to the code that makes it true. The privacy policy said the data export held a user's entries. The code that built the file left each entry's reflection out, and nobody noticed for weeks, because the sentence and the code lived in different places and changed on different days (the full story). Now any sentence about a mechanism changes in the same release as the mechanism, and the published legal pages are generated from one source file each, with a check that the live copy and the source are byte-for-byte equal.
I'd put the lawyer's question next to the sentence from the start. The April version of this case study described a pipeline of seven attorney questions in a separate document, each with a provisional decision. It was better than an email, and it still separated the question from the thing it was about. Today there are 31 notes inside the source of the three public pages, each beside the sentence it questions, with the date, the reasoning and, where there is one, the alternative wording, and the publishing step strips them out. When an attorney finally reads the policy, the question is sitting where the answer goes.
Impact
- Three statutes read from the enacted text, with the source and the confidence of each recorded, and one dead bill identified that compliance guides still quote as law.
- Eleven requirements, each with its basis, status, gap, owner, effort and reversibility. Ten shipped within a week of the read, across the app, the server function, the database and three public pages. The eleventh is an internal procedure, still owed.
- A yearly referral count that leaves out test runs, survives account deletion and counts every path that shows resources, with its first posting promised for January 2027.
- 31 attorney notes beside the sentences they question, in the sources of the three public pages.
The limit, stated plainly: no attorney has reviewed this work, and no page says Duskglow complies with anything. Washington's law is not yet in force, and the first California report is not yet due. What I can show is the method, the list, the shipped features, and the notes that turn a future legal review into a day's work instead of a discovery project.
Principles
Read the text. A summary is a copy of a version that may no longer exist, and it won't tell you which version.
Build to the strictest law you're under. One mechanism, no per-state branches, nothing that depends on knowing where the user is.
Count in a way that survives your own promises. The deletion right and the test suite both change the number, and both had to be designed in before the first real row.
Describe the mechanism and let the reader judge the status. A description can be checked. A claim can only be defended.
Put the lawyer's question next to the sentence it's about.