wayanjimmy
EN

Building Laras-Bot: From Takopi, the Utah Pattern, to the Dream of Coding from a Phone

A few weeks ago, I tried Takopi — a CLI tool that runs coding agents and connects them to Telegram1. The idea is simple yet incredibly powerful: an automation layer on top of a coding agent, with a chat interface as the gateway. When I tried it, it immediately felt like it “clicked.”

However, after using it for a while, I started feeling like something was missing—especially in terms of observability while the agent was working. Then I came across a post demonstrating a pattern that perfectly filled that gap. Ultimately, this led to a personal project that has now been running for nearly a week in my homelab.

This is the story of how laras-bot was born.

Takopi: When Everything First “Clicked” #

Takopi is essentially an automation layer on top of CLI-based coding agents. It supports multiple engines like Pi and Codex and provides a gateway to Telegram.

What got me excited wasn’t just the features, but how it manages projects:

  1. Project registration: Just run takopi init in a repo and create an alias. From anywhere (including via chat), you can simply send a command like /happy-gadgets do something.
  2. Topic binding: Each topic in Telegram can be bound to a single project. This keeps conversations organized: one topic = one project context.

This is very similar to the UX in Slack or Discord: each channel discusses something different, contexts are separated and not mixed up. From here, I thought, “This is the way I want to work from now on.” But as I mentioned, there was still something nagging at me.

The Gap to Fill: Resilience & Error Monitoring #

Actually, Takopi is already great; it even has streaming output to see what the agent is doing. But my main issue was more about resilience and monitoring when errors occur.

Learning from my experience with OpenClaw, many cron jobs or chat sessions often hit silent errors—they just stop without warning, or fail without us knowing at which step or why.

For small tasks, it’s not a big deal. But if an agent needs to run for 10-30 minutes—for example, during a major refactor or complex debugging—I need deeper visibility:

I needed a system that was more “stubborn” and could clearly tell me when something went wrong. That’s when I felt I needed something more than just streaming logs.

The Utah Pattern: Agent Loops as Durable Workflows #

Then I read a blog post by JoelClaw about Utah2, and he linked to an open-source project called Utah from Inngest3.

Utah provided an implementation example that really made the pattern click for me. The idea is this:

Every interaction with the agent is orchestrated as a durable workflow. Every step (think, act, observe) is a step that can be monitored, retried, and traced.

The flow looks something like this:

text
Incoming message → trigger workflow
  ├── step: acknowledge (show "typing...")
  ├── step: think (LLM call)
  ├── step: tool-read
  ├── step: tool-exec
  ├── step: think again (loop)
  └── step: send-reply

Plus some cool additions:

What makes Utah interesting isn’t just the code, but the pattern. This is a pattern that keeps surfacing in various projects: Pi has session management, Takopi has project management, Utah has workflow durability—it all seems to “converge” on the same set of needs.

Converging Inspirations Lead to Laras-Bot #

JoelClaw’s writing pointed to something I’m now increasingly convinced of: there are architectural patterns being independently discovered by many people, and they all converge into a similar form.

From every project I studied, several components were always present:

ComponentTakopiUtahPi
Transport/GatewayTelegramInngest CloudTerminal
OrchestrationInternalInngest (durable)Session management
Project/ContextProject + branchWorkspace filesSession dir
MemoryFile-based + distillationSession file
ObservabilityMinimalInngest dashboardInternal

Each has its own strengths. But the problem was: none of them had everything in one package.

Decision: Build It Myself #

From there, the decision was clear: I wanted to combine the best parts of each pattern, but using infrastructure that was already in my own homelab.

My chosen tech stack:

Why Mattermost instead of Telegram?

Takopi chose Telegram because it’s easily accessible to everyone. But for my needs, Mattermost made more sense:

  1. Self-hosted: Data doesn’t pass through someone else’s servers.
  2. Slack-like: The UX is what I’m used to at work, so it’s more familiar.
  3. Channel = Project: Each channel can be bound to a single project, exactly like the Takopi concept but cleaner.
  4. Full control: Can be customized and monitored as I see fit.

And most importantly: Temporal was already running in my homelab. So if I want to see the workflow dashboard, I just open the web UI, and I’m done!

Laras-Bot: The Architecture #

I named this project laras-bot. The code is in its own repo and it’s currently running smoothly as a systemd service.

Main Flow #

laras-bot interaction in Mattermost Example of interacting with laras-bot in Mattermost.

text
Mattermost (WebSocket)
  → Message received
    → Project Router (determines which project)
      → Dispatcher → Temporal Workflow (chatThreadWorkflow)
        → Activities:
            ├── ensureSession (create/resume session)
            ├── createRun (record run in DB)
            ├── executeEngineTurn (run engine CLI)
            └── finalizeRun (save results)
              → Engine Adapter (Pi / Gemini)
                → Events via NATS → Progress Consumer → Mattermost

Temporal Workflow: chatThreadWorkflow #

This is the heart of the system. I designed this workflow using a drain-and-exit loop pattern inspired by Utah’s singleton per chat concept. The idea is simple: only one active workflow per chat thread is allowed, so two agents won’t fight over the same context simultaneously.

The flow works roughly like this:

  1. Wait for an incoming message (via signal).
  2. Process the message: ensure sessioncreate runexecute enginefinalize.
  3. Check if there are still messages in the queue.
  4. If empty, wait for a 10-second timeout and then complete the workflow.
  5. A new workflow will automatically start if a new message arrives later.

All of this is clearly visible in the Temporal Web Dashboard. I can trace every step, view the event history, and easily debug if something looks off. No more silent errors without a trace.

Engine Adapter: Pi and Gemini #

So far, I’ve implemented two engines:

Pi: Because it has the pi-ai package that can connect to various AI providers. The great thing is I can easily switch providers and use the --append-system-prompt feature to inject the agent’s persona.

Gemini CLI: I really like the Gemini model, and the CLI is getting better and better. Plus, a Google One AI Pro subscription offers great value: you get Gemini CLI, NotebookLM, AI Studio, etc. The price is similar to other AI subscriptions but with a ton of features.

Both engines run as child processes. The adapter captures the JSONL output, cleans up the events, and sends them via NATS so we can see real-time progress in Mattermost.

Scheduled Tasks via Temporal #

Temporal Dashboard - Scheduled Tasks View of scheduled tasks in the Temporal dashboard.

This feature makes daily tasks fully automated. The cron is managed directly by Temporal, which is better because:

Some tasks that are already running automatically:

TaskSchedule
Brain auto commitevery 4 hours
Brain qmd updateevery 6 hours
Daily Brain Memorydaily
Morning Journal & Weatherdaily morning
Daily Journal Retro Checkdaily afternoon
Monday Blog WriterMondays
Sunday Weekly DigestSundays

Temporal Dashboard - Workflow Trace Trace detail for a single running workflow.

Review After a Week of Use #

laras-bot has been running for almost a week now. Here are some stats:

The most noticeable changes compared to before:

  1. Monitor from anywhere: Just open the Temporal dashboard to see all running workflows and trace them step-by-step.
  2. Restart without losing state: If there’s a connection issue, I just restart the server. Hanging workflows will automatically resume because their state is safe in Temporal.
  3. Full control: Everything runs in my homelab, with no dependence on third-party services for orchestration.

Not Just About Tools, But Patterns #

In the broader context, this story is a continuation of my previous post on the evolution of coding agent workflows4. From Cody to Amp to Pi, the focus has always been the same: context must be maintained, sessions must be separated, and agents must have clear roles.

Laras-bot adds another layer: orchestration must be observable. It’s not just about the agent running; we need to see how it’s running, where, and for how long.

The patterns emerging from Takopi, Utah, Pi, and laras-bot are essentially the same:

  1. Separate context per project/thread: Takopi uses topics, laras-bot uses channel binding.
  2. Durable execution: Utah uses Inngest, laras-bot uses Temporal.
  3. Engine abstraction: All of them allow for switching engines (Pi, Gemini, etc.).

While the implementation details differ, the patterns are strikingly similar. This makes me increasingly certain that this isn’t just a trend, but the “right” way to manage coding agents at a personal scale.

What’s Next? #

There are still a few things I want to improve:

But for now, the most important part is achieved: a coding agent that is observable, easy to manage, and on standby 24/7 in my homelab. Excellent!


The laras-bot project is a personal tool I built for my own needs. The code is not yet open-source, but the concepts and patterns can be learned from this post.

Footnotes #

  1. Takopi - CLI tool for coding agents

  2. JoelClaw - Utah: Convergent Architecture

  3. Utah GitHub Repository

  4. The Evolution of Coding Agent Workflows

  5. Agent Abstraction Inspiration - Anvie on X

Subscribe