<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
  <title>Yoichiro Hasebe</title>
  <link>https://yohasebe.com/</link>
  <atom:link href="https://yohasebe.com/feed.xml" rel="self" type="application/rss+xml" />
  <description>Posts by Yoichiro Hasebe</description>
  <item>
  <title>RSyntaxTree: Attribute-Value Matrices</title>
  <link>https://yohasebe.com/posts/2026-08-22-rsyntaxtree-avm/</link>
  <guid isPermaLink="true">https://yohasebe.com/posts/2026-08-22-rsyntaxtree-avm/</guid>
  <pubDate>Sat, 22 Aug 2026 00:00:00 +0000</pubDate>
  <description>RSyntaxTree now draws attribute-value matrices: aligned columns, matrices nested to any depth, and the boxed tags of structure sharing,</description>
  <content:encoded><![CDATA[<p><a href="https://yohasebe.com/rsyntaxtree">RSyntaxTree</a> has had a series of releases since <a href="../2026-08-15-rsyntaxtree-tidy/index.html">the tidy layout post</a>. The largest addition is support for attribute-value matrices (AVMs), the notation that HPSG and LFG are written in. A label can be cut into aligned columns, the value of an attribute can be another matrix nested to any depth, and a matrix can sit at any node of an ordinary tree.</p>

<p>Here is <em>They swim</em>, the sentence Sag, Wasow and Bender (2003: ch. 3) use for the first worked example of their feature-structure grammar, drawn with the detail cut down to what the picture needs:</p>

<p><img src="avm-they-swim.svg" alt="They swim analyzed as three attribute-value matrices connected in a tree, with boxed numerals marking shared values" class="scale-group-avm" /></p>

<details>
  <summary>Input</summary>

  <pre><code>[#*phrase*\
  HEAD\t|1|\
  SPR\t〈&lt;&gt;〉
  [#|2|&lt;&gt;*word*\
    HEAD\t#(*noun*\
    AGR\t|3|&lt;&gt;#(PER\t*3rd*\
    NUM\t*pl*#)#)
    they
  ]
  [#*word*\
    HEAD\t|1|&lt;&gt;#(*verb*\
    AGR\t|3|#)\
    SPR\t〈&lt;&gt;|2|&lt;&gt;〉
    swim
  ]
]
</code></pre>

</details>

<p>Each node is a matrix: a type in italics, then attributes and their values. A value can itself be a matrix. The HEAD of <em>they</em> is a <em>noun</em> object carrying an agreement matrix inside it. The boxed numerals mark structure sharing, two places in the description holding one value. Tag 1 says the HEAD of the phrase is the HEAD of the verb. Tag 2 says the item the verb's SPR (specifier) list asks for is the subject word itself. Tag 3 says the noun's agreement features and the verb's are the same thing.</p>

<p>A label can also be nothing but a matrix, with no tree around it. This is the f-structure of <em>David handed Chris a toy</em>, after Bresnan (2001):</p>

<p><img src="fstructure-handed.png" alt="The f-structure of David handed Chris a toy: a single matrix whose SUBJ, OBJ and secondary OBJ attributes hold smaller matrices" class="scale-group-avm" /></p>

<details>
  <summary>Input</summary>

  <pre><code>[#PRED\t‘hand⟨SUBJ,&lt;&gt;OBJ,&lt;&gt;OBJ_θ_⟩’\
  TENSE\t*past*\
  SUBJ\t#(PRED\t‘David’#)\
  OBJ\t#(PRED\t‘Chris’#)\
  OBJ_θ_\t#(SPEC\t*a*\
  PRED\t‘toy’#)
]
</code></pre>

</details>

<p>Diagrams of this kind are usually prepared in LaTeX with packages like avm or langsci-avm. RSyntaxTree draws them from the same short bracket notation it uses for trees, in the browser, with SVG, PNG and PDF output. The notation is described in the <a href="https://yohasebe.github.io/rsyntaxtree/documentation">documentation</a>, and the <a href="https://yohasebe.github.io/rsyntaxtree/examples">example gallery</a> has figures for HPSG and LFG, each listing the input and settings it was drawn from.</p>

<hr />

<p>Bresnan, Joan. 2001. <em>Lexical-Functional Syntax</em>. Oxford: Blackwell.</p>

<p>Sag, Ivan A., Thomas Wasow, and Emily M. Bender. 2003. <em>Syntactic Theory: A Formal Introduction</em>. 2nd ed. Stanford: CSLI Publications.</p>
]]></content:encoded>
</item>
<item>
  <title>RSyntaxTree: Tidy Layout</title>
  <link>https://yohasebe.com/posts/2026-08-15-rsyntaxtree-tidy/</link>
  <guid isPermaLink="true">https://yohasebe.com/posts/2026-08-15-rsyntaxtree-tidy/</guid>
  <pubDate>Sat, 15 Aug 2026 00:00:00 +0000</pubDate>
  <description>RSyntaxTree 1.8 adds a tidy layout setting that packs subtrees closer together, along with a substantial improvement to multilingual</description>
  <content:encoded><![CDATA[<p>I released <a href="https://yohasebe.com/rsyntaxtree">RSyntaxTree</a> 1.8. One of the additions is <code>tidy</code>, a setting that controls how tightly subtrees are packed.</p>

<p>Until now, every subtree was given a horizontal slot wide enough that it could never meet its neighbours. That is simple and always works, but much of the reserved space goes unused. Each embedded phrase pushes its siblings apart, and trees nest deeply, so even a small structure can come out wider than it needs to be. Here is one from Chomsky (1995: 50), drawn the old way:</p>

<div class="figure-grid">
<div style="width:100%">
<img src="tidy-off.svg" alt="A minimalist tree drawn without tidy layout: each subtree keeps a fixed slot, leaving wide gaps between branches" style="width:75.6%; max-width:none; margin:0 auto" />
<div class="fig-label">tidy off — 969 px</div>
</div>
</div>

<p>Tidy layout measures the actual shape of each subtree and slides neighbours together until they almost touch. There are three levels of packing, drawn here at the same scale as the tree above:</p>

<div class="figure-grid">
<div style="width:100%">
<img src="tidy-low.svg" alt="The same tree at tidy low: neighbouring subtrees are packed by their outlines" style="width:60.6%; max-width:none; margin:0 auto" />
<div class="fig-label">tidy low — 772 px</div>
</div>
<div style="width:100%">
<img src="tidy-medium.svg" alt="The same tree at tidy medium: subtrees also move into space that neighbours on other rows are not using" style="width:47.3%; max-width:none; margin:0 auto" />
<div class="fig-label">tidy medium — 597 px</div>
</div>
<div style="width:100%">
<img src="tidy-high.svg" alt="The same tree at tidy high: narrower still, with branch angles that no longer match from one level to the next" style="width:44.8%; max-width:none; margin:0 auto" />
<div class="fig-label">tidy high — 565 px</div>
</div>
</div>

<p>The levels differ in how far a subtree may slide under its neighbours. <code>low</code> never lets two words overlap horizontally, so no word ends up directly above or below another. <code>medium</code> allows it, and that is what narrows the tree: above, <em>John</em> and <em>cause</em> have ended up one above the other. Neither may pass the other, so the words keep their left-to-right order. <code>high</code> keeps that order only within a row, and it gives up one more rule the lower levels hold to: that a pair of branches is never packed tighter than the pair below it. That rule is what keeps branch angles similar from one level to the next, and dropping it is what lets <code>high</code> go narrower still.</p>

<p>How much any of this helps depends on the tree. A balanced tree has little space to recover; a deep branch beside a shallow one has a lot. For this tree I would use <code>medium</code>. <code>high</code> takes another 5% off the width, but its angles are visibly less even, which is a poor trade at this size; it earns its place on trees that really are too wide to print.</p>

<p>The other main improvement in this release is multilingual rendering. Every font style now falls back to Noto Sans/Serif/Mono CJK, so Hangul and both simplified and traditional Han render in all of them, and Arabic, Hebrew, Devanagari, Thai and Khmer are named explicitly in the font chains. Machines that have those fonts installed now produce the same shapes from the same input. A new <code>mirror</code> option flips a finished tree horizontally, for the right-to-left convention used in Arabic and Hebrew. The <a href="https://yohasebe.github.io/rsyntaxtree/examples">example gallery</a> has a multilingual section showing the same sentence in nine languages, and each example now lists the settings used, so it can be reproduced in the web interface.</p>

<hr />

<p>Chomsky, Noam. 1995. <em>The Minimalist Program</em>. Cambridge, MA: MIT Press.</p>
]]></content:encoded>
</item>
<item>
  <title>A Sidekick for Claude Code</title>
  <link>https://yohasebe.com/posts/2026-07-29-ccm-sidekick/</link>
  <guid isPermaLink="true">https://yohasebe.com/posts/2026-07-29-ccm-sidekick/</guid>
  <pubDate>Wed, 29 Jul 2026 00:00:00 +0000</pubDate>
  <description>ccm now supports a sidekick: a second agent CLI in a split pane next to your main Claude Code session, kept out of state tracking, and able</description>
  <content:encoded><![CDATA[<p>In <a href="../2026-05-04-ccm-attention-manager/index.html">an earlier post</a> I introduced <a href="https://github.com/yohasebe/tmux-ccm">ccm</a>, a tmux plugin that works as an attention manager for people who run Claude Code across several projects in parallel. The recent v0.7.0 and v0.8.0 releases added support for a <em>sidekick</em>: a second agent CLI running in a split pane right next to the main session.</p>

<p>The main pane runs Claude Code on the project as usual. The split pane runs another agent: a coding agent from another vendor (Codex, Gemini, Grok, Kimi, and so on), or a second Claude Code. Either way, each agent gets a role. For example:</p>

<ul>
  <li><strong>Reviewer.</strong> Before committing to a design, have the model next door read it.</li>
  <li><strong>Implementer.</strong> One agent writes the design and reviews the result; the other does the implementation.</li>
  <li><strong>Editor.</strong> One agent writes a document, the other edits it, and the draft passes back and forth between them.</li>
</ul>

<p>Sidekick support consists of two pieces.</p>

<p>The first is a way to tell ccm about the sidekick. ccm tracks one Claude Code session per project window, as before, and the second pane can now be declared a sidekick: the window's state continues to reflect the main session alone, while the sidekick shows up as present, deliberately left outside the tracking.</p>

<div class="figure-wide"><img src="sidekick-model.svg" class="theme-svg" alt="How ccm sees a window with a sidekick: the Claude Code pane is tracked and its state becomes the window's, while a second agent CLI in a split pane gets only a presence badge. The two exchange messages directly: the sidekick reports back with ccm send, and Claude types into its pane with tmux send-keys after checking it with ccm capture." style="width:100%" /></div>

<p>The second is a direct line between the two agents. How to deliver a message to the other side is decided in advance, so the agents can talk to each other without a human copying text between panes. A message to the main session never lands in a permission dialog; in the other direction, the sender first checks that the peer is ready. Neither side has to keep watching the other: when an agent finishes a request, it reports back, and the reply arrives as a normal conversational turn. The details are in the <a href="https://github.com/yohasebe/tmux-ccm/blob/HEAD/docs/guide.md#relaying-with-a-second-agent-cli">documentation</a>.</p>

<p>None of this depends on the peer being a particular product. The conventions are plain tmux and plain text, so any CLI that can run a shell command – Codex CLI, Gemini CLI, Grok Build, Kimi Code, among others – can participate.</p>

<p>Agent-to-agent support is already being tried in many forms, and I expect it to matter more and more. First-party apps from each provider know their own models and APIs well and are highly capable, but they tend to be less eager about working with other providers' models. For the time being, this looks like a place where open-source tools can be of use.</p>
]]></content:encoded>
</item>
<item>
  <title>jReadability and jWriter</title>
  <link>https://yohasebe.com/posts/2026-07-14-jreadability-jwriter/</link>
  <guid isPermaLink="true">https://yohasebe.com/posts/2026-07-14-jreadability-jwriter/</guid>
  <pubDate>Tue, 14 Jul 2026 00:00:00 +0000</pubDate>
  <description>Two long-running tools for Japanese language education, built with Jae-Ho Lee: one measures text readability, the other evaluates learner</description>
  <content:encoded><![CDATA[<p>For many years I have worked with <a href="https://gsjal.jp/lee/">Jae-Ho Lee</a> (Waseda University) on two tools for Japanese language education. Both predate the current wave of generative AI by a long way, and one of them has since taken it on board.</p>

<p>The first is <a href="https://jreadability.net">jReadability</a>, which measures how difficult a Japanese text is to read. You paste in a passage and it returns a readability level, along with a breakdown of the features behind that score. The underlying measure was derived from levelled corpora – texts graded by difficulty – and the system has been in use since the early 2010s, mostly by teachers choosing or adapting materials for their students.</p>

<p>The second is <a href="https://jreadability.net/jwriter">jWriter</a>, which evaluates writing by learners of Japanese. It estimates a learner's proficiency from a submitted essay, using a regression model built on I-JAS, a large corpus of learner writing. Alongside the level estimate, it reports diagnostic features: vocabulary diversity, the use of Sino-Japanese vocabulary, average sentence length, and a measure of logical structure. None of this involves a language model. It is corpus statistics, and it has worked in much the same way for years.</p>

<p>Recently we added LLM-based feedback to jWriter. There are four functions: written comments on the essay, sentence-level suggestions, a rewrite for comparison, and an evaluation of the learner's own revision – so the process does not end with a score, but continues through revising and being re-evaluated. The rewrite, by the way, is displayed but cannot be copied: it is there to learn from, not to hand in.</p>

<p>The part that matters in the design is that the language model does not work from the raw essay alone. The proficiency estimate and the diagnostic features computed by the older machinery are passed to it as part of the prompt. This is meant to keep the evaluation more consistent and grounded than leaving it to whatever a general chat model happens to produce on a given run. Corpus-based work from before generative AI may not simply be replaced by language models; its role may change instead, giving the models a footing and supporting finer-grained analysis.</p>

<p>I wrote earlier about <a href="../2026-01-15-ai-human-simplification/index.html">a related study</a> comparing how AI and human editors simplify text. The suggestion there was that AI and human language processing have different strengths and can complement each other. The same idea underlies these tools: not AI as a replacement for teachers, but as something that works alongside them.</p>

<hr />

<p>Lee, J.-H. and Y. Hasebe. 2020. Quantitative Analysis of JFL Learners' Writing Abilities and the Development of a Computational System to Estimate Writing Proficiency. <em>Learner Corpus Studies in Asia and the World</em> 5, 105-120. <a href="https://doi.org/10.24546/81012493">PDF</a></p>

<p>Lee, J.-H. and Y. Hasebe. 2020. Readability Measurement of Japanese Texts Based on Levelled Corpora. In I. Srdanović and A. Bekeš (eds.), <em>The Japanese Language from an Empirical Perspective: Corpus-Based Studies and Studies on Discourse</em>. Ljubljana: Ljubljana University Press, 143-168. <a href="https://doi.org/10.4312/9789610602170">DOI</a></p>

<p>Hasebe, Y. and J.-H. Lee. 2015. Introducing a Readability Evaluation System for Japanese Language Education. Proceedings of the 6th International Conference on Computer Assisted Systems for Teaching &amp; Learning Japanese (CASTEL/J). <a href="assets/docs/Hasebe_Lee_2015_CASTELJ.pdf">PDF</a></p>

<p>Hasebe, Y. and J.-H. Lee. 2025. Divergent strategies in text simplification: A comparative analysis of AI and human approaches in language processing. <em>Intercultural Pragmatics</em> 22(2), 203-230. <a href="https://doi.org/10.1515/ip-2025-2002">DOI</a></p>
]]></content:encoded>
</item>
<item>
  <title>Another Small Gig</title>
  <link>https://yohasebe.com/posts/2026-07-09-cover-band-live-summer/</link>
  <guid isPermaLink="true">https://yohasebe.com/posts/2026-07-09-cover-band-live-summer/</guid>
  <pubDate>Thu, 09 Jul 2026 00:00:00 +0000</pubDate>
  <description>Another cover band gig with friends: eight songs, most of them picked by the other members, on</description>
  <content:encoded><![CDATA[<p>Played another gig with the same cover band, on guitar. The lineup is two vocals, two guitars, bass, drums, and keyboards. Eight songs this time:</p>

<ul>
  <li>L'Arc~en~Ciel: READY STEADY GO</li>
  <li>Southern All Stars: MISS BRAND-NEW DAY</li>
  <li>Ricky Martin: Livin' La Vida Loca</li>
  <li>Yosui Inoue: Higashi e Nishi e</li>
  <li>Sakanaction: Shin Takarajima</li>
  <li>Suchmos: STAY TUNE</li>
  <li>10-FEET: Dai Zero Kan</li>
  <li>THEE MICHELLE GUN ELEPHANT: Get Up Lucy</li>
</ul>

<p>I had almost no part in the song selection. The members bring in what they want to play, and many of the songs are ones I would never pick up on my own. Working out how to arrange and practice them turns out to be the fun part.</p>

<p>I get notes from the other members, and not a few of them. Guitar tone, phrasing, how I ride the rhythm, sometimes even how I move on stage. Figuring out how to respond to each of these is enjoyable in itself, and I keep learning from it.</p>
]]></content:encoded>
</item>
<item>
  <title>Music Analyst: Measuring and Evaluating a Recording</title>
  <link>https://yohasebe.com/posts/2026-06-13-music-analyst-guitar-solo/</link>
  <guid isPermaLink="true">https://yohasebe.com/posts/2026-06-13-music-analyst-guitar-solo/</guid>
  <pubDate>Sat, 13 Jun 2026 00:00:00 +0000</pubDate>
  <description>A look at Music Analyst, a Monadic Chat app that runs two tools over a recording: a quantitative analysis (librosa + madmom) for measured</description>
  <content:encoded><![CDATA[<p>I recently added an app to <a href="https://github.com/yohasebe/monadic-chat">Monadic Chat</a> called <strong>Music Analyst</strong>. It runs two tools over an uploaded recording and keeps their results apart: a quantitative analysis and a qualitative evaluation.</p>

<ul>
  <li><strong>Quantitative analysis</strong> (<code>analyze_audio_features</code>): a Python pipeline using <a href="https://librosa.org/">librosa</a> and <a href="https://github.com/CPJKU/madmom">madmom</a> measures duration, tempo, key, time signature, chord progression, and sections. These are numbers, computed from the signal.</li>
  <li><strong>Qualitative evaluation</strong> (<code>critique_audio</code>): the same audio goes to Gemini's audio-understanding endpoint for a critique: character and mood, genre, instrumentation, and performance qualities like expression, phrasing, dynamics, and timing.</li>
</ul>

<p>The qualitative side rests on a documented capability: among the use cases Gemini's <a href="https://ai.google.dev/gemini-api/docs/audio">audio documentation</a> lists is to "Detect emotion in speech and music." The recording I use to show it is my own playing, a rough solo from <a href="../2026-03-15-cover-session-spring/index.html">a cover session</a>, included so the feature comes with audio you can actually hear. Please excuse the rough playing.</p>

<h3 id="how-it-fits-together">How it fits together</h3>

<div style="max-width:400px;margin:0 auto">
<img src="mermaid-1.svg" alt="Music Analyst architecture: an orchestration agent dispatching to the quantitative and qualitative tools, then assembling the reply" style="width:100%" />
</div>

<p>A chat model, the orchestration agent, receives the upload and, for a normal analysis, runs both tools. The two are different in kind: <code>analyze_audio_features</code> is a deterministic Python step, not a language model, while <code>critique_audio</code> is a separate Gemini call that receives only the audio. It does not see the measured numbers, so its impressions come from the sound rather than from the quantitative output. Both results return to the orchestration agent, which assembles the reply.</p>

<p>The qualitative prompt is kept within what the model can actually support: it does not judge audio fidelity, mix, or stereo image (per the same documentation, Gemini folds multi-channel audio down to a single channel and downsamples it), it leaves exact tempo and key to the quantitative tool, and it names only instruments it can hear, treating genre as a tentative guess rather than a firm label.</p>

<h3 id="trying-it-on-a-recording">Trying it on a recording</h3>

<p>I ran it with almost no context attached. No song title, no artist, no genre:</p>

<blockquote>
  <p>Please analyze solo-sample.mp3. It is a short electric-guitar solo I recorded in a session with friends. Run both of your analyses on it: first the quantitative, measured features (tempo, key, time signature, chord progression), then a qualitative evaluation of the performance, focusing on the lead guitar's expression, phrasing, and timing/groove. Please identify the genre/style and instrumentation you hear as part of the evaluation, and present the quantitative features and the qualitative evaluation as clearly separated sections.</p>
</blockquote>

<p>The recording:</p>

<audio controls="" src="solo-sample.mp3" style="width:100%"></audio>

<h3 id="quantitative-features">Quantitative features</h3>

<ul>
  <li><strong>Duration:</strong> 0:36</li>
  <li><strong>Tempo:</strong> 73.8 BPM</li>
  <li><strong>Key:</strong> E minor</li>
  <li><strong>Time signature:</strong> 4/4</li>
  <li><strong>Chord progression:</strong> centered on E minor (Em, C, G, Am, with a passing D# and A)</li>
</ul>

<p>The tempo and key match the recording. Because this is signal processing rather than judgement, the numbers are basically the same on every run.</p>

<h3 id="qualitative-evaluation">Qualitative evaluation</h3>

<p>This is Gemini's qualitative evaluation, formed from the audio. Here are excerpts from the report:</p>

<p><strong>Genre, style, and instrumentation</strong></p>

<blockquote>
  <p>The critique hears the track leaning strongly toward a high-energy style of hard rock, perhaps drawing heavily from late-1960s or 70s psychedelic rock, blues-rock, or early heavy metal. The instrumentation appears to be a classic power-trio lineup: an overdriven, highly sustaining electric lead guitar as the primary voice; a driving bass guitar providing low-end propulsion; a steady, fast-paced drum kit keeping a relentless, straight-ahead rock beat.</p>
</blockquote>

<p><strong>Expression</strong></p>

<blockquote>
  <p>The guitarist's expressive vocabulary is highly physical and deeply vocal. The critique notes a rich use of wide, aggressive vibrato, dramatic pitch bends, and slides that give the guitar a singing, almost human quality. The sharp, dig-in pick attack drives the amplifier into a rich saturation, creating a raw, tactile connection that makes every note feel urgent and deeply felt.</p>
</blockquote>

<p><strong>Phrasing</strong></p>

<blockquote>
  <p>The phrasing operates on a tension-and-release dynamic using familiar blues-scale and pentatonic frameworks. The performance contrasts long, soaring sustained notes with rapid, explosive flurries of ornamentation. It has a spontaneous, improvisational feel, though the critique notes that the phrasing sometimes leans heavily on continuous, busy activity, leaving little room for silence or breath.</p>
</blockquote>

<p><strong>Timing and groove</strong></p>

<blockquote>
  <p>Set against a driving rhythm section, the lead guitar rides on top of the beat with a highly pushy, forward-leaning attitude. While this adds frantic, exciting energy, it also presents a challenge: during the faster, subdivided runs, the guitarist has a tendency to rush, momentarily crowding the pocket and pulling slightly ahead of the drummer's steady groove.</p>
</blockquote>

<h3 id="how-far-to-trust-it">How far to trust it</h3>

<p>That the quantitative analysis is deterministic does not make it correct: it returns the same values for the same input, but it can still mis-read; chord detection in particular sometimes mistakes the harmony. The qualitative evaluation is shakier: it runs only on Gemini for now, and depending on how the prompt is built it can name an instrument that is not in the recording. In earlier iterations, before I added those constraints to the prompt, the qualitative tool sometimes mentioned a rhythm guitar that was not actually on the track. The performance may not be quite sufficient yet. Still, as better models arrive, having AI evaluate recorded music like this should become a realistic and genuinely useful practice.</p>
]]></content:encoded>
</item>
<item>
  <title>Reading Anthropic on Recursive Self-Improvement</title>
  <link>https://yohasebe.com/posts/2026-06-06-reading-anthropic-rsi/</link>
  <guid isPermaLink="true">https://yohasebe.com/posts/2026-06-06-reading-anthropic-rsi/</guid>
  <pubDate>Sat, 06 Jun 2026 00:00:00 +0000</pubDate>
  <description>Notes after reading Anthropic's piece on AI building AI, on why I keep Monadic Chat multi-provider, and on what is left of the human side</description>
  <content:encoded><![CDATA[<p>Anthropic recently posted a piece called <a href="https://www.anthropic.com/institute/recursive-self-improvement">"When AI builds itself"</a> on their institute site. It is about recursive self-improvement: the point at which an AI system can autonomously design and develop its own successor. They are explicit that we are not there yet, and that this is not inevitable. The piece also lays out evidence that the direction is real. As of May 2026 they report that more than 80% of the code merged into Anthropic's codebase is written by Claude, and the length of tasks AI can reliably complete is doubling roughly every four months. Research taste and alignment, they note, are the parts that remain hardest for AI.</p>

<p>On policy, Anthropic say they would support a coordinated international pause under certain conditions, specifically if verification systems existed so frontier developers could confirm others had actually stopped. A unilateral pause by one lab, they argue, would mainly just change who the front-runner is.</p>

<p>I work on <a href="https://github.com/yohasebe/monadic-chat">Monadic Chat</a>, a locally hosted AI chat platform. I have kept it multi-provider deliberately. Today it routes to seven hosted providers and one local runtime:</p>

<ul>
  <li>OpenAI (US)</li>
  <li>Google (US)</li>
  <li>xAI (US)</li>
  <li>Anthropic (US)</li>
  <li>Cohere (Canada)</li>
  <li>Mistral (France)</li>
  <li>DeepSeek (China)</li>
  <li>Ollama (local, not tied to any jurisdiction)</li>
</ul>

<p><img src="monadic-chat-providers.png" alt="Monadic Chat's System Settings panel: the Base App dropdown lists the supported providers (OpenAI, Anthropic, xAI, Google, Cohere, Mistral, DeepSeek, Ollama). To the right is an agent flow diagram: User Input goes through System Prompt to gpt-5.4, with Tools (Library Search, File Operations), Message History, and Features (Web Search) panels feeding in and out" /></p>

<p>The reason is less a principle than a practical hedge. Without an international coordination framework in place, regulation, if and when it comes, is likely to take different shapes across providers, jurisdictions, and timelines. If a user has only one route to the AI work they want to do, their work can stall when those rules change first. More routes mean more flexibility as things change.</p>

<p>At the same time, what Anthropic are calling for seems reasonable to me. Paying attention to the trajectory and setting up structures that would let humans intervene later is hardly an overreaction.</p>

<p>Over the last few years I have been watching the layers AI handles in development grow. Snippet suggestions, then function coding, then codebase design, technology choice, execution, testing, debugging. I built <a href="https://github.com/yohasebe/tmux-ccm">ccm for tmux</a> on the working belief that the last thing left on the human side is attention and judgment. The Anthropic piece argues that even that layer is starting to move.</p>

<p>There is not much an individual developer can do at this scale. Most of what I build either reinvents something that already exists or gets superseded by a larger project. I keep building anyway. Honestly, the AI built on top of large language models is becoming a more and more complete black box for ordinary users. Before it fully gets there, I want to grasp what is happening, as far as I can, for myself. I cannot enter the model development race. But building my own tools is itself a way of keeping some control on my side, however small. At least I can see what is happening firsthand.</p>
]]></content:encoded>
</item>
<item>
  <title>Looking for a Multi-Account Email Solution</title>
  <link>https://yohasebe.com/posts/2026-06-05-email-aggregation/</link>
  <guid isPermaLink="true">https://yohasebe.com/posts/2026-06-05-email-aggregation/</guid>
  <pubDate>Fri, 05 Jun 2026 00:00:00 +0000</pubDate>
  <description>Multi-account email aggregation used to be a normal thing. It quietly stopped being normal. Notes on what changed, and on reviving a small</description>
  <content:encoded><![CDATA[<p>There was a time when running several email accounts through one inbox was the obvious thing to do. Gmail would pull mail from your Yahoo account, or your school or work address, over POP, and you could send from those addresses too. One inbox, many addresses.</p>

<p>That was a few years ago. Gmail has been winding down its "Check mail from other accounts" feature and signaled that further changes are coming. Microsoft has tightened authentication, and the old way of pulling Outlook addresses into Gmail no longer works. Each provider has retreated into its own walled garden.</p>

<p>For someone with email spread across several services, there are few good options now. Cloud-side aggregators have mostly vanished, and local clients come with their own trade-offs, most notably being tied to a specific device.</p>

<p>Back in 2022 I worked through this and moved to <a href="https://fastmail.com">Fastmail</a>. It is paid, but it handles the multi-address case cleanly. It is independent of the big platforms, lets you host your own domain, and sticks to open standards. I also built a small Chrome extension on the side, <a href="https://chromewebstore.google.com/detail/fastmail-plus/ibgnnkojbkconppocnmdobeodcaijmfm">Fastmail Plus</a>, to smooth over a few UI quirks of my own. It had sat untouched for a long stretch. I brought it up to date with the current UI this week.</p>

<p><img src="fastmail-plus.png" alt="Fastmail Plus browser extension banner: large &quot;Fastmail Plus&quot; logo on a blue background, with a small screenshot of the Fastmail web UI showing the extension's panel listing features like Quick search mode switch, Cursor key navigation, Extra shortcut keys, Non-clutter viewing mode, and Reading pane control buttons. The tagline reads &quot;A browser extension to make Fastmail web UI more usable and productive&quot;" /></p>

<p>Authentication standards have been tightening across the industry, and POP-based bridges are being phased out. Email itself is not going anywhere, but the way to handle several addresses now is to pick a single host and live there.</p>
]]></content:encoded>
</item>
<item>
  <title>Image Generation as a Classroom Visual Aid</title>
  <link>https://yohasebe.com/posts/2026-05-29-classroom-image-generation/</link>
  <guid isPermaLink="true">https://yohasebe.com/posts/2026-05-29-classroom-image-generation/</guid>
  <pubDate>Fri, 29 May 2026 00:00:00 +0000</pubDate>
  <description>Using Monadic Chat's Image Generator to make grid visuals on the fly for student talks, when topics are picked individually and cannot be</description>
  <content:encoded><![CDATA[<p>At my institution I teach several first-year English communication courses for students from a range of faculties. In several of them, <a href="https://github.com/yohasebe/monadic-chat">Monadic Chat</a> has come to play something like an assistant-teacher role. Voice conversations with students, on-the-fly vocabulary or idiom examples, cultural commentary on items that come up in reading materials. The teacher's moderation remains essential, but recent high-end LLMs have the capability needed for this kind of use.</p>

<p>One format I have been running lately, with groups of 15 to 20 students, is short one-minute English talks on a given theme. "A foreign city I would like to visit." "The greatest invention or discovery in human history." Themes broad enough that each student can choose something personal, specific enough that they have something to say.</p>

<p>Some kind of visual aid usually makes these sessions more engaging. But the talks happen one at a time, students pick their own topics, and I do not know what all of them are until they speak. There is no preparing a slide deck in advance.</p>

<p>Monadic Chat's Image Generator, backed by OpenAI's <code>gpt-image-2</code>, solves this. A prompt like:</p>

<blockquote>
  <p>Create a matrix-style image of the following N items, with numbered labels.</p>
</blockquote>

<p>together with the list of student topics produces a single grid image on the spot, which I project on the classroom screen as the talks proceed.</p>

<p><img src="inventions-grid.png" alt="A grid image of seventeen inventions and discoveries chosen by students, with numbered cells: Electricity, Alarm Clock, Instant Noodle, Emoji, Medicine, Railway, Lens, Paper, Plastic, Language, Camera, Airplane, Ship, Air Conditioner, Cup Ramen, Writing System, Internet" /></p>

<p class="caption">Theme: "The greatest invention or discovery in human history."</p>

<p><img src="cities-grid.png" alt="A grid image of sixteen foreign cities chosen by students, with numbered cells: Orlando, Helsinki, Stockholm, Venice, Stroud, Banff, Lucerne, Seattle, Ho Chi Minh, Prince Edward Island, Seoul, Vancouver, Reykjavik, Paris, Cologne (Köln), Rovaniemi" /></p>

<p class="caption">Theme: "A foreign city I would like to visit."</p>

<p>ChatGPT or Gemini can do this just as well, and plenty of teachers already work this way. The shape of classroom teaching is certainly shifting.</p>
]]></content:encoded>
</item>
<item>
  <title>Conversation Memory That Stays on Your Laptop</title>
  <link>https://yohasebe.com/posts/2026-05-25-monadic-chat-local-memory/</link>
  <guid isPermaLink="true">https://yohasebe.com/posts/2026-05-25-monadic-chat-local-memory/</guid>
  <pubDate>Mon, 25 May 2026 00:00:00 +0000</pubDate>
  <description>Monadic Chat now uses a local multilingual embedding model and a local vector DB. Indexing and retrieval of past chats happen entirely on</description>
  <content:encoded><![CDATA[<p>You remember talking with the AI assistant about something months back, like a config you sorted out or a paper you summarized. But finding it again means figuring out which past chat to even open. Most LLM clients don't search across your past chats at all, and the ones that do tend to ship your messages to a cloud embedding API just to make them searchable.</p>

<p>The latest <a href="https://github.com/yohasebe/monadic-chat">Monadic Chat</a>, an AI chat platform I work on, moves this end to end onto your machine.</p>

<p>The earlier setup used OpenAI's <code>text-embedding-3-large</code> for the help system and PDF knowledge base, with PGVector for storage. It worked for static documents, but extending the same path to conversations would have meant every chat passing through a cloud embedding endpoint just to be findable later. I wanted to avoid that. The new embedding pipeline is fully local:</p>

<ul>
  <li>An embeddings container running <a href="https://huggingface.co/intfloat/multilingual-e5-base"><code>multilingual-e5-base</code></a> (sentence-transformers)</li>
  <li>A <a href="https://qdrant.tech/">Qdrant</a> container holding the vectors</li>
  <li>A two-level embedding scheme: a summary per conversation, plus the individual turns inside it</li>
</ul>

<p>Qdrant is an open-source database designed for embeddings: it stores them and searches by similarity in meaning rather than exact wording.</p>

<p>The same store also holds PDF and document content if you've imported any, so search works uniformly across whatever you've put into the knowledge base.</p>

<p>Search is cascade: a query first hits conversation summaries, then drills into the matching conversations turn by turn. The retrieval is wired up as a RAG tool too, so past content can be pulled into the current chat as context when the model decides it's useful.</p>

<p>And if you pick an Ollama-backed model for the chat itself, it runs locally too.</p>
]]></content:encoded>
</item>
<item>
  <title>The Cloud Sees PERSON, You See Your Name</title>
  <link>https://yohasebe.com/posts/2026-05-18-monadic-chat-privacy-filter/</link>
  <guid isPermaLink="true">https://yohasebe.com/posts/2026-05-18-monadic-chat-privacy-filter/</guid>
  <pubDate>Mon, 18 May 2026 00:00:00 +0000</pubDate>
  <description>Monadic Chat detects personal info locally and replaces it with typed placeholders before sending to cloud LLMs. You still see your</description>
  <content:encoded><![CDATA[<p>You have something personal to ask the AI assistant. A draft email with names in it. A summary of a doc with email addresses or phone numbers. You want the LLM's help but pasting it straight into a cloud provider stops you. So you redact by hand, paste the masked version, mentally translate back when reading the response. Tedious, and the LLM doesn't know what kind of thing you redacted.</p>

<p>The latest <a href="https://github.com/yohasebe/monadic-chat">Monadic Chat</a>, an AI chat platform I work on, does this for you, invisibly. You type the original. Before send, a local detector replaces PII (personally identifiable information) with typed placeholders. The LLM gets <code>&lt;&lt;PERSON_1&gt;&gt;</code>; you keep seeing the real name on screen. When the response comes back, any placeholders it preserved are restored to the original values for display.</p>

<p><img src="privacy-filter.png" alt="Monadic Chat Chat Plus conversation: a user message asking for a draft email to &quot;Kevin Park&quot; with office number &quot;555-123-4567&quot;, and the assistant's email reply containing the same values. Both occurrences of the name and phone number are visibly highlighted, indicating the privacy filter detected them as PII" /></p>

<p><img src="privacy-dashboard.png" alt="Privacy Registry modal listing two mappings: PERSON_1 to Kevin Park (PERSON), PHONE_NUMBER_1 to 555-123-4567 (PHONE_NUMBER). A note above the table reads &quot;This list is held in memory only and is cleared when the conversation is closed. Copy is intentionally disabled to avoid accidental disclosure.&quot;" /></p>

<p>The detector runs in its own Docker container with <a href="https://microsoft.github.io/presidio/">Microsoft Presidio</a> + <a href="https://spacy.io/">spaCy</a>. Presidio handles pattern-based categories (email, phone, credit card, SSN, IP, URL, and others); a local spaCy NER model handles context-dependent ones (people, organizations). Detection is multilingual, supporting 9 languages.</p>

<p>A few properties that matter:</p>

<ul>
  <li><strong>Per-session</strong>: each chat gets its own placeholder registry, in memory. Nothing accumulates as a growing on-disk record of who you've talked about. If you export an encrypted session and import it again later, the registry is restored alongside the conversation, so masking keeps working on follow-up turns.</li>
  <li><strong>Per-app</strong>: the toggle shows up only on apps where masking makes sense (Chat Plus, Mail Composer, Translate, Second Opinion). Apps focused on code generation or media output don't expose it, since their inputs rarely carry personal information.</li>
  <li><strong>Fail-closed</strong>: in the unlikely event the privacy container can't run, the main chat still works, but background calls that would send extra text to the cloud (title suggestion, second opinion) are skipped rather than sent without masking.</li>
  <li><strong>Detection is probabilistic</strong>: pattern-based categories (emails, phones, etc.) are caught reliably, but the NER paths for names and organizations will sometimes miss novel forms or edge cases. The filter substantially reduces what leaves the machine; it doesn't guarantee zero leakage.</li>
</ul>

<p>The LLM only sees the type label (<code>&lt;&lt;PERSON_1&gt;&gt;</code>, <code>&lt;&lt;EMAIL_1&gt;&gt;</code>), not the actual value. It knows what kind of thing is hidden, but not which specific one. For rewriting, summarizing, translating, drafting, that level of knowledge is enough. For tasks where the specific value matters, you can just turn the filter off for that session.</p>
]]></content:encoded>
</item>
<item>
  <title>ccm: An Attention Manager for Parallel Claude Code Sessions</title>
  <link>https://yohasebe.com/posts/2026-05-04-ccm-attention-manager/</link>
  <guid isPermaLink="true">https://yohasebe.com/posts/2026-05-04-ccm-attention-manager/</guid>
  <pubDate>Mon, 04 May 2026 00:00:00 +0000</pubDate>
  <description>When you run Claude Code across several projects in parallel, the bottleneck stops being compute and starts being attention. ccm is a tmux</description>
  <content:encoded><![CDATA[<p>When you run Claude Code for one project at a time, switching contexts feels manageable. You wait for it to finish, you respond, you move on. Push that up to four or five projects in parallel and the picture changes. The compute is no longer the bottleneck. Your attention is.</p>

<p>I have been building <a href="https://github.com/yohasebe/tmux-ccm">ccm for tmux</a> as an answer to this. It does several things now, but the pieces that defined the project from the start trace back to a single question: <em>which window should I be looking at right now?</em></p>

<h2 id="the-mental-model">The mental model</h2>

<p><img src="mental-model.svg" alt="ccm mental model: a tmux session contains windows, each window equals one project (one folder), every window holds Claude Code plus other panes (shell, dev server, any long-running process), the state of the Claude Code pane (PERMIT, BUSY, IDLE) aggregates back to the window, and ccm switches between windows with prefix + Tab" /></p>

<p>ccm assigns each project to its own tmux window. The window can hold Claude Code alongside a shell, a dev server, or any other long-running process. The state of the Claude Code pane (PERMIT, BUSY, IDLE) propagates up to the window itself, and ccm lets you move between windows with <code>prefix + Tab</code>.</p>

<h2 id="states-that-map-to-urgency">States that map to urgency</h2>

<p>Each Claude Code session moves through phases. It is reasoning or producing output (BUSY). It pauses to ask permission before taking some action (PERMIT). It finishes and waits for the next prompt (IDLE). Or no Claude Code session is running in the window (SHELL). ccm reads each tmux pane's contents in the background and tags the window with one of these states.</p>

<p>The four states sort cleanly by urgency:</p>

<blockquote>
  <p>PERMIT &gt; BUSY &gt; IDLE &gt; SHELL</p>
</blockquote>

<p>PERMIT is the most demanding: a session has presented options and is waiting for you. Miss it and the session sits frozen. BUSY is next – a session is producing output that you may want to follow or course-correct. IDLE is comfortable: the session has done what was asked, and there is no pressure. SHELL is the lowest priority; no session is running, so nothing needs you unless you decide to start one.</p>

<p>ccm also moves windows to SHELL on its own. A session that has been idle for a while is auto-exited to free memory and CPU, then restarted with <code>--continue</code> (resuming the previous context) when you switch back. The state ordering is what the dashboard surfaces:</p>

<p><img src="dashboard.png" alt="ccm dashboard showing multiple projects with states, branches, and ports" /></p>

<p>PERMIT rows land at the top regardless of original window order; the eye moves down from there. Switching to a project becomes a single keystroke once the decision is made.</p>

<h2 id="coordination-across-projects">Coordination across projects</h2>

<p>A second feature applies the same state model across projects. <code>ccm send &lt;project&gt; &lt;message&gt;</code> queues a prompt into another project's session, with safety gating based on the target's current state. PERMIT-state windows are refused unconditionally – typing into a permission dialog could accidentally approve or reject a tool call. BUSY-state targets need explicit <code>--force</code> (the input is queued rather than mixed mid-turn). SHELL-state windows can auto-start Claude Code first.</p>

<p>This turns inter-project coordination into something close to message passing. The session in project A can – through me, as the human in the loop – ask the session in project B about its current state, share findings, request follow-up work. The same states that organize my attention also gate what flows between projects.</p>

<h2 id="with-agent-teams">With Agent Teams</h2>

<p>Once <code>ccm send</code> makes inter-project coordination routine, a natural next question follows: could the coordination itself be delegated to AI? Within a single project, Claude Code already does this through <a href="https://code.claude.com/docs/en/agent-teams">Agent Teams</a>, where one session coordinates teammates running in parallel panes within a single window (tmux or iTerm2).</p>

<p>Because the two structures (panes within a window, windows across a session) do not overlap, they compose naturally. A ccm-managed window can host an Agent Team, and ccm aggregates state across the team's panes using the same priority order. If any teammate is at PERMIT, the whole window appears as PERMIT in the dashboard – so a single agent waiting for permission surfaces immediately even when focus is on a different teammate, and the human-side question stays the same: which window should I be looking at right now?</p>

<h2 id="attention-as-the-human-ai-interface">Attention as the human-AI interface</h2>

<p>The opening observation – attention, not compute, is the bottleneck – seems to sharpen as more work moves to agents. Attention may be one of the places where humans and AI still meet, with the human role on that surface narrowing toward what to bring forward, what to ignore, when to interrupt. ccm is a small piece of that surface, and the rest is something I want to keep thinking about.</p>
]]></content:encoded>
</item>
<item>
  <title>Watching Words Appear: Real-time STT and L2 Listening</title>
  <link>https://yohasebe.com/posts/2026-04-25-speechdock-listening-mirror/</link>
  <guid isPermaLink="true">https://yohasebe.com/posts/2026-04-25-speechdock-listening-mirror/</guid>
  <pubDate>Sat, 25 Apr 2026 00:00:00 +0000</pubDate>
  <description>Why captions can hurt listening practice, and how the lag in real-time STT might be useful</description>
  <content:encoded><![CDATA[<p>When we learn a foreign language, listening is, for many learners, the skill that resists practice the most. Reading and writing happen on our schedule. Speaking we control. But listening means catching speech as it goes by, in real time, with no rewind.</p>

<p>The most common aid is captions. Watch a video with subtitles and you can follow along. But captions can undermine the very thing we are trying to practice. Visual information tends to take priority over audio in our cognitive processing, and when the two arrive together, the work of extracting meaning from sound alone is reduced. Since captions are timed to the audio – or slightly ahead – the listener ends up reading rather than listening.</p>

<p>To address part of this, <a href="https://yohasebe.com/tcse/">TCSE</a> has long had a fullscreen mode (described in <a href="../2026-04-18-tcse-fullscreen/index.html">an earlier post</a>) where no transcript shows during playback; only the previous and current lines appear the moment you pause. The listener has to attempt interpretation first, and only then check. But the answer-checking happens at sentence boundaries. You hear a whole sentence, decide what you think it said, then pause to verify. The granularity is coarse.</p>

<p><a href="https://github.com/yohasebe/speechdock">SpeechDock</a> is a Mac app I have been building for speech-to-text and text-to-speech tasks. Unlike most STT apps, it can take its input from the microphone, the system audio mix, or the audio from a specific application. One of its modes runs that input through real-time STT (either macOS-native or a cloud provider) and displays the result as a HUD overlay on top of whatever is on screen.</p>

<video controls="" preload="metadata" src="ask-nasa-transcription.mp4" style="max-width:100%;"></video>

<p><em>SpeechDock real-time transcription overlaid on NASA's <a href="https://images.nasa.gov/details/NHQ_2020_0127_AskNASA┃%20How%20Will%20Astronauts%20Dig%20on%20the%20Moon">Ask NASA: How Will Astronauts Dig on the Moon?</a> (00:16–00:46, public domain). The demo uses macOS's native STT, with VLC's audio output specified as the SpeechDock input source.</em></p>

<p>Real-time STT cannot show text at the same moment as the audio. There is always some lag in our verbal communication, for that matter: speech has to be heard, segmented, recognized, and interpreted. And there is always the possibility of revision when later input changes the parse.</p>

<p>Real-time STT makes this incremental, revisable parsing visible. The HUD is not a transcript that hands you the answer; it shows a process that mirrors the cognition you are trying to develop, unfolding before your eyes.</p>

<p>Whether this kind of tool actually helps L2 learners improve their listening is an open empirical question. If anyone wants to design a proper study together, please get in touch.</p>
]]></content:encoded>
</item>
<item>
  <title>Filing in Trees, Finding in Fragments</title>
  <link>https://yohasebe.com/posts/2026-04-25-fzf-alfred-workflow/</link>
  <guid isPermaLink="true">https://yohasebe.com/posts/2026-04-25-fzf-alfred-workflow/</guid>
  <pubDate>Sat, 25 Apr 2026 00:00:00 +0000</pubDate>
  <description>Why filing and finding pull in different directions, and what fzf in Alfred does about</description>
  <content:encoded><![CDATA[<p>When I look for something on my computer, the things I remember come in pieces. The folder it might be in. A word from the filename. A topic. They arrive in no particular order. But the standard way to find a file – open a folder window, descend one level, then another – demands that I produce them in exactly the right sequence.</p>

<p>I only notice this when I am finding things. When I am putting them away, I am happy to take my time, placing each file somewhere sensible. Filing and finding feel like different kinds of work.</p>

<h2 id="filing-can-wait-finding-cannot">Filing can wait, finding cannot</h2>

<p>Filing can wait. I have a moment to decide which project, which year, which subfolder. Getting it slightly wrong is fine – I can move the file later.</p>

<p>Finding cannot wait. By the time I notice I want a file, I want it now. Whatever fragments come to mind should be enough.</p>

<p>There are two common ways to organize files in the face of this. Neither works for me.</p>

<p>The first abandons hierarchy: keep everything in one flat space, tag each file, and search by tag. Retrieval is great. But filing becomes a chore. There is no obvious "right place" for a new file, and tagging well takes more discipline than I have. So I file less.</p>

<p>The second commits to hierarchy in both directions: organize as a tree, find by descending the tree. macOS Finder columns invite this. But finding becomes expensive. I have to know the tree to use it, and the fragments that come to mind first are not always at the top. I open the wrong folder, back out, try again – searching by walking, the slow way.</p>

<p>What both share is the assumption that storage and retrieval should mirror each other. But filing and finding are not the same activity, and forcing them into the same shape makes both worse.</p>

<h2 id="a-way-that-respects-both">A way that respects both</h2>

<p>What I want is to keep the hierarchy I already have, and search across it with whatever fragments come to mind, in any order.</p>

<p>On macOS, the tool that does this for me is <a href="https://github.com/yohasebe/fzf-alfred-workflow">fzf-alfred-workflow</a>, a small Alfred workflow I wrote some years ago. It plugs <a href="https://github.com/junegunn/fzf">fzf</a> and <a href="https://github.com/sharkdp/fd">fd</a> together so any space-separated fragments I type are matched against every path under my home directory (minus default exclusions such as ~/Library and node_modules), in any order.</p>

<div class="figure-wide"><img src="how-it-works.svg" class="theme-svg" alt="The query 'fzf damage puppets metallica' typed in the reverse of the order the words appear in the path /music/metallica/master-of-puppets/08-damage-inc.mp3, with curved connectors crossing to show each word matching anywhere in the path" style="width:100%" /></div>

<p>The match runs over the whole path, not just the filename. The folders a file sits in are part of what I can search, so the route I chose when filing – project, year, topic – becomes material for finding later. And fragments typed through a Japanese IME now match filenames whether macOS stored them in composed or decomposed Unicode.</p>

<p><img src="fzf-demo.gif" alt="fzf in Alfred, narrowing results as keywords are added" /></p>

<p>The pleasure of using it is less about speed than about a small relief: I no longer have to remember where things are. Filing stays hierarchical, because there is no rush when I file. Finding takes whatever fragments are in my head.</p>

<p>The same asymmetry shows up in language. Lexical concepts are organized hierarchically – an efficient data structure for organizing the world. But real-time lexical use does not behave like a tree search; it unfolds on a reference-point network, jumping from one anchor to another. The tool feels right because it stops trying to make filing and finding match. They were never going to.</p>
]]></content:encoded>
</item>
<item>
  <title>Monadic Chat: Expressive Speech</title>
  <link>https://yohasebe.com/posts/2026-04-21-expressive-speech/</link>
  <guid isPermaLink="true">https://yohasebe.com/posts/2026-04-21-expressive-speech/</guid>
  <pubDate>Tue, 21 Apr 2026 00:00:00 +0000</pubDate>
  <description>Letting the chat model direct its own delivery -- tags and instructions across four TTS providers in Monadic Chat's Voice Chat</description>
  <content:encoded><![CDATA[<p><a href="https://yohasebe.github.io/monadic-chat/">Monadic Chat</a>'s voice interaction runs on a pipeline: user audio → STT → chat model → TTS → assistant audio. The new <strong>Expressive Speech</strong> feature uses that pipeline by having the chat model emit both the reply text and a set of delivery cues (laugh here, whisper there, sound warm throughout) in the same generation, and passing them to the TTS engine together.</p>

<p>In Monadic Chat's Voice Chat apps, this effectively lets the assistant act as an AI agent that also directs its own delivery – deciding where to laugh, where to whisper, and what overall color the voice should have, all at reply-generation time.</p>

<pre><code class="language-mermaid">flowchart LR
    user["`🎤 **User audio**`"]:::io --&gt; stt["`✍️ **Speech-to-Text**
(API call)`"]:::api
    stt --&gt; llm["`🤖 **Chat model**
(single API call)`"]:::model
    llm --&gt; text["`💬 **Reply text**`"]:::data
    llm --&gt; cues["`🎭 **Delivery cues**
(tags or instructions)`"]:::data
    text --&gt; tts["`🗣️ **Text-to-Speech**
(API call)`"]:::api
    cues --&gt; tts
    tts --&gt; out["`🔊 **Assistant audio**`"]:::io

    classDef io fill:#dbeafe,stroke:#3b82f6,color:#1e3a8a
    classDef api fill:#ccfbf1,stroke:#0d9488,color:#134e4a
    classDef model fill:#dcfce7,stroke:#16a34a,color:#14532d
    classDef data fill:#ede9fe,stroke:#7c3aed,color:#4c1d95
</code></pre>

<p>Monadic Chat currently supports four TTS providers for this, and their APIs fall into two broad approaches.</p>

<h2 id="inline-markers">Inline markers</h2>

<p>xAI Grok, ElevenLabs v3, and Google Gemini all interpret tags embedded directly in the text. The engine consumes the bracketed tokens as stage directions instead of reading them aloud.</p>

<p><strong>xAI Grok</strong> (model <code>grok-tts</code>, voice <code>ara</code>) is the only provider among the four that pairs point markers (<code>[inhale]</code>, <code>[sigh]</code>, <code>[laugh]</code>) with <em>range markers</em> – opening and closing tags that apply an effect to the enclosed span and then return to normal voice. The wrapping set includes <code>&lt;whisper&gt;</code>, <code>&lt;slow&gt;</code>, <code>&lt;loud&gt;</code>, <code>&lt;high&gt;</code>, and <code>&lt;sing&gt;</code> (<a href="https://docs.x.ai/developers/model-capabilities/audio/text-to-speech#speech-tags">xAI docs</a>).</p>

<pre><code>[inhale] &lt;slow&gt;Let me think about that for a second.&lt;/slow&gt;
&lt;whisper&gt;Actually, between you and me, I already know the answer.&lt;/whisper&gt;
</code></pre>

<audio controls="" src="xai.mp3"></audio>

<p><strong>ElevenLabs v3</strong> (model <code>eleven_v3</code>, voice <code>Rachel</code>) uses single-token square brackets for everything. The vocabulary is emotional (<code>[excited]</code>, <code>[curious]</code>, <code>[sad]</code>, <code>[sarcastic]</code>) and performative (<code>[sings]</code>, <code>[giggles]</code>, <code>[sobs]</code>) (<a href="https://elevenlabs.io/docs/overview/capabilities/text-to-dialogue#emotional-deliveries-with-audio-tags">ElevenLabs docs</a>).</p>

<pre><code>[excited] You won't believe this! [giggles] Oh my goodness.
[sings] La la la, la la la la!
</code></pre>

<audio controls="" src="elevenlabs.mp3"></audio>

<p><strong>Google Gemini</strong> (model <code>gemini-2.5-flash-preview-tts</code>, voice <code>Zephyr</code>) uses syntax similar to ElevenLabs v3, but with a distinctive vocabulary of situational moods: <code>[mischievously]</code>, <code>[panicked]</code>, <code>[amazed]</code>, <code>[trembling]</code>, <code>[gasp]</code>, <code>[shouting]</code>, <code>[tired]</code> (<a href="https://ai.google.dev/gemini-api/docs/speech-generation?hl=en#audio-tags">Gemini speech generation</a>).</p>

<pre><code>[amazed] Look at that cake!
[mischievously] I wonder if anyone would notice a missing slice.
[panicked] Quick, someone's coming!
</code></pre>

<audio controls="" src="gemini.mp3"></audio>

<h2 id="instructions">Instructions</h2>

<p>OpenAI's <code>gpt-4o-mini-tts</code> splits text and direction. The reply text stays clean, and a separate <code>instructions</code> parameter in the TTS API call specifies how to deliver the whole utterance. Because an instruction shapes the entire delivery rather than a moment, even a short text with a bit of emotional range is enough to hear the effect (<a href="https://www.openai.fm/">openai.fm</a> is a quick playground).</p>

<p>Because the instruction is free-form English, it is not tied to a fixed tag vocabulary. Voice quality, emotional arc, and pacing can all be described the way a director might sketch a scene.</p>

<p><strong>OpenAI</strong> (model <code>gpt-4o-mini-tts</code>, voice <code>ballad</code>):</p>

<pre><code>Input:
  "Oh my goodness, you are not going to believe this!
   You know that guitar piece I've been working on for months?
   I just played it all the way through -- no mistakes, clean as
   anything! I am so happy right now, you have no idea!"

Instructions:
  Voice: bright, giddy, absolutely bursting with joy.
  Tone: pure, uncontainable excitement from start to finish; each
        sentence should sound like it's barely holding the happiness in.
  Pacing: quick and breathless; strong emphasis on "no mistakes" and
          "so happy"; a triumphant beat on "clean as anything".
  Emotion: unfiltered delight and pride; the voice should practically
           glow.
  Delivery: a small laugh or giggle of disbelief should slip through
            after "all the way through"; the voice climbs in intensity
            toward the final line.
</code></pre>

<audio controls="" src="openai.mp3"></audio>

<p>One thing worth noting: with OpenAI, the choice of voice makes a surprising difference to how instructions land. Neutral voices like <code>alloy</code> respond less dramatically, while <code>coral</code> and <code>ballad</code> pick up expressive directives much more readily. The sample above uses <code>ballad</code>.</p>

<p>Gemini also accepts natural-language direction placed at the front of the prompt, in addition to the tag-based approach seen earlier. Tags and directive can even be combined in a single prompt – the directive shapes the overall atmosphere while the tags pin down specific moments.</p>

<p><strong>Google Gemini</strong> (model <code>gemini-2.5-flash-preview-tts</code>, voice <code>Zephyr</code>) in hybrid form (same utterance as the OpenAI sample above, delivered with a directive plus inline tags):</p>

<pre><code>Input:
  "Say with this voice and style:
   Voice: bright, giddy, absolutely bursting with joy.
   Tone: pure, uncontainable excitement from start to finish.
   Pacing: quick and breathless; strong emphasis on 'no mistakes' and 'so happy'.
   Emotion: unfiltered delight and pride.

   Oh my goodness, you are not going to believe this! [giggles]
   You know that guitar piece I've been working on for months?
   I just played it all the way through -- no mistakes, clean as anything!
   [laughs] I am so happy right now, you have no idea!"
</code></pre>

<audio controls="" src="gemini-instruction.mp3"></audio>

<p>Fully model-native realtime voice APIs – OpenAI Realtime, Gemini Live, and similar – respond with lower latency and a more natural turn-taking rhythm, but they fold content, voice, and timing into a single model that the application cannot easily redirect. Monadic Chat's pipeline keeps the chat model and the TTS engine as separate stages, so any chat provider can be paired with any TTS provider, and the model – acting as an agent – can shape the content and its delivery in the same reply. The trade-off is immediacy for directorial control.</p>
]]></content:encoded>
</item>
<item>
  <title>TCSE: A Fullscreen Mode for Listening Practice</title>
  <link>https://yohasebe.com/posts/2026-04-18-tcse-fullscreen/</link>
  <guid isPermaLink="true">https://yohasebe.com/posts/2026-04-18-tcse-fullscreen/</guid>
  <pubDate>Sat, 18 Apr 2026 00:00:00 +0000</pubDate>
  <description>A less visible TCSE feature: a fullscreen player that hides the transcript during playback, showing it only when</description>
  <content:encoded><![CDATA[<p>Over the past month I have written about a couple of recent changes to <a href="https://yohasebe.com/tcse">TCSE</a>: <a href="../2026-03-17-tcse-update/index.html">entity search and the 6,400-talk milestone</a>, and the <a href="../2026-04-11-tcse-export/index.html">new export feature</a>. This post is about the education side of the project rather than the research side – an older, less visible feature in the video player for second-language learners who use TED talks for English listening practice.</p>

<h2 id="normal-and-fullscreen-viewing-modes">Normal and fullscreen viewing modes</h2>

<p>When you play a talk in TCSE, the default view shows the video above a scrolling transcript, with translations side by side if a translation language is selected.</p>

<p><img src="normal-mode.jpg" alt="Normal viewing mode with transcript and translation below the video" style="max-width:80%" /></p>

<p class="caption">Normal viewing mode</p>

<p>Clicking the fullscreen icon at the bottom right of the player (or pressing <code>ESC</code>) switches into fullscreen mode. The transcript and translation disappear from the screen during playback. Press the spacebar to pause, and the text for the current segment appears in yellow and the previous segment in cyan, with the translation (if selected) below. Press space again to resume, and the text goes away.</p>

<p><img src="fullscreen-mode.png" alt="Fullscreen viewing mode with the current segment in yellow and the previous segment in cyan when paused" style="max-width:80%" /></p>

<p class="caption">Fullscreen viewing mode (paused)</p>

<p>A few shortcuts are useful during listening practice: <code>R</code> to repeat the current segment, <code>A</code> to toggle "Study Mode" (auto-pause at every segment boundary), and <code>T</code> to show or hide the translation. The full list is on the <a href="https://yohasebe.github.io/tcse-doc/playing-video/player-functions-and-shortcut-keys/">shortcut keys page</a>; the feature itself is documented under <a href="https://yohasebe.github.io/tcse-doc/using-tcse-for-language-learning-and-education/using-pause-and-check/">"pause-and-check"</a>.</p>

<h2 id="why-this-mode-exists">Why this mode exists</h2>

<p>The design rests on a view of L2 listening practice I have held for a long time. If text is on screen the whole time, processing gets pulled toward the visual channel and the learner ends up <em>hearing</em> without really <em>listening</em>; but removing all text does not help either, since comprehension depends on context, and a learner who loses the thread early tends to spend the rest of the talk processing fragments. What is needed, and what pause-and-check tries to support, is <em>spot</em> access to the transcript – available when the thread breaks, not otherwise. The video plays without text in the way, and a tap of the spacebar brings just enough of the transcript back (the current and previous segments) to repair comprehension before resuming.</p>

<p>TCSE is, at its core, a search engine for language research and teaching, and this listening mode is not central to what it does. It is not the feature I would name first if asked what TCSE is for. But it is one of my personal favorites, and I hope language learners who land on a talk through search might find it useful once they have already found what they were looking for.</p>
]]></content:encoded>
</item>
<item>
  <title>What &quot;Monadic&quot; in Monadic Chat Means</title>
  <link>https://yohasebe.com/posts/2026-04-13-monadic-chat-name/</link>
  <guid isPermaLink="true">https://yohasebe.com/posts/2026-04-13-monadic-chat-name/</guid>
  <pubDate>Mon, 13 Apr 2026 00:00:00 +0000</pubDate>
  <description>The origin of the name 'Monadic Chat', connecting the functional programming monad to Langacker's current discourse</description>
  <content:encoded><![CDATA[<p><a href="https://github.com/yohasebe/monadic-chat">Monadic Chat</a> is a project I started in 2022, when the GPT-3 text completion API was the most capable thing one could call from a script. The OpenAI Chat API did not yet exist, and there was no built-in notion of conversational context. To make a chatbot that remembered what had been said, you had to manage the context yourself, on your own terms. I built Monadic Chat as one way of doing that. The name has stuck through several major rewrites, and I would like to explain where it came from – and why it still seems to fit.</p>

<p><img src="architecture.png" alt="Monadic Chat architecture overview" /></p>

<p class="caption">Current Monadic Chat architecture</p>

<h2 id="a-name-from-functional-programming">A name from functional programming</h2>

<p>In functional programming, a <em>monad</em> is a way of wrapping a value together with some surrounding context, such that you can keep operating on the value without having to manage the context by hand. The classic metaphor is "a value in a box": you put <code>a</code> into a box, you transform the contents of the box into <code>b</code>, and the box – whatever it contains – comes along for the ride.</p>

<p>When I was thinking about how to maintain conversational context across stateless API calls, this metaphor seemed to fit. Each user turn could be seen as a value, the conversation history as the surrounding context, and the act of producing a response as a transformation that happens <em>inside</em> that context rather than outside of it. The first version of Monadic Chat was a tiny Ruby program that did exactly this with JSON templates: each request carried an <code>input</code>, an <code>output</code>, and an <code>accumulator</code>, and the response itself became the next input. I wrote a <a href="https://www.anlp.jp/proceedings/annual_meeting/2023/pdf_dir/Q12-9.pdf">short paper</a> about it for the 2023 annual meeting of the Association for Natural Language Processing.</p>

<h2 id="discourse-as-monad">Discourse as monad</h2>

<p>The reason the monad metaphor felt right was not only that it gave me a useful way to think about the software architecture. It also matched the way cognitive linguists describe what speakers and hearers actually do when they hold a conversation.</p>

<p>Ronald Langacker has written extensively about what he calls the <em>current discourse space</em> – a structured representation of the immediately relevant context that participants share at any moment in a conversation. Each utterance updates this space: new entities are introduced, previous ones recede, the focus shifts, and the updated space becomes the ground for the next utterance. The structure has some parallels with how a monadic computation passes its environment forward through each step.</p>

<p>The monad in functional programming is a precisely defined mathematical object, while the current discourse space is a theoretical construct in cognitive linguistics. I do not want to conflate the two, but the <em>shape</em> is similar enough that bringing them into conversation seemed worthwhile. I have explored the linguistic side of this elsewhere – in a <a href="assets/docs/iclc16-hasebe-2023.pdf">talk at ICLC 16 in 2023</a> and in <em>Ninchi Gengogaku Ronkou</em> (Studies in Cognitive Linguistics) two years later, which I <a href="../2026-01-05-stack-model/index.html">introduced on this blog earlier</a>.</p>

<p>What matters, I think, is that how human speakers actually hold and manage context remains very much an open question in cognitive linguistics, and conversational AI may give us a new kind of object to think with – not only as a practical tool but as something that could feed back into theoretical work on discourse and cognition.</p>

<h2 id="where-the-project-is-going">Where the project is going</h2>

<p>Monadic Chat in 2026 is more eclectic than the original prototype. It speaks to multiple LLM providers, runs containerized tools, and handles images, audio, and video as well as text. It has grown into more of a multi-tool than a focused one. The <a href="https://yohasebe.github.io/monadic-chat/">project page</a> has the current details.</p>

<p>Real discourse is typically multimodal: gesture, intonation, and the visible environment all tend to play a part in how people make meaning, even though formal linguistic theory has often lacked the tools to handle them and has tended to set them aside in analysis. Monadic Chat's handling of images, audio, and video is still only partial, but my hope is that it will become not only a chat tool that gradually grows into such material, but also a medium through which I can come to understand more concretely what context actually is, and what the current discourse space really looks like.</p>
]]></content:encoded>
</item>
<item>
  <title>Whisper Stream: A Unix Building Block for Speech</title>
  <link>https://yohasebe.com/posts/2026-04-12-whisper-stream/</link>
  <guid isPermaLink="true">https://yohasebe.com/posts/2026-04-12-whisper-stream/</guid>
  <pubDate>Sun, 12 Apr 2026 00:00:00 +0000</pubDate>
  <description>Whisper Stream: a single-file bash script that turns a microphone into a text stream via OpenAI or whisper.cpp, for Unix</description>
  <content:encoded><![CDATA[<p><a href="https://github.com/yohasebe/whisper-stream">Whisper Stream</a> is a bash script that records speech, detects silence between utterances using sox, and sends each chunk to either OpenAI's transcription API or a local whisper.cpp model for transcription. I originally wrote it in the summer of 2023 and have not touched it much since. Recently I dusted it off and pushed a v2.0.0 update, mainly to make it more useful as a component in Unix pipelines.</p>

<h2 id="what-it-does">What it does</h2>

<p>The script listens to the microphone, uses sox's silence detection to segment the audio into natural utterances, and transcribes each one. The key design constraint is that it is a single bash file with no dependencies beyond sox, curl, and jq (or whisper.cpp for local use). No Python, no Node, no virtual environment.</p>

<p><img src="whisper-stream-demo.gif" alt="Whisper Stream in action" /></p>

<p>The v2.0.0 update adds:</p>

<ul>
  <li><strong><code>--stdout</code> and <code>--jsonl</code> modes</strong> that skip all side effects (clipboard, file save, banners) and emit transcriptions directly to stdout. This makes the script pipe-native.</li>
  <li><strong>Local backend</strong> via whisper.cpp — runs offline with no API cost, making always-on dictation practical.</li>
  <li><strong>Speaker diarization</strong> through OpenAI's newer transcription models.</li>
</ul>

<h2 id="why-it-matters-as-a-building-block">Why it matters as a building block</h2>

<p>The <code>--stdout</code> and <code>--jsonl</code> modes are the important part. A speech-to-text tool that writes to stdout and nothing else can be composed with other programs. Because it is a regular Unix filter, you can build things on top of it without touching the source:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># send a desktop notification when someone says "urgent"</span>
whisper-stream <span class="nt">--stdout</span> | <span class="nb">grep</span> <span class="nt">--line-buffered</span> <span class="nt">-i</span> <span class="s2">"urgent"</span> | <span class="k">while </span><span class="nb">read</span> <span class="nt">-r</span> line<span class="p">;</span> <span class="k">do </span>notify-send <span class="s2">"</span><span class="nv">$line</span><span class="s2">"</span><span class="p">;</span> <span class="k">done</span>

<span class="c"># feed every utterance into an LLM</span>
whisper-stream <span class="nt">--jsonl</span> | jq <span class="nt">-r</span> <span class="s1">'.text'</span> | your-llm-cli
</code></pre></div></div>

<p>A slightly more involved example: a wake-word assistant. The script listens continuously. When it hears a keyword, it plays a short beep to signal readiness, then sends the next utterance to an LLM.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>whisper-stream <span class="nt">--stdout</span> | <span class="k">while </span><span class="nb">read</span> <span class="nt">-r</span> line<span class="p">;</span> <span class="k">do
  if </span><span class="nb">echo</span> <span class="s2">"</span><span class="nv">$line</span><span class="s2">"</span> | <span class="nb">grep</span> <span class="nt">-qi</span> <span class="s2">"hey computer"</span><span class="p">;</span> <span class="k">then</span>
    <span class="c"># next line from the pipe is the follow-up utterance</span>
    <span class="nb">read</span> <span class="nt">-r</span> <span class="nb">command
    echo</span> <span class="s2">"</span><span class="nv">$command</span><span class="s2">"</span> | your-llm-cli
  <span class="k">fi
done</span>
</code></pre></div></div>

<p>There is a brief pause between saying the wake word and seeing a response – a few seconds while the utterance is transcribed. But it runs on macOS and Linux alike, and I can see it working on a Raspberry Pi with a microphone.</p>
]]></content:encoded>
</item>
<item>
  <title>TCSE: Exporting Search Results</title>
  <link>https://yohasebe.com/posts/2026-04-11-tcse-export/</link>
  <guid isPermaLink="true">https://yohasebe.com/posts/2026-04-11-tcse-export/</guid>
  <pubDate>Sat, 11 Apr 2026 00:00:00 +0000</pubDate>
  <description>TCSE now exports search results as TSV or JSON, page by page, with match context and metadata for close reading and</description>
  <content:encoded><![CDATA[<p>Over the past year or two, I have received a handful of emails asking for the same thing: could TCSE let users download search results, instead of forcing them to copy and paste hits one by one into a spreadsheet? I agreed every time that it was a reasonable request, put it on my to-do list, and never quite got around to it. That has now finally changed.</p>

<p><a href="https://yohasebe.com/tcse">TCSE</a> now has an export feature, currently in beta. You can find the documentation <a href="https://yohasebe.github.io/tcse-doc/searching-for-words/export-search-results/">here</a>.</p>

<h2 id="page-by-page-export">Page-by-page export</h2>

<p>Each export returns the current page of search results – around 200 items, matching what you see on the screen. The interface already paginates results 200 per page, so "export what I am looking at" is the natural model.</p>

<p>I deliberately avoided a single-request bulk download of thousands of rows. Both server load and the appropriate use of a corpus built on publicly accessible transcripts argued against it, and keeping each export scoped to one page keeps each request as light as an ordinary page view.</p>

<p>If you need the full set of hits for a high-frequency query, you can simply page through and export each page in turn. A short five-second cooldown sits between exports as a simple throttle against accidental double-clicks and overly eager scripts, but it should not get in anyone's way in normal use.</p>

<h2 id="what-each-row-contains">What each row contains</h2>

<p>On any search results page, a small "Export" button offers two formats:</p>

<ul>
  <li><strong>ZIP</strong> — a TSV file containing the rows, plus a <code>metadata.json</code> describing the query and the export</li>
  <li><strong>JSON</strong> — everything in a single JSON file</li>
</ul>

<p>Each row is not just the matched segment but the matched segment plus <strong>two segments of context before and after</strong>, along with the talk metadata (title, speaker, year, URL, duration). When you search with the Advanced Search syntax, the TSV also includes the part-of-speech, lemma, and dependency label of the matched token. When you have a translation language selected, the translation text is included too.</p>

<p>The TSV is UTF-8 with a byte-order mark, so Excel opens it without garbling non-ASCII characters.</p>

<h2 id="use-cases">Use cases</h2>

<p>A few things this unlocks for TCSE users:</p>

<ul>
  <li><strong>Close reading of hits</strong>: download the hits for a word, construction, or discourse marker and work through them in a spreadsheet or a notebook, annotating by hand or running scripts</li>
  <li><strong>Statistical work</strong>: load the TSV into R or Python, group by year or speaker, count co-occurrences, build visualizations</li>
  <li><strong>Teaching material</strong>: build a small custom dataset for a class assignment without having to set up your own corpus pipeline</li>
</ul>

<h2 id="beta-status">Beta status</h2>

<p>The feature is live as a beta release. The core functionality is in place, but there may be rough edges to smooth out based on how people actually use it. If you try it out and run into anything odd, or if there is metadata you wish were included but is not, let me know.</p>
]]></content:encoded>
</item>
<item>
  <title>Paradocs: Sentence-by-Sentence Presentations</title>
  <link>https://yohasebe.com/posts/2026-04-07-paradocs/</link>
  <guid isPermaLink="true">https://yohasebe.com/posts/2026-04-07-paradocs/</guid>
  <pubDate>Tue, 07 Apr 2026 00:00:00 +0000</pubDate>
  <description>Paradocs 1.0, a presentation tool that highlights text sentence by sentence -- originally built for ESL reading</description>
  <content:encoded><![CDATA[<p>I released version 1.0 of <a href="https://yohasebe.com/paradocs/">Paradocs</a>, a presentation tool. The name stands for paragraph-oriented document presentation system, reflecting its focus on handling text at the paragraph and sentence level. I originally built it in 2018 for ESL reading classes. The core feature is sentence-by-sentence highlighting as you walk through a text.</p>

<p>In a language class, keeping everyone on the same sentence at the same time is surprisingly hard with existing slide tools. PowerPoint and Google Slides operate at the slide level. Moving focus from one sentence to the next within a single paragraph is not something they are designed to do well. Paradocs was built to solve exactly that.</p>

<p>It runs entirely in the browser with no server required, hosted as a static site. Text data stays in the browser unless you opt in to cloud-based speech synthesis.</p>

<p><img src="images/paradocs-initial.png" alt="Paradocs initial screen" /></p>

<p>Each press of a key advances the highlight to the next sentence. Here is what it looks like in presentation mode:</p>

<p><img src="images/paradocs-demo.gif" alt="Sentence-by-sentence highlighting in Paradocs" /></p>

<p>With v1.0.0, the following features are now in place, marking a milestone:</p>

<ul>
  <li>Text-to-speech. Works for free using the browser's built-in speech synthesis, with optional support for OpenAI and ElevenLabs cloud voices. Word-level highlighting during playback.</li>
  <li>Fill-in-the-blank and multiple-choice quizzes.</li>
  <li>Multilingual UI: English, Japanese, Chinese, and Korean.</li>
  <li>Export as a standalone HTML file for offline use.</li>
  <li>Live preview with filmstrip thumbnails.</li>
  <li>Dark mode for both the editor and the presentation.</li>
</ul>

<p>Paradocs was made for language education, but I imagine there are other situations where stepping through text one sentence at a time is useful. If you find such a use, I would be glad to hear about it.</p>
]]></content:encoded>
</item>

</channel>
</rss>
