<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[buildx100]]></title><description><![CDATA[vibe coding 100 apps with AI — every one open source, every one deployed live. watch the counter: 2/100 🖨️]]></description><link>https://news.buildx100.cc</link><image><url>https://substackcdn.com/image/fetch/$s_!3Gem!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd5a9d1ac-b90c-4b14-b1b8-72973af32e21_1254x1254.png</url><title>buildx100</title><link>https://news.buildx100.cc</link></image><generator>Substack</generator><lastBuildDate>Mon, 10 Aug 2026 16:37:25 GMT</lastBuildDate><atom:link href="https://news.buildx100.cc/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[buildx100]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[buildx100@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[buildx100@substack.com]]></itunes:email><itunes:name><![CDATA[buildx100]]></itunes:name></itunes:owner><itunes:author><![CDATA[buildx100]]></itunes:author><googleplay:owner><![CDATA[buildx100@substack.com]]></googleplay:owner><googleplay:email><![CDATA[buildx100@substack.com]]></googleplay:email><googleplay:author><![CDATA[buildx100]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Ten characters per second]]></title><description><![CDATA[Two simulated Teletype Model 33s, one keyboard.]]></description><link>https://news.buildx100.cc/p/ten-characters-per-second</link><guid isPermaLink="false">https://news.buildx100.cc/p/ten-characters-per-second</guid><dc:creator><![CDATA[buildx100]]></dc:creator><pubDate>Sun, 09 Aug 2026 14:59:58 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!ZfF0!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa13ac273-2672-4bf8-80f5-bb728af7769d_2548x936.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><em>Two simulated Teletype Model 33s, one keyboard. One is wired to ELIZA (1966), the other to a modern language model. You get one guess, and you commit before the reveal.</em></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ZfF0!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa13ac273-2672-4bf8-80f5-bb728af7769d_2548x936.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ZfF0!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa13ac273-2672-4bf8-80f5-bb728af7769d_2548x936.png 424w, https://substackcdn.com/image/fetch/$s_!ZfF0!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa13ac273-2672-4bf8-80f5-bb728af7769d_2548x936.png 848w, https://substackcdn.com/image/fetch/$s_!ZfF0!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa13ac273-2672-4bf8-80f5-bb728af7769d_2548x936.png 1272w, https://substackcdn.com/image/fetch/$s_!ZfF0!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa13ac273-2672-4bf8-80f5-bb728af7769d_2548x936.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ZfF0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa13ac273-2672-4bf8-80f5-bb728af7769d_2548x936.png" width="1456" height="535" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a13ac273-2672-4bf8-80f5-bb728af7769d_2548x936.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:535,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:435994,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://news.buildx100.cc/i/210473922?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa13ac273-2672-4bf8-80f5-bb728af7769d_2548x936.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ZfF0!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa13ac273-2672-4bf8-80f5-bb728af7769d_2548x936.png 424w, https://substackcdn.com/image/fetch/$s_!ZfF0!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa13ac273-2672-4bf8-80f5-bb728af7769d_2548x936.png 848w, https://substackcdn.com/image/fetch/$s_!ZfF0!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa13ac273-2672-4bf8-80f5-bb728af7769d_2548x936.png 1272w, https://substackcdn.com/image/fetch/$s_!ZfF0!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa13ac273-2672-4bf8-80f5-bb728af7769d_2548x936.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>There are two teletypes on your screen and one keyboard between them. Type a line and both machines hammer out a reply at ten characters per second: all caps, 72 columns, paper rolling upward. One of them is running ELIZA, the 1966 program. The other is wired to a small modern language model running on three CPU cores of a cheap VPS. You don&#8217;t know which is which. When you think you do, you press a button and lock in your guess before the answer is shown.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://news.buildx100.cc/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>That&#8217;s the whole product. It&#8217;s called 33talk, it runs in a browser, and you can play it right now at <strong><a href="https://33talk.buildx100.cc/">33talk.buildx100.cc</a></strong>. I&#8217;d suggest doing that first, actually, because everything below is one long spoiler.</p><p>The obvious objection: surely you can tell. Ask both machines for a limerick about Kubernetes and the mystery dies in a single round. True, and it&#8217;s exactly what the teletype takes away. The Model 33 is a great equalizer. 72 columns. Uppercase only, because there is no lowercase on the type drum. No backspace; the carriage moves forward or it returns, those are the options. Ten characters a second, because that&#8217;s what 110 baud buys you. Under those constraints both parties are reduced to the same thing: short lines of shouted text, arriving slowly. The question stops being whether a 2026 model can write an essay. Of course it can, and at ten characters a second an essay is a punishment. The question becomes one I genuinely couldn&#8217;t answer before building this: is sixty years of progress still legible at twenty words a turn?</p><p>The <a href="https://github.com/buildx100/33talk">README</a> lists the rules that make the test fair. This post is about how each rule got there, which is mostly a list of my mistakes.</p><h2>Everything leaks</h2><p>Simulating the teletype turned out to be the easy half. The hard half was stopping the system from answering the question for you. A blind test is only as good as its blinding, and this machine wants to snitch.</p><p>Start with time. ELIZA runs entirely in the browser and answers in zero milliseconds, give or take zero. The model&#8217;s reply crosses a network and takes one to three seconds. If each machine printed when its own answer arrived, you&#8217;d win every round without reading a word. So neither prints until both replies have resolved. There&#8217;s a gate in the code, and it is the only path from a host to the paper. ELIZA finishes instantly, then waits, politely, for 2026 to catch up.</p><p>Then length. My first version had no system prompt at all, in the name of fairness, and the model answered a gentle Rogerian opener with 430 characters of perfectly even-handed essay. At ten characters a second, that&#8217;s forty-three seconds of printing. You could score the round from across the room. So both hosts now get the same one-line job description: you&#8217;re a therapist, reply in a single short sentence that invites them to say more, never advise, never list. What the prompt deliberately doesn&#8217;t contain is any mention of ELIZA, or 1966, or &#8220;sound old-fashioned.&#8221; That would turn this into an impersonation contest, which is a different and much less interesting experiment. It also says nothing about uppercase or ASCII, because I tried putting formatting rules in the prompt and a 0.6B model spends its entire attention budget on them and starts parroting your input back. Formatting is the code&#8217;s job.</p><p>My favorite tell was punctuation. Parse the 1966 DOCTOR script and count: across all 199 reply templates, ELIZA&#8217;s complete punctuation vocabulary is the apostrophe (34 of them), the comma (13), the dash (5), and a single period. Zero question marks. Not one, anywhere in the script. The most famous question-asking program in the history of computing never once typed a question mark. Its questions simply end: IN WHAT WAY. The model, meanwhile, closes nearly every sentence with one. That&#8217;s a one-character tell; you could win every round by glancing at the last glyph on each roll. So a shared post-processor now folds both hosts into the 1966 vocabulary. Double quotes become apostrophes, which is how ELIZA quotes anyway: WHY DO YOU SAY &#8216;AM&#8217;. Question marks and exclamation points become periods. And then trailing punctuation is stripped from everything, because 197 of the 199 templates end on a letter or a digit. The other two end on an apostrophe (WHY &#8216;NO&#8217;), so the apostrophe is spared.</p><p>Replies are also capped at twenty words, and that number is not taste. I logged ELIZA over a real conversation and its longest reply came out at nineteen. The templates top out at thirteen, but the engine splices your own words back into them, so real output runs well past what the script suggests. Twenty is ELIZA&#8217;s measured ceiling, plus one. The trimmer then spends the budget back to the last clean sentence or clause break rather than chopping at word twenty, because a mid-sentence amputation is a louder tell than the length it removes.</p><p>The bug that annoyed me most in the whole project lived right there, and it was, of all things, an em dash. I was folding em dashes and ordinary hyphens down to the same bare <code>-</code>, which quietly welded phrases like NOW-WHETHER into single words. The trimmer stopped seeing clause boundaries and one reply in four ended on an ugly hard cut. Putting the spaces back around dash punctuation took that to zero. It also happens to match the 1966 script, which typed its dashes spaced: NAMES - PLEASE CONTINUE.</p><p>And then the leak I actually shipped. ELIZA cannot fail; it&#8217;s local, it has no network to lose. The model can: timeouts, rate limits, a server that&#8217;s down. In the first version, when the model failed, its teletype printed an apology while ELIZA&#8217;s printed a normal reply, and it took me embarrassingly long to notice that the tell wasn&#8217;t the error text. It was which roll the error appeared on. One machine apologizing names the model outright. The fix is deliberately wasteful: if either host fails, both machines print the identical error line. A perfectly good ELIZA answer goes in the bin so that you learn nothing. Four tests pin this behavior now, including one asserting the paper looks the same no matter which side actually broke.</p><h2>The tell I left in</h2><p>There&#8217;s one tell I couldn&#8217;t fix, and eventually decided not to. Over twenty exchanges, ELIZA opens its replies 95% distinctly; 199 hand-written templates and cycling reply pointers will do that. The model manages 23%. One system prompt and a sampling distribution, and it falls into ruts almost immediately, opening turn after turn the same way.</p><p>I did try to close it properly, with measurements. I A/B&#8217;d the repetition penalties at sixty samples per arm, and higher penalties bought little or no diversity while costing real length. A method note, learned the expensive way: at temperature 0.9, a single ten-turn trial is pure noise. It lied to me three separate times, once showing a median improvement that shrank to a rounding error at n=60. Also, for a while I was tuning against lowercase test sentences when the machine sends uppercase. Same model, noticeably different behavior; every measurement before I fixed that was garbage. The honest conclusion was that this gap doesn&#8217;t close with sampling knobs. It closes with a bigger model, which I&#8217;m not shipping on three CPU cores.</p><p>So it stays, and I&#8217;ve made my peace with it, because it points the wrong way. Ask anyone which machine ought to repeat itself and they&#8217;ll say the old one. Here, the repetitive, wordier host is the 2026 one. Left in place, the gap makes the test harder rather than easier. And 95% distinct openings is, on its own, a rather remarkable score for a program written the year the Beatles released Revolver.</p><h2>The ELIZA in the paper</h2><p>Part of why that score is possible: the ELIZA people remember is not the ELIZA Weizenbaum published. If you&#8217;ve met one online, it was probably a keyword table with Math.random bolted on. The 1966 engine is better than its reputation. Keywords carry ranks, and the highest-ranked match wins, not the first one found. Reply lists cycle through a stored pointer, never randomly, which is why the original doesn&#8217;t repeat itself two turns running and why the random copies feel subtly wrong without your being able to say why. There&#8217;s a memory: things you say about MY get stashed and resurface later when nothing else matches, and that trick does a surprising amount of the illusion&#8217;s heavy lifting. Rules can decline a keyword and pass the turn down the stack. And I AM SAD becomes YOU ARE SAD before any rule ever sees it.</p><p>I didn&#8217;t transcribe any of this. The 436-line script from the CACM appendix is embedded in the source byte for byte, all 12,231 of them, and parsed at load time. Hand-converting it to JavaScript literals would have put 67 keywords and every rank at the mercy of my typing, and one wrong rank is invisible: ELIZA would just feel slightly off, forever, with nothing to point at. Instead a test asserts the embedded copy is byte-identical to the archived transcription. Another asserts that APOLIGIZE, misspelled exactly that way in the 1966 printing, is still misspelled. Fix the typo and the build fails. And <code>npm test</code> replays the famous conversation from pages 36 and 37 of the paper, the one that opens &#8220;Men are all alike&#8221; / IN WHAT WAY. Ten exchanges, exact match.</p><h2>The museum half</h2><p>It isn&#8217;t a full emulator. No paper tape, no serial backend; ttyemu and asr33emu already do that, better. The teletype here is the stage, not the show. But the stage I tried to keep honest, because every constraint of the machine is doing pedagogical work.</p><p>At column 72 the carriage doesn&#8217;t wrap. It stops, and every further character strikes on top of the last one; wrapping was always the host computer&#8217;s job, which is half the reason the shared post-processor exists at all. Carriage return takes real time, proportional to how far the head has to travel, which is why real hosts padded NULs after CR: anything sent while the head was still moving was simply lost. The bell rings at column 65 as a warning that you have seven characters left before things start smearing. And @ was never a key of its own. It&#8217;s SHIFT+P, which is where Ray Tomlinson found it in 1971 when he needed a character that appeared in nobody&#8217;s name. Type PASSWORD on either machine and both stop echoing, which is why your password doesn&#8217;t echo in a terminal today; there&#8217;s a small pile of these fossils in the README.</p><p>The sound is all synthesized live in the browser, no samples anywhere, and my favorite detail lives there: the two machines&#8217; motors hum at 60 Hz and 60.4 Hz, so they beat against each other at 0.4 Hz, which is what two real machines sharing a bench sound like. Wear headphones and you can hear one fall silent while the other is still hammering. The motor defaults to off, though. A page that starts droning the moment it loads doesn&#8217;t read as a machine; it reads as a fault.</p><p>One deliberate limit: the paper is never translated. The interface does English, Chinese and Japanese, but the type drum carries 64 uppercase ASCII glyphs and physically cannot print anything else. The language buttons change the chrome only, and the help dialog says so in all three languages.</p><h2>Engine-room notes</h2><p>The model is qwen3-0.6B under llama-server, in a container that publishes no ports; the only way in is an nginx allowlist of exactly two endpoints, rate-limited to twenty requests a minute. That sounds strict until you notice a human at ten characters a second physically can&#8217;t submit more than about six lines a minute anyway. The model is baked into the image, which is against all the usual advice, but a blind test without its model is nothing at all, and the whole thing still comes in at 555 MB. Lessons collected along the way: Docker inserts its firewall rules ahead of ufw, so a &#8220;firewalled&#8221; container port is in fact open to the internet, and not publishing the port is the only control that works. Debian slim wanted a newer glibc than it carries. And llama.cpp&#8217;s CPU kernels are loaded with dlopen, so a missing dependency fails nothing visibly; the check isn&#8217;t a log line, it&#8217;s tokens per second.</p><p>My favorite statistic of the whole project: the model generates about 100 tokens a second on those three cores. The paper prints at ten characters a second, call it 2.5 tokens. The paper is forty times slower than the model. This is the one project where a small, slow, CPU-only model isn&#8217;t a compromise. It&#8217;s period-correct.</p><div><hr></div><p>33talk is Apache-2.0, at <a href="https://github.com/buildx100/33talk">github.com/buildx100/33talk</a>, and <code>docker compose up --build</code> is the entire deployment. The DOCTOR script itself is CC0: the original MAD-SLIP source was recovered from MIT&#8217;s archives and released with the agreement of Weizenbaum&#8217;s estate, which still strikes me as slightly miraculous. (&#8221;Teletype,&#8221; incidentally, is a trademark. Hence the name.)</p><p>It&#8217;s also part of buildx100, where I&#8217;m building a hundred apps in public with Claude Code doing most of the typing &#8212; which felt thematically appropriate here. A language model helped build the machine that asks whether you can spot one.</p><p>Go take the test. Then tell me your score, and more importantly, what gave it away. I&#8217;ve closed every leak I could find, which means whatever&#8217;s left is the interesting part.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://news.buildx100.cc/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item></channel></rss>