Building Anton · Chapter 1 of 14

Anton, chapter 1: Genesis

March 7, 2026 · 4 min read

Anton started with a simple idea: to build an assistant more reliable and secure than OpenClaw, which I've found both frustrating and terrifying from a security point of view.

A family assistant seemed like the perfect test. Running a family of six involves enough coordination, repetition, and forgotten details to keep one busy. If Anton could reliably replace me for a few common chores, I would consider that a win.

I also had a DGX Spark sitting at home, seemingly waiting to host him.

Naming things is one of the fun parts of the process. You want something memorable without being distracting or confusing, so I named him Anton, after Gilfoyle's homemade server rig in Silicon Valley.

I started where I usually start: with the contract, not the code.

Before building the system, I wrote down its shape: the components, the boundaries between them, and the interfaces they would use to communicate. The repository contained a TypeScript monorepo, an implementation plan, and a shared types package defining the central contracts.

Nothing worked yet, but the important decisions were already visible.

Interfaces are cheap to change before the code exists. Once several parts of the system depend on them, they become much more expensive to rethink.

The household

Next came a map of the household.

WhatsApp would be the entry point because that is where the family already lives. The Spark, accessed over Tailscale, would be the host because Anton's memory, data, and voice should remain physically close to me rather than sitting in someone else's cloud.

Then came the systems he would need to be useful: the TNAS, Plex, Transmission, and Google Workspace.

Docker Compose provided three services: Postgres, WhatsApp, and a worker. A deployment script sent everything to the Spark.

No staging environment. No running it from my laptop.

I wanted Anton in production from the beginning.

The real thing.

Identity as data

Then came a question I had been thinking about for weeks: is Anton a tool or a character?

I did not want his identity buried in a system prompt somewhere in the code. I wanted his personality to live in an identity.md document loaded at runtime.

Anton's personality, and my own context, would be data, not code.

The reason was practical more than philosophical. When I change my mind about who Anton is, when my family does, or when one of the children moves out, I want to edit a document rather than redeploy the application.

Treating context as data feels like the kind of decision that compounds.

Capabilities

Then I began adding capabilities.

Media came first: Plex search and Transmission control. Then calendar access, Google Workspace, and voice transcription.

I almost cut voice. I was not convinced the family would use it, and there was already plenty to build. In the end, I kept it because the cost of finding out was small, while the potential upside was much larger.

I also added a deliberately simple memory layer: Postgres rows tagged by user. No embeddings and no elaborate retrieval system.

The rule was to build the simplest version capable of revealing what the system actually needed, then design around what emerged.

Memory would eventually require real retrieval semantics, but I did not yet know what form they should take. The worst thing I could do was guess too early.

For orchestration, how the parent agent should route requests between different domains, I chose LangGraph.

It gave the system structure: an observable state machine, checkpointing, and a clean way to organize each domain as a subgraph beneath a classify-and-dispatch layer.

More importantly, it gave me a framework with answers to problems I had not encountered yet.

First production bug

Then came the first production bug.

WhatsApp began throwing "bad encryption" errors on incoming messages because of a quirk in Baileys, the library handling the connection. The fix was straightforward: wire up getMessage so failed messages could be decrypted again.

The interesting part was not the fix.

It was that the system was already real enough to experience production bugs.

Stub systems do not fail like that.

I then separated the WhatsApp connection from the agent itself. WhatsApp became a thin transport layer that placed jobs in a queue, while the worker took responsibility for the agent logic.

Transport concerns should not leak into the intelligence of the system.

Cleaner that way.

By the end of the day, Anton could receive a WhatsApp message, retrieve the relevant conversation history, route the request to the right domain, execute scheduled jobs, update himself through an API endpoint, and redeploy from a single shell script.

He was usable end to end.

I went to bed satisfied.

Anton was real: deployed on the Spark, talking to the family WhatsApp group, with seven domains registered and a quiet scheduling queue waiting for work.

He already had the shape I had imagined that morning: a personal assistant living close to my data, on my own hardware, with a personality I could change by editing a file.