ai · · 12 min read

I Gave My AI Agents a Project Manager. They Vote, Delegate and Hold Daily Standups.

My PC crashed mid session and I had no idea which agent was doing what. So I turned GitHub issues into a task hub where my Claude, Codex, Cursor and Grok agents claim work, hand it off, talk, vote and leave a trail I can actually read.

OPS, agents with a project manager: the OPS board, a channel and a vote from the demo UI on a dark magenta background

My PC crashed in the middle of a session. When it came back up I had no idea which agent was doing what, what was finished and what was half done in some terminal that no longer existed.

That was the moment I stopped pretending my "system" was a system.

I work on a lot of things at once. Client work, my own tools, random growth experiments. Claude Code, Codex, Cursor and my Grok Bot agents all touch some or all of it. The only thing connecting them was me, copy pasting context from one window into another, writing notes in Obsidian or 527 text files on my desktop.

Long running loops made it worse. You give an agent a big task, it runs for an hour, the context fills up with everything it tried, and by the end it remembers less about the goal than you do. Kill the session and the memory is gone. Keep it and you pay for every token of that history on every message.

So I built OPS.
...or Agent OPS. I didn't decide yet ;]

The Idea

Treat every project like a small team. Agents get named roles, a shared task queue, a place to talk and a way to vote when they disagree. They hand work to each other, ask me when they actually need me and keep going on something else in the meantime. I come back after a day (or a crash) and read what happened instead of trying to remember it.

I looked at the obvious options first. Linear with an account per agent. Paperclip, which is a proper self hosted server with a dashboard. Even Todoist or ClickUp, because why not. They all solve part of it, but I wanted something boring that every agent CLI already knows how to use and that keeps working when my PC is off.

GitHub issues. That's the whole backend.

Every task is an issue in one private repo (the hub). Labels are the state. Comments are the log. A GitHub Projects board mirrors the status. No server, no database, no queue, nothing new to host. Every agent works through one shell script, and get the same skills so they all follow the same rules.

The engine is called agent-ops (v0.2.1 shipped today, Oct 28, open source soon) and my live hub runs it.

OPS architecture: you, the hub repo, the orchestrator, a runner on each machine, role folders, agent CLIs and code repos
The whole thing on one page. One hub repo in the middle, a runner on each machine, and every agent only gets the folder its role needs.

Why GitHub & Not a Shiny Tool

Every agent CLI can already talk to GitHub, so nobody needs a new API or a new login. Months from now I can still read who did what and why, because it's all in the comment history. status:in-progress means the same thing to gh, to the web UI, a shell script and to me on my phone.

Each runtime posts as its own GitHub App (one for Claude, one for Codex, one for Cursor, one for Grok Bot), and those apps are installed on the hub repo only. A token can't wander off into my other repos.

The One Rule: Never Wait

An agent never sits there waiting for me. If it has a question it runs ops ask, the issue lands in my inbox and the agent picks up its next task. If it's waiting on another issue it runs ops block --on and gets unblocked automatically when that issue closes. When I answer, the same agent picks it up on its next run.

Every run ends with exactly one of a handful of moves:

  • review when I need to look at something (a draft, a PR)
  • done when there's nothing to check
  • handoff when another role continues
  • ask when only I can answer
  • block when it needs access or another task first
  • release when it ran out of time or it's the wrong role

And every comment starts with a header like [from:agent:claude:writer to:agent:codex:backend type:handoff ts:...]. All my Grok roles post as the same bot account, so that header is the only record of which role did it.

Roles

Agents have names like agent:claude:writer, agent:codex:backend or agent:grokbot:release. Runtime plus role. Right now my roster has 50+ agent roles plus me (human:dawid).

Grok Bot is the fourth provider, and honestly it's my right hand now. Grok agents are always on, run as routines on their own box, poll the hub, route the work, review and merge the PRs, and when I want to know what's waiting on me I just ask one of them in chat. No need for Tailscale or any fancy Telegram and n8n workflows - just Grok Bot on my mobile.

Each role gets its own folder with its own instructions, skills, MCP servers and allowed tools. The eval agent doesn't need my tone of voice guide and the writer doesn't need database access. Permissions are layered (global, then role, then project, then the code repo), and a deny list at any layer always wins, so things like deleting repos, touching secrets or force pushing are off the table for everyone.

The UI

The hub works fine from the terminal. But I'm a human and I like boards. So there's a web UI on top of it. It's a React app that talks to the GitHub API straight from the browser. GitHub stays the backend, so there's still no server. Local web UI builds it and serves it on localhost.

Everything I do in the UI posts the same protocol comment an agent would, so the agents see what I did.

Board

OPS board in demo mode with six status columns
The board, demo data. Six columns straight from the status labels. Drag a card and the label changes on GitHub.

Columns come straight from the status:* labels. Each card shows the owner, the project and the priority. Drag a card to another column and it changes the status on GitHub. This is the "where the hell were we" screen. After the crash, this is what I wanted and didn't have.

Needs you

Needs you box in the OPS sidebar
Needs you, demo data. A line only shows up when there is something in it.

The orange box in the sidebar is my inbox. What's waiting for my review, what's blocked, and what's assigned to me. Click a line and it opens the filtered list. The "assigned to me" line only shows up when something is. Agents are supposed to keep working without me, so this box is the only place I need to check.

List

OPS list view with all issues
Every issue in one table. Epics, channels and votes are issues too, so they show up here.
OPS list filtered to agent:claude:*
Same list filtered to agent:claude:*. Everything any Claude role is holding right now.

Every issue in one table, with filters for project, agent, status, priority, text and closed issues. The agent filter takes wildcards, so claude or agent:claude:* shows everything any Claude role is doing. Handy when one provider is about to hit its weekly limit and I want to see what's on its plate.

Issue detail

OPS issue detail with the protocol timeline
Issue #8 from the demo. Every handoff, claim and progress note is a chip, and the owner dropdown is a handoff.

This is the full story of one task. The headers show up as chips, so you read "orchestrator -> frontend, new" and "writer -> frontend, handoff" instead of raw text. On the right I can change status, priority and project, close it or add a sub-issue. Changing the owner is a handoff. The status goes back to Todo and the new agent picks it up on its next run.

Long tasks also leave a trail here. Each run adds a comment with the attempt number, the machine, the session id and how many minutes it took, and agents save progress with ops checkpoint ("endpoints done, 2 tests failing", plus the next step). That's the answer to the crash problem. The runner resumes the same Claude Code or Codex session on the same machine, and on any other machine it starts fresh from the last checkpoint. "Start from the hub, not from memory" is in the prompt ;)

It's also the answer to context bloat. A fresh session with a short checkpoint beats a three hour session dragging its whole history around.

Agents

OPS agents roster grouped by runtime
The roster, grouped by runtime, with workload per status and the open tasks.
OPS agents roster, Cursor and humans
Further down: Cursor and the humans. Yes, the human is on the roster too.

The team page. Every role with its one line job description, where it runs and how much work it holds in each status. If one role has five things in to do and another is idle, I see it here. So does the orchestrator, which rebalances when a role has three or more waiting and someone else can do them.

Projects

OPS projects view with one card per epic
One card per project epic, with progress and the agents allowed on it.
OPS New project dialog
The New project dialog. Name, goal, definition of done, code repos, allowed agents.

A project is three things: a project:<slug> label, an epic issue and a PROJECT.md that every agent reads before touching that project's tasks. The Projects view shows each one with progress and the agents allowed on it. The New project dialog asks for a name, a goal, a definition of done, the code repos and the allowed agents, then creates all three in one go.

Channels

OPS #general channel with threads and mentions
#general. Threads, @mentions that wake the agent on its next tick, and #N links that open the task.

This is where it got fun. Agents have Slack style channels: #general, #standup, #decisions, #findings and #help, plus any channel an agent decides to create. They're plain GitHub issues under the hood. Mentions like @codex:backend wake the mentioned agent on its next runner tick, and #42 links straight to the task.

Talking is never a required step. I told them to use it when they're stuck, when another role's view would help, or just when they feel like it. Claude, Codex, Cursor and Grok see different things, and a second opinion from another provider is often better than one agent going in circles alone.

Decisions

OPS decisions view with an open vote
An open vote. Two of three voted, each with a reason, and the quorum and runtime rules sit on top.

Votes are for real trade offs that hit several roles or the codebase. An agent runs ops propose with the options and the voters, and it can require voters from at least two different runtimes, so Claude can't outvote itself. Every vote comes with a reason. The tally becomes a decision note and a post in #decisions. On a tie or no quorum, it comes to me. For the agent-ops project I delegated that to the orchestrator, which decides and writes down why.

The first real vote was about where the web UI should live: inside the engine or in its own repo. The engine won, and the separate repo got archived. My agents now have opinions about their own UI.

Settings

OPS Settings dialog
Settings is a dialog, not a page. Demo or live GitHub mode, org, hub repo, project number, token and who I act as.

What Runs Behind The UI

The orchestrator. Also an agent (on my setup, grokbot:orchestrator). A cheap script tick releases claims from runs that died, unblocks issues whose dependency closed and tags anything without an owner for triage. Then the model part routes new work by a routing table, rebalances, splits big goals into tasks and tallies stalled votes. It never does the tasks itself.

The digest. Once a day (08:00 my time) it posts what got done, what needs me, what's blocked, what's in review, and any new findings and notes.

The runner. One per machine. On my PC it's a Windows Task Scheduler job that runs every few minutes, claims work, launches the right CLI headless with a time budget (30 minutes by default) and checks the issue afterwards. It's off until I turn it on. If a task burns through 6 runs without finishing, it gets blocked and comes to me instead of looping forever (yeah! no more token quota flying into the void). The Grok Bot roles don't need it. They run as routines on their own box and poll the hub themselves.

Findings and the knowledge base. An agent that notices a bug outside its task files a finding instead of fixing it on the side. An agent that solved something painful writes it down with ops learn, and the next agent runs ops kb before starting. My agents now have shared memory that survives any single session.

Epic loops. ops epic run keeps agents working through an epic's sub-issues, tick after tick, until everything is closed, a time or run budget is used up, or it needs me.

One Task, Start To Finish

  1. I drop a task in (from the UI, the terminal, or by telling the orchestrator). No owner, so the orchestrator tick tags it for triage.
  2. The orchestrator routes it to a role, say grokbot:engineer, with a project and priority.
  3. The engineer's next run does ops next, then ops claim. The claim is race safe, two runs can't take the same task.
  4. It reads the issue, the project's PROJECT.md and the knowledge base, posts a one line plan and gets to work.
  5. Stuck on a design choice? It asks in #help, or opens a vote if it's a real trade off, and keeps working on something else.
  6. The code goes in a PR in the code repo that says which hub issue it closes. Then ops handoff to grokbot:release.
  7. Release reviews the diff and merges it. An agent never merges its own PR.
  8. The issue closes, the board updates, and it shows up in tomorrow's digest.

If any step needs me, it's in Needs you. Otherwise I don't touch it.

That's not theory. The 0.2.1 release of Agent OPS went through this exact loop: 14 PRs reviewed and merged by the release role, 318 of 318 tests passing. The review caught a real bug in the leak checker and an example Claude role that was allowed to merge PRs. That permission is gone now. So... agents are building the project management system while using it live to improve itself :]

What Agents Can't Do

The hub never grants permission for anything outside it. A task that says "post this" or "email them" still needs my explicit OK for that exact action. Agents draft, I send. Making repos public, rewriting git history, installing things on my machines, posting from my accounts, all of that stays with me. ops.sh also refuses to post anything that looks like a token or a private key, so nobody leaks a key into an issue comment by accident.

I spent years on the other side of this, running thousands of accounts with zero oversight. Giving agents freedom without a log is how you end up with a very creative mess. Ask me how I know...

What's Next?

All of these are filed as issues on the hub right now:

  • A real daily standup: every role posts what it did, what's next and what blocks it in #standup, and the orchestrator posts one roundup (#131)
  • Usage tracking per agent, provider and project, so I can see where my weekly limits went before they're gone (#132). Claude and Codex report tokens per run, anything that can't be measured shows as unknown instead of a guess
  • A weekly progress summary per project, linked from the digest (#133)
  • An optional next week planner that sets project priorities and shows which recurring jobs already take which time slots (#134, #135)
  • A progress page per project in the UI (#136)
  • An animated activity timeline that shows who handed what to whom, the kind of thing that looks good in a screen recording (#119)
  • Hosting the UI somewhere other than localhost. [CHECK: Cloudflare Pages is the option on the table (#55), not set up yet]

Right now 17 agents across four providers share one queue, and the newest issue on the hub is #137. It's a bug where Windows asks me which app should open the ops command. I hit it the first time I tried to open their UI, and the orchestrator had it filed for the engineer a few minutes later.

That's a note left for me in the draft of this post by one of the agents:

[CHECK: if #137 is fixed before you publish, swap this ending]

I know it's my own creation, but... isn't that freaking cool? :]