Last updated: 2026-10-21 — covers sessions 1–9 in full (through the 03:35 prompt on 22 Aug), plus the live-call rows from sessions 12–13 and 16–18, and the money rows from sessions 16 and 20. Sessions 10–11 are in their own files. If the session files carry prompts newer than that, this document is behind them and they win.
Every instruction given across all sessions, collapsed to its final form — where an instruction was later replaced, only the replacement is listed, with the superseded version noted so the reversal is not lost. Then checked against what the code actually does.
Status: built = in the product and exercised · built, unrun = in the product, never executed against reality · not built.
A programmable phone line for a team and its coding agents — not a PR-review app. The line goes both ways: anyone rings in and it lands in a watching terminal; any command rings out to one, some, or all teammates. The morning PR briefing and the weekly paper are two branded skills built on the CLI, offered as examples of what the line can do; the CLI is the tooling anyone builds their own skills on. The voice still takes no action — it collects decisions and hands them to a terminal that acts. (The product started, in session 1, as a solo morning PR call; session 9 turned it into team infrastructure, and its final prompt reframed it as tooling.)
| Intent | Status |
|---|---|
| Get the decisions coding agents are blocked on out of a screen and into a phone call | not built — rounds does not exist |
| A weekly paper, landing Saturday morning — the team's week by member, the week ahead, the state of the product (was: nightly, single-person); mailed to the whole team | built — cadence split, per-author facts, --team send; first Saturday edition unrun |
| The paper and the morning brief are two branded skills, not the product; the CLI is tooling anyone builds skills on | built — reframed in the skill descriptions and the landing page; no third-party skill exists yet |
| The landing page sells the team line and the tooling, not solo PR review; no false lines | built and live — rebuilt around the line + skills; the Stripe-cancel and caller-is-always-you falsehoods removed |
| The call brief nightly and the edition weekly from the same Saturday sitting (was: both nightly from one reading; before that, two loops) | built, unrun |
| The phone assistant takes no action; the transcript goes to a terminal on the user's own machine, which acts | built — still true on live calls: the voice has no tools and hands requests to the caller's own terminal |
Call your Claude session: ring in while your own terminal watches, and every request is handed during the call to a fork of the Claude Code session where /screenless was armed; the answer is spoken back (session 12; before this, the session only acted after the call) |
built and proven on scripted PSTN calls: a commit question answered in 10 s, and a forked session's code word recalled in 7 s; no human call yet |
| The line hangs up by itself once both sides said goodbye, or the caller asked it to, and never on someone still talking; a dropped voice or phone stream mid-call does not end the call (session 13: the session-12 version was proven on a scripted call but failed on Jan's first real one, because a mid-call socket drop had already marked the call ended) | built — unit-checked against the lines of call 28180e09; not yet deployed or called |
| Live answers come fast: a session too long to fork quickly (over 150k tokens) answers through a fresh run handed its brief and summary, and the voice answers from its notes when they suffice (session 13; was: every request forked the whole session) | built — measured 15.5 s / $0.21 per fork of a 529k-token session against 5.3 s / $0.01 fresh; not yet on a call |
The live greeting never talks over the caller, and calls them by the name they set themselves (screenless settings --name), or none (session 13) |
built — loudness check unit-tested on synthetic mu-law; not yet on a call |
A session's owner chooses who else may hear its brief when they ring in (screenless watch --brief-for team or emails; default only the owner). A teammate hears where things stand; nothing they ask runs during the call. A teammate with their own live watcher gets their own session, first (session 13, asked for on the call: "developers like me and Tomáš") |
built — typechecked, prefix query checked on SQLite; not deployed or called |
Live calls gated: reads and local work in the project only, plainly read-only machine commands (ps, screen -ls) included; push, deploy, send, delete and secrets wait for the session after the call |
built — gate unit-checked and exercised in forks; session 13 widened the reads and closed ~/.claude/.credentials.json |
| The line never answers a phone call on its caller id: a member's ring-in is rejected as busy and the line rings their verified number straight back, so only their phone can pick up; WhatsApp calls to the same number are answered at once, told apart by Meta's SIP domain and the number's WhatsApp id in Meta's headers (Telnyx gives them no route of their own). Both get the owner's full rights; only a test ring is restricted. No PIN, no attestation (session 16; was, session 13: full rights only on STIR/SHAKEN attestation A or a PIN keyed on the phone during the call, read off the media stream — removed with its keypad, settings flag and columns) | built and proven — ring-backs and WhatsApp calls answered live on Jan's phone (19:09–19:17, 18 Oct); WhatsApp needed OPUS on the TeXML app |
Who may do what: people and their level on the team page (screenless team level), levels and rules in a people-free .agents/permissions.md; a level a repo does not define gets its default (session 16; was, session 17: callers listed by email in the repo file) |
built and deployed — page and command used against production; no teammate has called through a level yet |
| Install by pasting one prompt into Claude Code; the agent installs, verifies the user's number by asking for the code, and invites everyone at the company it finds in Slack or the directory, after the user says yes (session 16; Jan chose inviting everyone over joining by domain) | built and deployed — flags and invite command checked offline; the prompt not yet run end to end |
| WhatsApp messages to the session: one fork per conversation, carried on until 30 minutes of quiet; screenshots, files and voice notes; 👀 when a live session has it; queued with a reply when none is live (session 16) | built and proven — Jan's messages answered by his live session, 18 Oct |
Meetings: the session joins an online meeting as a voice (screenless meet <link> [--at <time>], ad hoc or when it accepts an invite), speaks only when named, and each request is gated by who asked through the owner's permissions file; minutes from the same pool. It arrives with a chat message only, answers questions about the meeting itself from what it heard, says in its own words what it is doing only when a request takes over 4 s, and leaves 5 minutes after joining an empty meeting. The room is the unit: `--trust listen |
read |
| The landing page leads with "Your team's questions, answered without you." and a teammate's demo call (session 16; was: "Let your team call your Claude Code, not you", before that "Call your Claude Code session") | built and live |
On a live call, work that takes over about 20 s may send the caller a short progress note (screenless update "<text>"); the voice says it at a pause as progress, and the final answer still follows (session 14) |
built and deployed (b8796ad8) — gate and update path tested off-call; not yet heard on a call |
| The watcher exits the moment it receives a call, so the agent session is woken; the loop re-arming it is what never ends (was: a terminal process that never ends — reversed once it was clear a process that never exits can never reactivate the model) | built and proven — three real spoken requests plus a synthetic one delivered; exit-on-delivery verified |
| One Worker on the apex: landing page, team page and API together; the site Worker deleted (was: screenless.sh + api.screenless.sh as two Workers) | built and proven — merged, deployed, all surfaces answering; api.screenless.sh kept as an alias for old CLI configs |
| One store: KV removed entirely — settings on the user row, briefs/calls/watchers/outbox/codes/counters as tables, the team queue a query over calls, edition PDFs in R2 (was: KV beside D1, two consistency models, and an org-queue array to maintain) | built and proven — deployed, settings migrated, every CLI surface and a synthetic queued call re-verified on the new store |
One work channel: screenless watch delivers every finished call; collect/applied, the local state file and the ?since protocol deleted (was: a per-user APPLY channel beside the team watcher) |
built — wait now gates only the nightly; loop skill updated |
| Intent | Status |
|---|---|
| Organizations and users in a database; one org per user, joining a team means leaving the last | built and proven — D1; accept flow moved a user in production |
| Cloudflare SQLite chosen by the agent (offered: D1 or a Durable Object) | built — D1, because the roster and billing tabs are queries |
| One page at screenless.sh/team — sign-in, roster, invites, accept flow and billing as states of a single document | built and proven — exercised in the browser end to end |
| Invite by email only; the invitee fills in and verifies their own phone on accept (was: admin enters phone and email, invite by text + email) | built and proven — invite sent, accepted, member created pending phone |
| A wrongly typed phone must never strand anyone: re-enter and re-verify any time, before or after accepting | built — the pending number lives server-side until verified; "change your number" re-runs the same flow |
| Verifying a number that is on another account takes it over; the old account is disconnected (was: refused with "already on another account") | built — the OTP proves possession; the old holder loses the phone and every CLI token minted for it |
| A reminder email a day later if the phone is still unverified — email only, once | built, unrun — cron sweep, marked before send |
| Invites valid seven days; expired ones still shown as that person, marked expired | built — expiry state exercised only as "fresh" |
| The page shows who invited you, by name and email | built and proven |
| Roster shows members and pending invites with invited-when; creator is admin; only admins add, remove, change roles | built — remove/role paths written, not yet clicked in production |
| A plus button and email field is the whole add flow | built and proven |
| Intent | Status |
|---|---|
| Billing per organization: two weeks free with 2 hours of calls, no card, then one team plan (session 20; was: 30 trial minutes and $79 for 250 minutes (session 16); before that ~$10 free credit then pay-as-you-go (session 9); before that $99/month, 7-day trial, card up front) | built — plan.ts; the $79 plan had only the owner on it |
| One plan, sold as a founding price: |
built — site, llms.txt, terms, billing tab and Checkout say so; the lock is each subscription's own Stripe price |
| Overage above break-even, not double the cost: 15¢ a minute (session 20; was 40¢, before that 30¢ as "about double") | built — PRICE_PER_MINUTE_CENTS; cost measured 21 Oct at ~5.4¢/min of GPT-Live (the 18 Oct note of 15–18¢ was wrong), ~6.5¢ a meeting minute, up to ~12¢ a phone minute with telephony and Stripe |
| Keep GPT-Live in meetings, listening the whole time; no cheaper model and no switching it on and off, so the bot can become more proactive (session 20) | built — unchanged |
| Billing tab, visible to admins only: credit used and left, per-day statistics, who calls/costs most | built — rendered live with the grant; call stats have no calls to show yet |
| Topping up | built — Stripe is live (keys set by the user; a cs_live_ Checkout session created and verified); no real payment made yet, and the live webhook endpoint's event list is unverified — the billing page's own poll covers it either way |
| Calls debit the org by the minute when they complete | built and proven — a 50s demo call billed 25¢ and two ring-ins 11¢ and 8¢, all in the ledger |
| Intent | Status |
|---|---|
| (Superseded for callers whose own terminal watches live — they get the GPT-Live conversation above. Session 10 had already replaced the beep with a quiet Realtime voice.) A known caller who rings in hears no voice at all — straight to the record beep; only a stranger gets a robot voice, told to install screenless (was: a "press 1 for the assistant" menu; before that, ring-ins always answered by the assistant) | built — menu removed at 22:56; the recording path underneath it is proven by three real ring-ins, which also found the hangup/recordingStatusCallback bug |
| proven once (21:38, in Dutch) before its removal | |
| No keypress = a recording, transcribed, delivered as a request to the team's terminal — not a conversation | built and proven — two spoken requests transcribed by Telnyx Whisper and delivered |
| Requests route to the right terminal: your own first (earliest if you have two), else any teammate's watcher | built and proven — synthetic call exercised own-first, standby and dead-watcher failover; two real requests then routed live |
| Nothing gets lost when no watcher is up: the next watcher to spawn drains the backlog, one by one or in parallel as the agent chooses | built — queue holds seven days; --gate leaves a call unacked until screenless done, so it is re-handed rather than dropped |
| Three items per call, dossier-shaped; assistant answers from the brief and says when it cannot | built and proven — the 22:10 call on 19 Aug |
| Hang up on voicemail rather than brief it | built and proven |
CLI: call "<prompt>" blocks and returns the transcript |
built |
screenless call --to <who> / --all dials any, some, or all teammates; each call queued, transcripts routed to watchers (new — the founding "only ever dials your own verified number" widened to "only a verified number on your own team") |
built and proven — unknown/other-org/no-phone targets refused; --all dispatched a real call |
| Auth by phone number + OTP; only ever dials the verified number | built |
| Calls come from the Dutch number and it can be rung back | built and proven |
| Telnyx, EU media anchoring, third-party voices chosen on latency, ten languages | built |
| Call time configurable (default 08:00); timezone always from the machine | built |
| Sessions last a year | built |
| Intent | Status |
|---|---|
| A logo: an S of two half circles, a dot centred in each bowl; iterate by looking | built — chosen from rendered variants; ink stroke, rust dots, survives 16px |
| The logo and the name on every email, professional HTML, never raw markdown | built — one branded frame; the loop's markdown reports render through a small md→html pass; codes, invites, reminders, paper and reports all covered |
| The team page looks smooth, with the logo and the site's palette | built and proven — screenshots in session |
| Landing page argues the problem; findable and quotable | built and live — pricing section rewritten for pay-as-you-go |
| Intent | Status |
|---|---|
| Delivered by Resend to one confirmed email, bound to the account | built and proven |
| Free; only the call is paid | built |
| Deep dive from real codebase research; structural figures, no churn-per-PR charts | built, unrun |
| The nightly run schedules the call | built and proven |
| Intent | Status |
|---|---|
One command: curl -fsSL https://screenless.sh/install | bash |
built |
The skills install into every coding agent via the real skills.sh CLI — npx skills add janwilmake/screenless — so installs count in its stats; a bundled per-agent copy is the offline fallback (was: a hand-rolled per-agent copier only) |
built and proven — the wrap exits 0 and installs all three skills across agents in a sandbox HOME |
The loop is armed inside a Claude Code session; screenless wait is the gate |
built — proven for the nightly; screenless watch --gate armed beside it is new and unrun |
The installer ships press/ beside the skill |
built — now inside skills/morning-pr-review/, the skill that uses it |
| After the call, the machine acts on the decisions | built and proven |
| A PR comment the apply leg writes is signed and names what it ticketed | built, unrun |
| Intent | Status |
|---|---|
Record every prompt verbatim in intent/, topped up during the session |
built |
| Do ops work in the browser/CLI rather than handing back instructions | built — D1 created, Workers merged and deployed, page exercised live this session |
Push to main, commits grouped by intent |
built |
Things asked for that the product does not yet do, or has not yet proven.
rounds— the actual product. Pull-request ingestion, cross-repo triage, the "this needs your eyes" router. Session 1's premise, still packaging ahead of product.The press-1 assistant path is the last unrun leg of the line.Proven at 21:38 on 21 Aug: a ring-in pressed 1, met the parked brief's assistant, and the conversation was routed to the watcher. Every leg of the line has now carried a real call.Stripe is in test mode.Live keys are in and a live Checkout session was created. Still open: no real payment has run, and whether the live webhook endpoint subscribes tocheckout.session.completedis unverified — the billing page's poll-reconcile covers the gap either way.- The deep dive and new figures are unrendered; no edition has used them.
- Old-model leftovers: the $99 test-mode product in Stripe, and the now entirely unused KV namespace (kept as a backup of pre-migration state; delete it once a quiet week has passed).
- The pending invite left by this session's test — jan@wilmake.com sits in "My team" as a phone-unverified member; tomorrow's cron will send the first real reminder email. A live test, but also cleanup nobody asked for.
- The privacy page still promises transcripts gone in 24 hours, while a queued team call now keeps its transcript up to seven days so it cannot be lost. The page needs a sentence, or the retention needs a rethink.
- Multi-repo watch routing is blunt: the watcher reports its repo but assignment ignores it — a request about repo A can land in a terminal watching repo B if that is the only one up.
- Two API keys pasted into a chat transcript on 15 Aug still need rotating.
Decisions taken without a specific instruction, each reversible cheaply.
Money
- $500 as the price after the founding period, and 12 months as the lock;
the trial's 2 hours as "a real meeting or two". Telnyx's Dutch-mobile rate
and Recall's
web_4_coresurcharge are estimated, not read off an invoice. - Topup buttons at $10 / $25 / $100, custom amounts only via the API; bounds $5–$1000 per topup.
- Billed per second at the minute rate, only for completed calls — a no-answer, a voicemail, or a menu hang-up costs the org nothing.
- A call is allowed to push the balance below zero; the gate stops the next call, not the one in flight.
- One free grant per org, ever; a member removed from a team is parked in a fresh solo org with zero credit rather than a second $10.
- No card at setup at all — the trial's "card up front filters for intent" logic died with the subscription.
Teams
- The web session is a 30-day cookie keyed by user id, signed with the same secret as CLI tokens but not interchangeable with them.
- Sign-in is an emailed 6-digit code; the answer never says whether an address has an account.
- Re-inviting an email replaces the old invite and restarts the seven days — that is also what "resend" does for an expired one.
- The last admin can neither be demoted nor leave for another team without appointing a successor; admins cannot remove themselves.
- A CLI-first user gets an org named "My team" created silently on first sight, and their confirmed paper email doubles as their team-page sign-in.
- An invitee who runs
screenless setupinstead of finishing on the web is matched onto their invited row by email, not given a rival org.
The line and the watcher
- The robot greeting is Jan's sentence plus "after the tone", because the gather has to time out (3 s) before the beep starts and words spoken before it are lost.
- Ring-ins are answered only for verified members of a funded org; strangers and empty balances get one sentence and a hangup.
- Recording caps at 5 minutes, ends on
#or 6 s of silence; transcription is Telnyx-hosted Whisper, inline, so "Got it" is only said once the text is stored. - Watcher heartbeats live 90 s; polls every 10 s double as heartbeats; ties in routing break by earliest start, then id.
- Calling teammates: each is queued and routes by the callee-first rules, so a dispatched briefing lands in the callee's own terminal, not the initiator's — a sensible default, but a skill wanting all transcripts in one place would need its own routing. Dispatch is rate-capped at 10/hour per initiator on top of the org credit.
- Storage: the
stashandcounterstables are KV-shaped leftovers in SQL; expiry is one cron sweep instead of KV TTLs; a confirmed paper email now is the account email, so confirming an address another account holds is refused rather than silently shared. - Queued calls are read 20 at a time per org and their records kept 7 days;
nothing is ever acked on display — only
screenless doneafter the work ran, so every call is at-least-once (was: a display mode that acked on print, dropped with the never-ending default). - The
WORK <callId>line and the two-gate arming (wait+watch) in the loop skill are my wiring of the watcher into the existing session contract.
Look and words
- The logo: 248° arcs (270° read closed, 225° read as an 8), ink stroke, rust dots, the site's palette; the wordmark stays lowercase serif. The old data-URI newspaper favicon was replaced by it.
- Email frame: hosted PNG logo (Gmail strips inline SVG), paper card on wash background, one rust button style; the md→html subset covers what the loop's reports actually use and degrades to paragraphs.
- Landing pricing copy, the "30¢/min · first $10 on us" framing, and the README economics rewrite.
api.screenless.shkept alive as an alias rather than migrating old CLI configs.
Earlier assumptions that still stand
- Rate limits (OTP 5/number/hour, 60/hour global, 300/day; 20 calls/hour; 12 editions/hour), the fraud prefix blocklist, and retention windows (calls 24 h, briefs 48 h, outbox 7 days) — all invented in sessions 1–6.
- The ten languages and their voices; the AI-disclosure greetings.
- The three-file split of skill, project config and machine registry.
- The terms and privacy pages' wording; the landing page's design-partner
framing. (
GTM.mdandTODO.mdwere removed in session 9 — both were built on the superseded $99 subscription and the old single-agent, KV, Stripe-test world, and were more stale than useful.)