the schematics, as far as anyone got.
Maiko is a small Flask brain, a SQLite memory, and a React face, all running on your laptop. Agents are subprocesses in their own git worktrees. Here is how the pieces move. Click any bar to fold it.
// one process, no cloud
Nothing here is a service you call. It is one machine talking to itself, with the model doing the parts that need judgement and plain code doing everything that does not.
A Flask app with a background loop. It holds the API, the brain cycle, and the plugin host. It never blocks the UI to think.
One SQLite file. Pupdates, tasks, jobs, agents, insights, learnings, memos. Schema changes land as idempotent column patches, not migrations you run by hand.
A Vite + React app, also wrapped as a desktop shell. It reads the same API a curl command would. The world is a view, not the state.
Agents are coding subprocesses, each in an isolated worktree with its own CLAUDE.md. They talk back to the brain over a CLI, not a socket.
// a loop wakes on a cadence and walks fixed phases
Each phase is allowed to notice things, decide things, and hand work off. Plugins hook into it instead of running their own timers.
Pollers and plugins fold in what changed since last tick. One on_cycle_tick per cycle, so a plugin never has to own a thread.
New pupdates get sorted: ignore, surface as a memo, or become a task. Automations get their chance to fire here on plain predicates.
The home overview is regenerated: a short, voiced read of what is going on and what you would probably look at first. A greeting, not a dump.
Stuck jobs, awareness checks (two agents about to collide, who knows this area), and the end-of-day campfire when it is time.
// everything that happened
A pupdate is the one event type. A PR went stale, a build broke, a calendar block is starting, an agent finished, you left a comment: all of it lands as a pupdate. Pollers and plugins emit them; the cycle triages them; automations match on them. Because there is exactly one substrate, a new integration only has to answer one question: what pupdates do you make? Everything downstream already knows what to do with them.
// the trigger layer is deliberately dumb
A rule is a predicate over pupdates ("a PR I am tagged on went stale") and an action ("leave me a memo", "wake the reviewer"). No model runs to decide whether a rule fired, so it is fast, legible, and never surprises you.
Pure matching on pupdate fields. Same input, same answer, every time. You can read a rule and know exactly when it goes off.
Resolved from a name to a handler: memo, task, wake an agent, or a custom one a plugin registered.
// task → job → worktree → back to you
A task is the thing to finish. An AgentJob is the one
identity for a run: every session, automation kickoff, skill, and
UI launch routes through it, so there is a single place that knows
what an agent is doing and a single lock that stops two triggers
from racing.
Maiko cuts a git worktree and writes TASK.md and a CLAUDE.md (role protocol, the agent's character, the team's active insights). Siblings cannot step on each other.
The agent commits to its branch and runs maiko reply from inside the worktree. Talk-back is shell commands, not MCP tools, on purpose.
The brain watches job state instead of trusting it blindly. Stuck or silent jobs get noticed and nudged on the next cycle.
You read the diff in-app and leave inline comments. Request changes and the same job wakes, reads the comments, and goes again.
// one loop, three ways to run a model
Because an agent talks back over the CLI, the thing running the model sits behind one interface. The lifecycle above does not change when you change the runtime.
One-shot claude --print. The default. Fire, work, talk back, done.
A persistent pane you can attach to and watch. New prompts inject into the live session instead of starting cold.
An Ollama runtime for the parts you would rather keep entirely on the machine. Same job, same talk-back.
// the memory that maintains itself
Review feedback and an agent's own confessed mistakes are signals. They graduate into two durable things: insights (short tribal notes that go in the playbook and inject into every new agent) and learnings (coding rules mined from your PR review history). Learnings are embedded locally and retrieved by relevance: an agent says what it is about to do and gets back only the handful of rules that apply, so hundreds of nits never drown a prompt. The embedding model runs on your machine. Nothing is sent out to score it.
At the end of the day the pack gathers at the campfire and shares what it learned. You approve what sticks. Tomorrow's pack wakes up with it. That ritual is a feature, not decoration.
// for anything you never want to look at again
Put a file in ~/.maiko/plugins/ or ship it as a pip
package that registers an entry point. It is discovered on startup
and can opt into any of these without owning a single timer or
thread.
Subclass the poller and define what you fetch and what pupdates it becomes. The cycle drives the interval.
Register a named handler and it shows up in the automation editor next to the built-ins.
Expose a one-time bootstrap action (import this, connect that) as a button in Settings.
Run on a brain phase or once per tick. The instant another tool starts yelling at you, make Maiko deal with it instead.
// read the long version
This page is the map. The full write-up has the corners: data and
persistence, the substrate in detail, A2A and the maiko
CLI, RAG retrieval, the parked LoRA experiment, awareness,
guardrails, and the sharp edges.