Anton, chapter 9: Threads and the cast
The chapter began with Clara.
She had become a real, non-technical co-owner of Anton, and her user rule was simple: answer clearly, avoid jargon, and escalate to me when necessary.
That small instruction changed the next ten days.
A second real user turns rough edges into real complaints. The system no longer had the luxury of being understandable only to the person who built it.
Several things at once
The largest change was threads.
Until then, one run meant one conversation and one in-flight loop. Anything else waited.
That was workable for a personal assistant serving one person. It was not workable for a household where media triage, condominium reconciliation, school messages, and ordinary questions might all arrive at once.
Anton gained a thread registry backed by Redis.
Every run carried channel, group, parent, and thread identifiers. Incoming messages could join an active thread or begin a new one. A runtime tool allowed an agent to spawn child threads and fan work out in parallel.
The dashboard received a live event stream so I could see what each thread was doing.
Then came the details that turn concurrency from a demo into infrastructure: atomic injection, final draining, cancellation that cascaded from parents to children, limits on fan-out, and time-to-live caps that prevented abandoned work from living forever.
By the end of the day, Anton could run several long-lived tasks in parallel while users continued speaking to the relevant one.
Concurrency changed the assistant from a conversation into a small operating environment.
Libraries should be boring
An old package disappeared next.
The original @anton/skills package had gradually become a barrel of exports after skills moved into hot-reloadable Deno units. It no longer represented a real architectural boundary.
I deleted it.
The new layout followed three rules:
- Skills live as first-class deployable units.
- Domain
_libfolders contain small, stable helpers. - A narrow shared facade exposes only what the Node-side runtime needs.
The distinction became:
Libraries are boring and fixed. Skills evolve.
When a helper needs frequent changes to support new behaviour, it is probably becoming a skill.
The same effort produced a storage decision tree: facts for free-form memory, collections for typed items, files for blobs, and the family vault for permission-aware documents.
A one-page decision rule was cheaper than allowing four overlapping systems to drift for another month.
Permission to improve, not permission to rewrite
The coder agent gained three write scopes.
Tier one could change prompts.
Tier two could change prompts and skills.
Tier three could change any code.
The scope was assigned per invocation, and the coder could not elevate itself.
This made the self-improvement loop safer.
A system fixing a prompt regression should not gain permission to rewrite its own runtime merely because that would be convenient.
Autonomy should expand by explicit delegation, not by inference.
Spawn and awakening
Replication had produced copies of Anton. The mesh had allowed them to communicate.
Spawn and awakening turned those pieces into a lifecycle.
Spawn happened on the parent side: provision infrastructure, copy prompts, seed an identity, and register the new instance in the mesh.
Awakening happened on the clone: ask its operator what it was for, run diagnostics, establish its own context, and retain a mentor channel back to the parent.
A clone was no longer only a Docker stack.
It woke up, learned who it served, and joined its peers.
The language is deliberately anthropomorphic because the lifecycle itself had become easier to understand that way.
The cast
The local Gemma, Gustav, became the default model for every agent.
Because prompts lived in the database and models were addressed through stable names, the switch was one configuration change.
Local-first inference reduced cost, but it also revealed tasks where a stronger cloud model still mattered.
The answer was not to send everything to the expensive model.
It was escalation based on intent.
Routine requests stayed local. Research and difficult reasoning could ask for a specialist.
The model codenames became a cast: named characters with defined strengths, personalities, and roles. An agent could ask a specialist by name in the same way a person chooses which colleague to contact.
The naming had begun as convenient indirection in LiteLLM.
Now it became part of how Anton reasoned about collaboration.
A cast is easier to select from than a list of opaque provider IDs.
A heartbeat that mostly stays quiet
The heartbeat also became operationally useful.
Memory writes became idempotent so the same observation did not create duplicate facts. A topics collection recorded what had already been noticed. The heartbeat became thread-aware, avoiding interruptions during active conversations.
Most importantly, it learned not to repeat itself.
A proactive system is useful partly because it speaks.
It is trustworthy largely because it knows when not to.
The outbound gateway made quieting bookkeeping messages a single change. The response simplifier removed formatting artifacts before they reached Clara. Notification retries began actually sending the corrected message rather than only removing the bad claim.
By the end of the period, the heartbeat observed continuously and remained silent most of the time.
That was the desired behaviour.
Closing the loop
The condominium domain gained its first write path through Playwright.
Reading, extracting, and reconciling had already worked. Writing meant Anton could begin closing the loop rather than only reporting what it found.
The path was still browser-driven and therefore provisional. The next step would be the same one as before: observe the calls, identify the stable HTTP interface, and replace the browser once the deterministic route was understood.
Explore agentic. Build deterministic.
At the end of the chapter, Anton had one identity, ten domain agents, multiple concurrent threads, and a cast of model specialists.
Prompts lived in the database. Local Gemma was primary, with cloud escalation when needed. Skills ran in scoped Deno Workers. Secrets were encrypted. Clones could be replicated, spawned, awakened, and connected through the mesh. The heartbeat watched quietly. The self-improvement loop tracked deployments and regressions. WhatsApp and Telegram shared one worker and one outbound gateway.
Most of that is infrastructure.
That is fine.
The substrate had become stable enough for the interesting behaviour to happen above it: threads working in parallel, specialists consulting one another, clones finding their role, and an assistant that could keep watch without constantly demanding attention.
The system was no longer only a collection of capabilities.
It was beginning to resemble a cast of processes that could coexist.