Say It, Don't Spray It
In which I build an agentic writing companion, name it after something embarrassing, and end up on my couch on an iPad doing exactly what I hoped I would.
There’s a thing kids say when another kid is talking too fast and gets a little too enthusiastic about making their point. The words come out wet.
Say it, don’t spray it.
I dictate almost everything now. Ideas, plans, first drafts, feedback on drafts, replies to emails. I talk to computers the way people in the 80s imagined we’d talk to computers. And when I’m really going -- when the idea is right there and I need to get it out -- I spray it.
So the app is called Spray.
---
What I Actually Wanted
I love reading. I’m a reasonably strong writer. What I’m not great at is writing for public consumption -- the part where you take something that’s alive in your head and shape it into something that works on a page for someone who isn’t you.
That process takes me a long time. Longer than I’d like. And for a while I’ve been watching what’s happened in AI-assisted coding and thinking: why doesn’t this exist for writing?
The experience I wanted was specific. Not “AI writes content for me.” Not a better text editor. I wanted something closer to what I get when I’m in a good coding session with Factory Droid -- a genuine back-and-forth where I can think out loud, get pushback, iterate on a draft together, and end up somewhere neither of us would have gotten alone.
I also knew from the start that dictation had to be central to how it worked. I talk way faster than I type. For getting ideas out and doing early iteration, I almost exclusively use voice now. The only times I reach for a keyboard anymore are when I physically can’t talk out loud, or when typing would genuinely be faster than the overhead of dictation setup. (The juice/squeeze calculation is real. Sometimes it’s just three words.)
So I had two things: a clear problem I wanted to solve, and a set of constraints that would shape the solution.
I started building.
---
The Spec Phase (Where the Real Work Happened)
I’ve written before about how the spec is the work. With coding, I’ve found that when I spend serious time planning before any code gets written, the code almost takes care of itself. The inverse is also true: if I rush to code, I end up with something that technically exists but doesn’t do what I actually wanted.
I believed this. And then I built the initial spec for Spray and immediately forgot I believed it.
The first version of my plan had 13 Python files in a nested structure. MagicLink authentication. A multi-user access model. A bunch of infrastructure I was excited about but didn’t actually need.
I ran a technical review through Factory Droid using the compound engineering workflow, and it had opinions. Good ones. The security review caught some real edge cases. But the more useful part was the scope review -- the questions that forced me to articulate who this was actually for and what it actually needed to do.
MagicLink login: who are the users?
Me. I’m the only user.
So you need a multi-user auth system because...?
I don’t. I need a token in an environment variable that keeps the app from being completely open to the internet. That’s it.
The 13-file architecture became four Python files. FastAPI on the back end, plain HTML on the front. No frontend framework. No login system. No unnecessary abstractions between me and the thing I was trying to build. Each cut made the project smaller, clearer, and more likely to actually get finished.
Constraints aren’t limitations. They’re decisions made in advance. Every door you close before you start coding is a door you don’t have to reason about at 11pm when you’re tired and tempted to just add one more thing.
The final stack:
Python FastAPI backend
HTML frontend (no framework)
Deepgram for dictation (fast API, supports custom dictionaries)
Claude API with a three-model setup: Sonnet 4.5 for daily work, Opus 4.5 when I need to go deep, Haiku when I need something quick
Railway for deployment, with a Railway volume as the persistent file system
GitHub as the deployment trigger -- commit, Railway builds the container, done
No magic. Boring in the best way.
---
The Agentic Part (This Is the Bit That’s Actually Different)
Here’s where Spray is something different from a fancier writing tool.
The agent isn’t just responding to prompts. It has tools -- read files, write files, list files -- and it uses them autonomously as part of doing its job. When I give Spray a dictation and ask for a draft, the agent reads the relevant voice profile, reads corpus samples from my existing writing, checks if there are previous drafts of this piece, and then writes something. It’s not working from the context I happen to paste into the prompt. It’s building its own context before it acts.
This is what “agentic” actually means in practice, as opposed to the way the word gets thrown around. It’s not “AI that seems smart.” It’s an AI that takes sequences of actions -- with real tools, against real files -- in service of a goal you’ve given it. The difference in output quality is not subtle.
The file system was important to me for exactly this reason. I wanted the agent to have persistent memory across sessions in a form it could actually use. A Railway volume attached to the container gives it a real directory structure -- voice profiles, corpus samples, project folders, draft files -- that it can read from and write to on its own. Not a database the agent queries through an API. An actual file system the agent can navigate like a collaborator with filing access.
The context layer is where I spent the most time before I wrote a single line of application code. Voice and tone documents for each writing context. Corpus samples from my existing published work. Custom Deepgram dictionaries for each project -- because when I’m dictating something for BishopricOS, I use different vocabulary than when I’m dictating something for Unruly Context, and I don’t want the transcription guessing. Each custom dictionary took some upfront time and paid back immediately in transcription accuracy.
The agent also has skills -- structured instruction files that tell it how to handle specific tasks. How to triage a new dictation and recommend the right output format. How to draft for each of my different newsletters. How to generate derivative social posts once a main post is approved. The skills aren’t just system prompts; they’re persistent files the agent reads when it needs them, so they stay organized and editable without touching any code.
The result is something that feels -- and I want to be precise about this -- like working with a collaborator who has read everything I’ve written, knows what I’m trying to build, and remembers where we left off.
---
What It’s Like to Actually Use It
I’m on my iPad right now. On the couch. This was the specific thing I wanted to be able to do, and it’s working.
The workflow for a new post is: I open Spray, I tap the dictation button, and I talk. The Deepgram integration is fast -- transcription happens in near-real-time, with the custom dictionary doing its job to get the vocabulary right. I review the transcription, occasionally fix a word or two, and submit it to the agent.
The agent triages it -- is this a full post? A short note? Which newsletter? -- and tells me what it thinks before it does anything. If I agree, it goes and does the work: reads the relevant files, drafts something, saves the draft to the project folder. I read the draft, dictate my feedback, and we iterate.
The model switching turned out to matter more than I expected. Sonnet is fast enough for most of the back-and-forth, but when I need the agent to really wrestle with something -- voice calibration on a tricky piece, structure for a complex idea -- Opus earns its cost. And for quick tasks where I just need the agent to check something or run a formatting pass, Haiku is fast enough to feel instant.
The derivative stack is one of my favorite things. Once I approve a main Substack post, I can ask Spray to generate all the social derivatives at once -- the Substack note, the LinkedIn post, the X post, the OG metadata and image prompt. What used to be a multi-session, copy-paste-and-reformat process is now a single conversation. The agent has the context of the main post, knows the platform rules for each derivative, and produces all of them without me re-explaining anything.
---
The Thing That Surprised Me
I went into this expecting the dictation piece to be the revelation. It’s good, and the Deepgram integration works better than I hoped. But the thing that actually surprised me was how different the agentic experience feels from prompting.
When I’m prompting a model -- copying in context, asking for something, copying out the output -- the AI is a tool I’m operating. When the agent has file access and real task structure, it’s a partner I’m directing. The distinction sounds semantic. It isn’t.
The agent tells me when a dictation doesn’t have enough depth for a full post and should be a Substack note instead. It asks clarifying questions before it drafts because it’s read the skill files that tell it to. It saves drafts with consistent naming conventions because that’s in the instructions. It does things I would have had to remember to ask for.
There’s a reason I’m more productive in a good coding session with an agent than I am with a static code assistant. The agent is doing work between my inputs, not just responding to each one. Writing agents can do the same thing, and now I have one.
---
What’s Still Unfinished
The app works. I’m using it daily. But there’s a list.
The mobile experience is fine, but it could be better. I built for “works on iPad” not “optimized for iPad,” and the difference shows up in the small things -- tap targets, the way the dictation button behaves when you’re holding the device sideways. I’ll get there.
I haven’t built the feedback loop yet. Right now, when a post performs well or a draft gets significantly revised before publishing, that learning doesn’t flow back into the corpus or the voice profile automatically. I have to do it manually. That’s fine for now; I’m one person writing for three newsletters. But the system is designed to support that loop, and I’ll build it eventually.
And there are almost certainly edge cases I haven’t hit yet. The technical review caught several before launch, but production always reveals things that testing doesn’t.
That’s not a problem. That’s just what it looks like when a thing is alive.
---
The name is embarrassing in the best way. Every time I open the app I think of a ten-year-old me getting teased for talking too fast and getting too excited.
Say it, don’t spray it.
Sure. But also: say it. Get it out. Don’t let the gap between the idea and the polished draft be the reason the thing never exists.
That’s what Spray is for.


