PageSourceSearch

https://www.devtoolsdaily.com/_next/static/chunks/pages/blog/a-wee…with-buzz-coding-agents-cf778bbaf4273a95.js

js devtoolsdaily.com collected 2026-10-03 23:49:11 UTC 18,770 bytes, 1 lines download raw bytes

1(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[51601],{93859:function(e,n,t){(window.__NEXT_P=window.__NEXT_P||[]).push(["/blog/a-week-with-buzz-coding-agents",function(){return t(82782)}])},82782:function(e,n,t){"use strict";t.r(n),t.d(n,{__N_SSG:function(){return r},default:function(){return h},metadata:function(){return l}});var s=t(85893),i=t(91644),a=t(47185),o=t(86983),r=!0;let l={slug:"a-week-with-buzz-coding-agents",date:"2026-07-26",ogImage:"/og-images/a-week-with-buzz-coding-agents.png",metaTitle:"A Week With Buzz: Coding Agents in a Group Chat",description:"Self-hosting Block's Buzz on a VPS with a persistent Codex coding agent: the Docker Compose setup, the pairing-relay and Tauri CORS traps, invisible OOM kills, resource sizing, GitHub branch protection, and what Buzz still needs.",keywords:"buzz block, coding agents, acp agent client protocol, self-hosted relay, codex cli, copilot cli, vps coding agent, docker compose, caddy websocket, agent workspace, devtools daily",tags:["infrastructure","devops","ai agents","vps"]},d=function(e){let{children:n}=e;return(0,s.jsx)(a.Z,{title:l.metaTitle,date:l.date,children:n})};function c(e){let n={a:"a",code:"code",h2:"h2",h3:"h3",hr:"hr",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,i.a)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(n.h2,{children:"A week with Buzz: coding agents in a group chat"}),"\n",(0,s.jsx)(n.h2,{children:"Introduction"}),"\n",(0,s.jsxs)(n.p,{children:["Last week Block released ",(0,s.jsx)(n.a,{href:"https://github.com/block/buzz",children:"Buzz"}),", an open source workspace where humans and AI agents talk in shared rooms."]}),"\n",(0,s.jsxs)(n.p,{children:['The simplest way to describe it is "Slack for humans and coding agents". Under the hood it\'s a self-hosted relay with agents connected over the Agent Client Protocol (ACP), but that Slack picture is the right mental model. Their ',(0,s.jsx)(n.a,{href:"https://github.com/block/buzz/blob/main/ARCHITECTURE.md",children:"architecture document"})," has the details."]}),"\n",(0,s.jsx)(n.p,{children:"I tried it for a few reasons:"}),"\n",(0,s.jsxs)(n.ol,{children:["\n",(0,s.jsx)(n.li,{children:"My wife and I work on side projects together, and it's scattered across iMessage, WhatsApp, and GitHub issues. hard to keep track. I wanted something closer to a small private Slack where we could keep the projects and their context in one place."}),"\n",(0,s.jsx)(n.li,{children:"I wanted to to assign tasks to coding agents. There are differently skilled coding agents we have - e.g I use github copilot and codex, but my wife uses claude. and she is doing design."}),"\n",(0,s.jsxs)(n.li,{children:["I recently got a VPS server, so I wanted to have a good way to communicate with coding agents running inside it (",(0,s.jsx)(n.a,{href:"/blog/moving-from-azure-to-hetzner-vps/",children:"moving DevTools Daily off Azure onto a VPS"}),")"]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"It mostly worked. But I also ran into a bunch of rough edges around visibility, tooling, the mobile app, and docs."}),"\n",(0,s.jsx)(n.h2,{children:"What Buzz is"}),"\n",(0,s.jsx)(n.p,{children:"Buzz feels a lot like Slack. In a channel my wife and I can discuss a feature, share mockups, and argue about the approach. At some point we tag an agent, either to get its opinion or to actually implement something. We can also ask one agent to review another agent's work, all in the same thread."}),"\n",(0,s.jsx)(n.p,{children:"Here's a real example. I sent a screenshot and asked Codex about disabling the iOS keyboard hints in one of our apps. It explained exactly what to change and offered to open a PR:"}),"\n",(0,s.jsx)("img",{src:"/blog-img/a-week-with-buzz-coding-agents/agent-answers-in-thread.png",alt:"Codex answering a question in a Buzz thread about disabling the iOS predictive-text bar, with the original screenshot attached, and offering to open a pull request",style:{width:"70%",height:"auto"}}),"\n",(0,s.jsx)(n.h2,{children:"The setup I wanted"}),"\n",(0,s.jsx)(n.p,{children:"I already had a small VPS running a few other things. I didn't want Buzz checked into one of my app repos, and I didn't want it to eat the whole machine."}),"\n",(0,s.jsx)(n.p,{children:"Not everything can run on the VPS, though. iOS and Android builds need a Mac, so I also run a Codex agent on my Mac. It joins the same rooms and picks up tasks like the VPS agents, but it can also build the mobile apps and push them to TestFlight."}),"\n",(0,s.jsx)(n.p,{children:"Each agent runs in its own container with its own ACP bridge, rather than one shared bridge for all of them. That keeps their identities, credentials, and toolchains separate. Here's the shape of it:"}),"\n",(0,s.jsx)(o.Z,{markup:'\nflowchart TB\n  subgraph mine["My devices"]\n    mac["Mac \xb7 Buzz app<br/>
1+ Codex agent<br/>(builds iOS / Android)"]\n    phone["iPhone \xb7 Buzz app"]\n    wife["Wife\'s Mac \xb7 Claude Code agent"]\n  end\n  caddy["Caddy \xb7 HTTPS \xb7 wss://buzz.example.com"]\n  subgraph relay["Buzz relay stack (localhost only)"]\n    br["Buzz relay"]\n    pr["Pairing relay"]\n    pg[("Postgres")]\n    rd[("Redis")]\n    mn[("MinIO")]\n    br --- pg\n    br --- rd\n    br --- mn\n  end\n  subgraph vpsagents["VPS coding agents"]\n    subgraph cx["Codex container"]\n      cb["ACP bridge"] --- codex["Codex CLI"]\n    end\n    subgraph cp["Copilot container"]\n      pb["ACP bridge"] --- copilot["Copilot CLI"]\n    end\n    ws["Persistent workspace"]\n    codex --- ws\n    copilot --- ws\n  end\n  mine -->|HTTPS / wss| caddy\n  vpsagents -->|wss| caddy\n  caddy --> br\n  caddy --> pr\n  ws --> gh["GitHub"]\n'}),"\n",(0,s.jsx)(n.p,{children:"Caddy is the only public entry point. Buzz itself only listens on localhost, so its ports aren't exposed to the internet. Caddy handles the TLS certificate and proxies the WebSocket traffic to the relay."}),"\n",(0,s.jsx)(n.h2,{children:"What works"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"One place for all communication."})," Me, my wife, Codex, Claude, and Copilot in a single channel instead of five disconnected apps. Agents get the recent context, so I don't repeat myself when another one joins."]}),"\n",(0,s.jsx)(n.li,{children:(0,s.jsx)(n.strong,{children:"Different channels for different projects. Threads for tasks"})}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Agents do real work."})," Clone a repo, edit files, install deps, run tests, build the app, launch Chromium, take screenshots, push a branch, open a PR, check CI, review another agent's diff. Interface supports images/files/links etc."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"I can drive it from my phone."})," Post a task, answer a question, or ask for a review while away from the computer. The work keeps running on the VPS, so closing my laptop doesn't stop it."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Agent interaction feels natural."})," Codex implements, Copilot reviews the diff, Claude pokes at the design, and I decide what matters, all in the same thread."]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"Here I'm asking both agents, from my phone, to review each other's open PRs after a deploy:"}),"\n",(0,s.jsx)("img",{src:"/blog-img/a-week-with-buzz-coding-agents/agents-review-prs-mobile.png",alt:"A Buzz thread on a phone showing a Cloudflare deploy URL, a passing test summary, and a message asking Codex and Copilot to review each other's non-merged PRs, with both agents shown typing",style:{width:"50%",height:"auto"}}),"\n",(0,s.jsx)(n.h2,{children:"What's missing"}),"\n",(0,s.jsxs)(n.p,{children:["Almost all my complaints are operational, and they come down to one thing: ",(0,s.jsx)(n.strong,{children:"trust and visibility."})," Buzz tells me an agent got a message. It doesn't tell me what happens next."]}),"\n",(0,s.jsx)(n.p,{children:"The usual experience:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"hljs language-text",children:"Me: Please implement this issue and open a PR.\nAgent: I'll inspect the repo and take care of it.\n[Silence for 15 minutes]\nAgent: Here is the pull request.\n"})}),"\n",(0,s.jsx)(n.p,{children:'When it works, that silence feels like handing work to a person. When it doesn\'t, the same silence could be a dead compiler, an OOM kill, an expired credential, a stuck model call, or no work at all. A couple of times the agent even kept posting "still working" while the logs showed it had never tried to commit or push.'}),"\n",(0,s.jsx)(n.p,{children:"So here's what I think Buzz needs most, roughly in order:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"A per-agent state/chain of thought."})," I need to know if agent is alive or if was killed. Basically I need to see what i normally see locally in claude code or codex."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"Health and resource info."})," CPU, memory, memory limit, OOM events, disk, current turn duration, repo and branch, last tool call, last error, basically control plane for a setup."]}),"\n",(0,s.jsxs)(n.li,{children:[(0,s.jsx)(n.strong,{children:"More user friendly"})," ways to add agents/peoples. Now everything is done through first compiling some rust code then setting some env vars."]}),"\n"]}),"\n",(0,s.jsx)(n.h2,{children:"How I set it up on VPS"}),"\n",(0,s.jsx)(n.p,{children:"Docker Compose: the relay, Postgres for events, Redis for presence, MinIO for attachments, and the pairing relay. I first tried to fit that non-agent stack 
1into about 1 GB of memory:"}),"\n",(0,s.jsxs)(n.table,{children:[(0,s.jsx)(n.thead,{children:(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.th,{children:"Service"}),(0,s.jsx)(n.th,{children:"Memory"}),(0,s.jsx)(n.th,{children:"CPU"})]})}),(0,s.jsxs)(n.tbody,{children:[(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"Buzz relay"}),(0,s.jsx)(n.td,{children:"384 MB"}),(0,s.jsx)(n.td,{children:"0.40 core"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"PostgreSQL"}),(0,s.jsx)(n.td,{children:"320 MB"}),(0,s.jsx)(n.td,{children:"0.25 core"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"Redis"}),(0,s.jsx)(n.td,{children:"64 MB"}),(0,s.jsx)(n.td,{children:"0.10 core"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"MinIO"}),(0,s.jsx)(n.td,{children:"192 MB"}),(0,s.jsx)(n.td,{children:"0.15 core"})]}),(0,s.jsxs)(n.tr,{children:[(0,s.jsx)(n.td,{children:"Pairing relay"}),(0,s.jsx)(n.td,{children:"32 MB"}),(0,s.jsx)(n.td,{children:"0.05 core"})]})]})]}),"\n",(0,s.jsx)(n.p,{children:"These limits are aggressive, but they've been fine for a tiny personal workspace. They are not sizing advice for a team. Postgres and MinIO use persistent Docker volumes, so recreating or upgrading the relay doesn't wipe the history. The coding agents need a lot more, which I'll get to below."}),"\n",(0,s.jsx)(n.h3,{children:"The pairing relay"}),"\n",(0,s.jsx)(n.p,{children:"This one wasn't obvious during setup."}),"\n",(0,s.jsx)(n.p,{children:"The mobile app shows a QR code to pair a device. At first the code appeared for a second and then closed. The app said it couldn't reach the pairing relay, and the desktop logs showed a WebSocket 404."}),"\n",(0,s.jsx)(n.p,{children:"It turns out the main relay doesn't provide the pairing endpoint the clients expect. I had to run the standalone pairing-relay process and expose it through Caddy on a separate path:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"hljs language-caddy",children:"buzz.example.com {\n    handle /pair* {\n        reverse_proxy 127.0.0.1:18500\n    }\n    handle {\n        reverse_proxy 127.0.0.1:18300\n    }\n}\n"})}),"\n",(0,s.jsxs)(n.p,{children:["Once ",(0,s.jsx)(n.code,{children:"/pair"})," returned a successful WebSocket upgrade, mobile pairing started working. The pairing relay is basically stateless and uses only a few megabytes."]}),"\n",(0,s.jsx)(n.h2,{children:"Running Codex as a persistent VPS agent"}),"\n",(0,s.jsx)(n.p,{children:"I built a Docker image with Node.js, Git, GitHub CLI, Codex CLI, the Codex ACP adapter, Buzz's ACP bridge, Chromium, and the Flutter prerequisites. On start it launches the bridge, which starts Codex and connects it to the relay."}),"\n",(0,s.jsx)(n.p,{children:"A disposable container is fine, but a disposable dev environment would be wasteful. So the workspace, toolchains, caches, and the agent's login state all live on persistent volumes:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"hljs language-text",children:"/workspace          cloned repositories\n/toolchains         Flutter etc (install here, not in the container FS)\n/pub-cache /gradle-cache /chromium-cache\n"})}),"\n",(0,s.jsx)(n.p,{children:"Chromium is in the image, so the agent can run a web app headless and take screenshots. One thing to keep in mind: this persistence is durable state, not just convenience. Replacing a compromised container does not clean a compromised workspace."}),"\n",(0,s.jsx)(n.h3,{children:"GitHub access"}),"\n",(0,s.jsx)(n.p,{children:"The container uses GitHub CLI with a fine-grained token, limited to selected repos and permissions."}),"\n",(0,s.jsx)(n.h2,{children:"OOM kills and abandoned tasks"}),"\n",(0,s.jsx)(n.p,{children:"I originally tried to cap the agent at about 1 core and 1 GB. That's fine for chatting, Git, and small edits, but not for building modern apps. A Next.js or Flutter build would blow past the limit and the kernel would start killing things."}),"\n",(0,s.jsxs)(n.p,{children:["Sometimes it killed a single child process (a build worker, ",(0,s.jsx)(n.code,{children:"tsc"}),', a compiler, Chromium) while the bridge and Codex kept running. Sometimes it took down enough that the container restarted. Either way the task just stopped, and from the room the agent still looked like it was thinking. Buzz never posted anything like "a build was killed for hitting the memory limit."']}),"\n",(0,s.jsx)(n.p,{children:"Two changes helped more than just adding memory:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"I told the agent, in its instructions, not to run heavy production builds unless I explicitly ask, and to prefer type-checks and targeted tests instead."}),"\n",(0,s.jsxs)(n.li,{children:["I had it reuse a shared ",(0,s.jsx)(n.code,{children:"node_modules"})," across git worktrees instead of installing dependencies fresh for every branch. That cut a lot of the disk and memory churn on its own."]}),"\n"]}),"\n",(0,s.jsx)(n.h2,{children:"Wrapping up"}),"\n",(0,s.jsx)(n.p,{children:"I like it so far. I'll keep using it and wait for the app to get better, which should make this setup even nicer. For quick tasks, though, I still prefer working hands-on with Copilot on my Mac."}),"\n",(0,s.jsxs)(n.p,{children:["If you want to see how the box itself came together, start with ",(0,s.jsx)(n.a,{href:"/blog/moving-from-azure-to-hetzner-vps/",children:"moving DevTools Daily from Azure to a VPS"}),"."]}),"\n",(0,s.jsx)(n.hr,{})]})}function h(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return(0,s.jsx)(d,{...e,children:(0,s.jsx)(c,{...e})})}},47185:function(e,n,t){"use strict";t.d(n,{Z:function(){return d}});var s=t(85893),i=t(43078),a=t(9008),o=t.n(a),r=t(11163);t(48097);let l="https://www.devtoolsdaily.com";function d(e){let{date:n,children:t,title:a,useContainer:d=!0,articleSchema:c}=e,h=(0,r.useRouter)(),p=((null==h?void 0:h.asPath)||"").split("?")[0].split("#")[0],u="".concat(l).concat(p),g=c?JSON.stringify({"@context":"https://schema.org","@type":c.type||"TechArticle",headline:c.title,...c.description?{description:c.description}:{},...c.keywords?{keywords:c.keywords}:{},...c.date?{datePublished:c.date,dateModified:c.date}:{},mainEntityOfPage:{"@type":"WebPage","@id":u},author:{"@type":"Organization",name:"DevTools Daily",url:l},publisher:{"@type":"Organization",name:"DevTools Daily",url:l}}).replace(/</g,"\\u003c"):null,m=p.startsWith("/blog/")&&"/blog/"!==p&&"/blog"!==p?JSON.stringify({"@context":"https://schema.org","@type":"BreadcrumbList",itemListElement:[{"@type":"ListItem",position:1,name:"DevTools Daily",item:"".concat(l,"/")},{"@type":"ListItem",position:2,name:"Blog",item:"".concat(l,"/blog/")},{"@type":"ListItem",position:3,name:a,item:u}]}).replace(/</g,"\\u003c"):null,x=d?(0,s.jsxs)(i.W,{className:"blog-article",children:[(0,s.jsx)("h1",{children:a}),(0,s.jsx)("div",{className:"text-sm italic date pb-1 pt-1",children:n}),t]}):(0,s.jsxs)("div",{className:"blog-article",children:[(0,s.jsx)("h1",{children:a}),t]});return(0,s.jsxs)(s.Fragment,{children:[(g||m)&&(0,s.jsxs)(o(),{children:[g&&(0,s.jsx)("script",{type:"application/ld+json",dangerouslySetInnerHTML:{__html:g}}),m&&(0,s.jsx)("script",{type:"application/ld+json",dangerouslySetInnerHTML:{__html:m}})]}),x]})}},86983:function(e,n,t){"use strict";t.d(n,{Z:function(){return r}});var s=t(85893),i=t(67294),a=t(98539),o=t(46455);function r(e){let{markup:n}=e,t=(0,o.l)("light",{getInitialValueInEffect:!0}),r=(0,i.useRef)(null),[l,d]=(0,i.useState)(void 0),c=(0,i.useId)().replace(/[^a-zA-Z0-9]/g,"");return((0,i.useEffect)(()=>{let e=!1;return a.N.initialize({startOnLoad:!1,theme:"dark"===t?"dark":"default",securityLevel:"loose"}),(async()=>{try{let{svg:t}=await a.N.render("sd-mermaid-".concat(c),n);if(e)return;r.current&&(r.current.innerHTML=t),d(void 0)}catch(n){e||d((null==n?void 0:n.message)||"Failed to render diagram")}})(),()=>{e=!0}},[n,t,c]),l)?(0,s.jsx)("pre",{className:"overflow-auto rounded-md border border-red-400 p-3 text-sm text-red-500",children:n}):(0,s.jsx)("div",{ref:r,className:"flex w-full justify-center [&_svg]:max-w-full"})}},91644:function(e,n,t){"use strict";function s(e){return{...e}}t.d(n,{a:function(){return s}})},48097:function(){},43078:function(e,n,t){"use strict";t.d(n,{W:function(){return u}});var s=t(85893);t(67294);var i=t(69429),a=t(13637),o=t(90987),r=t(46976),l=t(33583),d=t(9535),c={root:"m_7485cace"};let h={},p=(0,a.Z)((e,{size:n,fluid:t})=>({root:{"--container-size":t?void 0:(0,i.ap)(n,"container-size")}})),u=(0,d.d5)((e,n)=>{let t=(0,o.w)("Container",h,e),{classNames:i,className:a,style:d,styles:u,unstyled:g,vars:m,fluid:x,mod:f,...w}=t,j=(0,r.y)({name:"Container",classes:c,props:t,className:a,style:d,classNames:i,styles:u,unstyled:g,vars:m,varsResolver:p});return(0,s.jsx)(l.x,{ref:n,mod:[{fluid:x},f],...j("root"),...w})});u.classes=c,u.displayName="@mantine/core/Container"}},function(e){e.O(0,[70152,31748,51039,59231,32702,92888,49774,40179],function(){return e(e.s=93859)}),_N_E=e.O()}]);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.