← Back

Chatto is Robots

I’ve recently open-sourced Chatto, my self-hostable team and group chat app that I’ve been working on for the past 9 months or so. I think it’s really good! Everyone who’s tried it seems to agree! (Which I appreciate so much! The positive reaction to Chatto has been humbling.)

But here’s the kicker: my workflows in building and maintaining Chatto are pretty much 100% agentic; I have, in fact, not written a single line of code since February this year. Yet, I have never felt as involved in a codebase as here. Agentic engineering has allowed me to spend a much larger portion of my time reasoning about the code instead of typing it out. The result is what I feel is the best application I have ever built.

In this blog post, I want to give you a good overview of my workflow. If you fundamentally hate the very idea of software being built with the help of AI, I’ll humbly ask you to make the effort to keep reading; your notion of what it means to work with coding agents may be incomplete, and maybe this post will help paint a more nuanced picture.

(Oh, and by the way: yes, I wrote the entire post myself. Having AI write blog posts is stupid! Don’t do it!)

Let’s dive in. The three pillars of my workflow are tools, agent guidance, and code reviews. I’m going to go through each of these one by one.

Tools

Models and Agents

I’ve had a €200/month subscription with one of the leading frontier labs since around December last year. Up until two months ago, this was Anthropic’s Claude Code; I have since fallen out of love with them and switched to OpenAI and Codex. Claude has become a weirdly unreliable mess, and Anthropic generally has some outright unpleasant views on what their users are allowed to do with their subscription and not. OpenAI’s models are fantastic, get to the point faster, and are way less obnoxiously chatty than Anthropic’s; the codex CLI is a significantly better computer citizen than claude; and to top it all off, OpenAI are being very generous with the many rate limit resets they’re handing out.

I don’t love paying €200 a month to a US-based frontier lab. It’s a lot of money — I keep referring to it as “half a PS5 every month”, even though this is no longer accurate — and being this dependent on a vendor, and one in the US to boot, is not great. But I see these as temporary caveats, and ultimately, the value this subscription provides is immeasurably higher. It allows me to move extremely fast by basically stripping most of the tedium from building complex applications. Some form of Chatto would exist without it, but it probably wouldn’t be as good.

It really is the easiest €200/month I’ve ever spent.

But like many others, I, too, am looking forward to local models eventually taking over “the bottom 80%” of this work. I’m super excited about the things happening in this space, and I’m convinced that 5 years from now, we will look back at 2026 and laugh at ourselves for shelling out all this money just to have a supercomputer write some Go for us. (Unless that supercomputer kills us first, an event that I reckon by now has a 50% chance of happening. Bring it, robots!)

Multi-Agent Orchestrators

Coding agents sometimes take their sweet time. If you’re just running a single session, you’ll end up with plenty of downtime.

The answer to this is parallelization; just run a bunch of agents working on different tasks in parallel. You can’t feasibly do that in the same directory, so you use git worktrees in order to give each agent its own little space to work in. Within each worktree, you’ll want to run your project’s setup task, and a copy of your coding agent. All this adds up, and you don’t want to spend half your workday manually managing git worktrees and running scripts, so instead, you’ll use an agent orchestration tool that does these things for you.

And there are so many of these out there. The one I’ve been using for the past half a year or so is Conductor, one of the few closed-source options. I don’t love that aspect of it, but other than that it’s generally fine. It’s also had some pretty terrible performance issues in the past; the main reason why I’m still using it is that I’ve grown accustomed to it, and switching to another tool introduces friction that I’m not eager to deal with.

But when I do eventually switch away from it, my new tool of choice will likely be Paseo. It follows the same UI paradigm as Conductor — they all do — but it’s open source and has a much saner architecture, with a server process running the actual agents and the UI only being a client that connects to it. It’s how I would have imagined such a tool to work if I had built my own, and I appreciate that.

In Conductor, starting work on a new task is one Cmd-N away; I enter the task, Conductor sets up the worktree, initializes the project, and gets going. When the agent is done, I get a notification; I can then review the code it has written, or click on a “Run” button to start a local copy of the project (this is configurable, and Conductor can assign per-worktree port ranges so you can run multiple copies of your app with no conflict). Another button opens the project in my browser for testing.

When there’s something I need to discuss with the agent, I can just keep the conversation going; the PR view even lets me leave comments on individual diff lines, like some sort of mini GitHub. There’s a big “Review” button that starts a second agent session with instructions to review the changes; Conductor lets you configure different agents and models for these if you want that.

Once I’m happy with the results, another button will post them as a PR with a single click; Conductor will notify me when CI is green, and then yet another button lets me merge the work into my main branch. If CI fails, the “Merge” button instead says “Fix errors”; one click on that will instruct the agent to inspect the CI output (Conductor will attach it to the prompt) and handle the failures.

This is what it looks like:

A screenshot of Conductor

That’s the high-level workflow. Start a new task, review the code, post a PR, handle CI, merge. Just like you’re used to! Except now you can just do multiple of these in parallel.

You might think that if it’s all just about clicking the next button to move things forward, why not fully automate it? I understand the sentiment — but I think it’s the fact that the user is still making these decisions that’s ultimately making Conductor and its ilk so powerful. I don’t believe that full automation is or should be a goal, and recommend that you be wary of people telling you that it is.

Agent Guidance

The most important thing you must understand about coding agents and LLMs in general — and that a surprisingly large number of people fail to — is that they’re not tiny little oracles that just hold the answers to your questions, or can magically whip up things you ask of them.

Try to think of LLMs and agents as natural language automation glue. I’m convinced that once you internalize this, you’re a better AI user than 95% of everyone else.

Agent Skills

Your primary tool for automation is Agent Skills. They’re your secret superpower, acting as a surface for codifying your workflows, preferences, style guides, and more.

Custom skills are what turn a model that can count the number of “r” in “strawberry” or not into a machine that knows the ins and outs of your project. I believe that writing and maintaining good skills contributes 90% to your agentic engineering success, and much more so than just downloading collections of random third-party skills (I would strongly advise against doing so for more reasons than one.)

I’m going to describe some of my skills from the Chatto repository.

Glossary and Architecture Inventory

First of all, for any non-trivial project, you have to establish some language. The glossary skill takes care of this, creating and maintaining an inventory of terms used in the project and what they refer to. You will have a much easier time if you tell your agent to modify the “Room Sidebar” than you would have with “you know, the sidebar on the right-hand side”.

Similarly, chatto-architecture-inventory creates a categorized inventory of relevant items from the project’s architecture; in Chatto’s case, this includes NATS/JetStream resources, a realtime event inventory, a list of runtime components, and more. This helps each fresh agent session to get up to speed with what’s contained in the rest of the repository without having to crawl its way through it first.

Architecture/Feature Decision Records

The most useful skill by far is probably adr. It instructs the agent to create and maintain a directory containing Architecture Decision Records; essentially Markdown files that describe individual aspects of the project’s architecture, and decisions made around them. Not only is this really useful documentation for coding agents; since these are versioned alongside the code, you can trace back how they mutate over time through the repository’s history.

The fdr skill does the same thing, but for features (things the users can see and/or interact with.) I don’t think “FDR” is an established term, at least not in the sense of “Feature Decision Records”, but I thought it fitting. Here’s the one for Voice Calls.

Letting the Agent write Documentation

Even though I believe that blog posts should be written by humans (like this one is), I’m a little more relaxed when it comes to documentation. Large parts of Chatto’s documentation are written by my agents, with a technical-writing skill in place to help them strike the right tone.

The chatto-release-notes skill helps me whip up release note pages like this one for the Chatto 0.4.0 release. It contains very specific instructions that help it decide what constitutes a noteworthy change and what doesn’t, and also to not outright overwrite existing copy (because I might have made some manual changes to it.)

Project Status

Even though skill recall and activation have been significantly more reliable with the OpenAI models I’m using now, they can be hit-and-miss, so I’m keeping the most important rules, very boringly, in my AGENTS.md files. One that has proven extremely valuable time and again and that I wholeheartedly recommend you add to your own projects is a line or two describing the current status of the project. For Chatto, these currently read:

  • Chatto is public, self-hosted, and has real user data.
  • The project is pre-1.0, but people are already self-hosting Chatto, so we want to avoid breaking changes where possible. For new API surface, prefer new protobuf fields on existing protobuf types, then new protobuf types. Only implement breaking API changes if absolutely necessary, but discuss this with the user first. Changes to the core protobuf messages (used by our persistence layer) must never be breaking.
  • Assume that mixed versions are in use in the wider ecosystem; but self-hosters have been advised to track :latest, or upgrade to newly released versions quickly.
  • The next planned version is 0.5.0. There’s a 0.5.0 milestone on GitHub, but also we’re locally tracking planned features and changes for 0.5.0 in docs/TODO-0-5.md. Please use these for guidance, and update them as we cross off features from the list. Do not add to the list unless the user specifically asks you to.

An earlier version basically said: “Nobody is using this yet, make any change you want.” The extent to which this helps your agents make the right decisions about architectural changes, new features, or just the order in which to implement things is immeasurable.

Empowering the Agent with Data

I have a separate, private repository that acts as an umbrella for all the different repositories that make up Chatto and Chatto Cloud. In this repository, I’ve added some extra skills that teach the agents about my infrastructure: what Kubernetes clusters there are, what each of them houses, and what other non-K8s systems are out there.

The agent has access to its own read-only service account on the entire infrastructure, which itself is entirely managed through GitOps (OpenTofu, FluxCD, et al.). It can answer questions about the cluster, read logs, and access my Prometheus metrics storage. When something unexpected happens, I ask the agent first; it will immediately get to work, tap into the data sources that help it identify the cause of the issue, and then help me implement the solution, which more often than not is a change in the Chatto code itself, which it quickly implements and posts a PR for. If a change to the infrastructure is required, this goes through my GitOps repositories, too; PR and everything.

And I’m documenting these incidents, too: there is a postmortems skill that instructs the agent to maintain a log of postmortems, similar to the ADR and FDR inventories mentioned earlier. Postmortems are useful documentation, and the frontier models have been extremely good at writing them.

Tests and Lints

As you can see, my workflow is extremely PR-heavy. Not only do PRs give me a clean interface for reviewing code before merging it, but the CI jobs that run there are just as important.

Chatto has an extensive test suite with literally thousands of tests, from Go unit tests to Vite-based component tests to a full suite of Playwright-driven end-to-end tests. I’ve made it a strict rule in my repository to cover every change, new feature, or bug fix with tests, and this test suite is the most important guardrail in keeping the codebase functional and maximizing agentic velocity (because it’s also useful for the agent to know when it mucked something up.)

An important facet of this is execution speed. In other projects, a test suite made up of thousands of tests, a large part of them fully end-to-end, sounds like the typical nightmare CI that runs for 45 minutes and then aborts with a flaky test failure; in Chatto, thanks to its architecture (which I will write more about in a separate blog post), the entirety of the test suite takes 5-7 minutes to complete. I have invested a considerable amount of effort into making this fast (also with agentic help, of course), and all of that is paying dividends with every commit I push.

Similarly, there’s a collection of linting commands that the agents will run before pushing a PR; enforcing some syntactic and semantic rules with tools or hooks that run outside of the fuzzy reasoning of an LLM is also extremely valuable (and I should, in fact, be doing even more of it.)

For frontend development, I have instructed the agents to keep an inventory of all Svelte components in a Storybook project, in a typical design system-like structure. This also helps them make good decisions about how to cut components, and what should maybe just be a Tailwind utility class instead. Storybook is super lovely. Use it!

Code Reviews

A PR-focused workflow implies code reviews, and yes, I do them for every change the agents implement; no exceptions. But the way I’m doing code reviews may be very different from what you’re used to.

First and foremost, before I look at any actual code, I look at the list of files that have been modified or added. I have developed a mental map of my repository that roughly splits it into two distinct regions:

The squishy bits: the entirety of the frontend code, large parts of the backend code, all of the documentation. Basically anything that is easy to fix or improve later without ruining anyone’s day, or something where I have enough trust in the agent’s capabilities to use all the scaffolding we’ve discussed here, including the test suite, to not make a mess.

The sturdy bits: my Protocol Buffer definitions, because changes to them typically mean a change to either Chatto’s internal persistence model or one of its APIs; also, the backend’s HTTP router and similarly load-bearing parts of the codebase.

Of course, I have skills that codify this distinction; the chatto-pr-checklist skill specifically instructs the agent to call out changes to any of the sturdy bits:

  • If this PR contains changes to our protocol buffers, please notify the user.
  • If this PR adds, removes, or materially changes public API endpoints or compatibility behavior, please notify the user.
  • If this PR contains any other changes that you feel might be a breaking change, please notify the user.
  • Please make sure that the PR uses Conventional Commit syntax, and PRs that ship breaking changes are marked accordingly.

But more importantly, once I see changes to them, or the agent raises them, that’s where I give them a thorough review, and more often than not, I do have feedback for the agent and prompt it to refine things before we merge.

The squishy bits? I often give them a glance, but I don’t care that much about them. The Chatto repository has accumulated quite a lot of rules that codify my preferences (“don’t use $effect if you could use $derived or an attachment” and similar), and usually, they work fine; every now and then I perform dedicated cleanup sessions (“find any instances of $effect and see if you can replace them with $derived or an attachment”). Svelte friends, you know what I’m talking about!

Will an expert Svelte coder look at Chatto’s frontend code and nope out in horror? Probably! But it turns out that many of the things that are offensive to a human just don’t matter all that much to a machine (and like with any rule, there are exceptions to this one, too.)

In Summary

And there you have it: this is how I’ve worked on Chatto for the last couple of months, shipping stupid amounts of features, but also wide-reaching architectural refinements, without any major breakage or too much of the usual slop smell that AI-coded projects often have.

I’m looking forward to continuing to work this way, but also to witnessing how it evolves over time. Because everything that I’ve written here will be obsolete a year or two from now, which to me is super exciting.

I’m also eager to find out how this sort of workflow can scale beyond a single engineer. This is going to be an extraordinarily tricky (but interesting) undertaking, and I already have some thoughts and opinions on that — but that’s going to be a separate blog post.

Thank you for taking the time to read this; hop on the Chatto HQ community to get in touch if you want to have a chat about my use of agentic engineering, Chatto, or anything else, really.