Back to research
Note2 min read

Why I started writing again

A short note on what this research log is for — and the kinds of problems I want to document as I ship.

I've been shipping software for a while now without writing much about it. That's changing here.

This page is where I'll keep the research — voice AI architectures, editorial UI decisions, and the quiet problems that don't make it into README files. Short notes when I have something useful to say, longer essays and deep dives when the problem is worth unpacking.

What to expect

A few things I'll keep publishing about, roughly:

  • Voice-agent internals. How real-time voice systems actually hold together — streaming, tool calls, latency budgets, fallback paths. The parts that aren't in the demos.
  • Shipping trade-offs. Decisions I'd make again, decisions I wouldn't, and why. Small receipts from products I've shipped end-to-end.
  • Interface craft. The difference between UI that looks done and UI that is done — mostly about motion, type, and the parts users feel before they can name.

What this is not

Not a newsletter. Not a growth channel. Not lorem-ipsum thought-leadership that overpromises and underdelivers.

If a post isn't useful to someone building the same thing, I won't publish it.

A code block, because why not

Here's the kind of thing you'll see when a post has technical meat:

// Streaming a voice agent response, trimmed for readability.
async function stream(prompt: string) {
  const session = await voice.open({
    model: "realtime-voice-latest",
    tools: [bookAppointment, lookupAccount],
  });
 
  for await (const event of session.send(prompt)) {
    if (event.type === "tool-call") await handle(event);
    if (event.type === "audio") speaker.play(event.chunk);
  }
}

That's it for the intro. The next post will have something concrete.

If you're working on something voice-AI-shaped and want to compare notes, the email link at the bottom of every page goes straight to me.