Repository navigation
refactor(agents): separate the agent runner and its reads from the desktop app - #961
Conversation
d6928af to
7ce397b
Compare
wesbillman
left a comment
There was a problem hiding this comment.
Two changes requested: preserve cancellation across the new asynchronous channel lookup, and migrate the documented Codex live-check caller to the new reads interface. Details are inline.
Star Lord's automated source review via Wes's account. Head: 7ce397b7ba1c924ac544318335c366338d64ba57; base: 96f1c2155eb0406349cecba5575bb01be0942ce8. Source-only; no tests or app/native execution. CI snapshot: required checks passed; Windows native validation skipped. Live runtime behavior remains unverified.
wpfleger96
left a comment
There was a problem hiding this comment.
🤖 thanks for splitting this out, the runner and reads seams read cleanly.
one blocker: this doesn't type-check once it's merged with current main. #846 landed today and added new this.relay(...) callers: channelName() in claude-code/runtime.ts (used by transcript discovery in find()), and name() plus find() in codex/runtime.ts. this PR removes the relay field, so git merges cleanly but both JavaScript CI jobs fail pnpm typecheck with TS2339: Property 'relay' does not exist (run). typecheck fails before the vitest shards start, so the unit suites haven't run on the merge either. could you rebase onto main and move those three onto AgentReads.channelName() / AgentReads.name(), keeping how they resolve names today? once that's done it'd be worth checking that saved-transcript discovery and display still work for both runtimes.
nonblocking: the description says the Codex runtime tests change "only how they build their fake session". they also add the stop-during-channel-read regression test and tighten the profiles.ensure assertion to "foreground". that's not a behavior change, since foreground is already the default, but the sentence should match.
I diffed the code removed from service.ts against runner.ts, and the move is faithful: this.X becomes this.host.X, the origin check becomes the heard predicate, and the order of reconcile, notify and resume is unchanged. fromOwner is byte-identical, and workflowOf keeps the same authority and profile revalidation. The Codex cancellation and live.mjs fixes from the earlier review are in.
a4dd702 to
a839030
Compare
wpfleger96
left a comment
There was a problem hiding this comment.
🤖 rechecked at a839030 and the blocker's fixed. the #846 reads go through AgentReads now, members is optional so Codex name() resolves the same way main does, and profiles returns Promise<void>. the rebase over #949 keeps the wait for the first Claude/Codex download, it just moved into claudeRun/codexRun. keying the Codex memory read by connection in a839030 is a good catch too, the move had lost that.
nonblocking: Codex name()/find() don't have a test through the reads like the Claude one in 98d993b, fine as a follow-up. adding the fake-host runner test in #967 works for me.
🤖 holding this until a second review finishes, will re-approve once it's clear
wpfleger96
left a comment
There was a problem hiding this comment.
🤖 second review came back clear too, so approving again at a839030. same notes as my earlier approval: the #846 reads go through AgentReads, the #949 download wait carries over into claudeRun/codexRun, and the connection-keyed Codex memory read looks right.
nonblocking: Codex name()/find() still don't have a test through the reads like the Claude one in 98d993b, fine as a follow-up.
ba2a08e to
dd1c24f
Compare
The per-agent queue, rate limit, mention and watch matching, due timers, run deadline and host claim move to AgentRunners in runner.ts. Everything the runner needs of the device, community and storage comes through a RunnerHost, which AgentsService implements. No behavior changes: the existing service tests pass unchanged. This lets a standalone host run agents with the same code as the app. Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> Signed-off-by: Logan Johnson <loganj@squareup.com>
The Claude runtime read the channel, conversation, names and memory through the owner's app connection. It now asks an AgentReads port for them, which the app fills from that same connection (ownerReads), so its behavior is unchanged. A standalone host can give it reads made as the agent instead. The run part of the type (models, run and the hand-over deadline) moves to run.ts, which imports no views, and fromOwner moves next to the attestation check it uses, so the run code no longer imports the agents service. Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> Signed-off-by: Logan Johnson <loganj@squareup.com>
The Codex runtime read the channel, conversation and memory through the owner's app connection, like Claude did. It now uses the same AgentReads port, filled by ownerReads in the app, so a standalone host can run it with reads made as the agent. The port's memory read takes a signal; each runtime keeps its own bound (Claude five seconds, Codex its turn). The run part of the type moves to run.ts, as Claude's did. Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> Signed-off-by: Logan Johnson <loganj@squareup.com>
A stop while a turn read its channel deleted the agent's entry, but the turn then created a fresh one and could reopen Codex. Recheck the delivery's signal after the read. The live check still passed a bare relay as its reads, so the first run failed on the missing workflow authority. Give it the owner's reads, and load ?raw imports and workingUntil as the app does. Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> Signed-off-by: Logan Johnson <loganj@squareup.com>
Nothing reads what it resolves with, so the type says so. Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> Signed-off-by: Logan Johnson <loganj@squareup.com>
Pins that the session list gets channel names from AgentReads, as it did from the owner's session before. Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> Signed-off-by: Logan Johnson <loganj@squareup.com>
The reads carry their connection, and the shared memory reads keep one read per connection and agent. Codex also checks the connection again once a read settles. This test pins that last check: a read through a connection that was replaced while it ran must not reach the turn, such as when a different owner signs in to the same community. Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz> Signed-off-by: Logan Johnson <loganj@squareup.com>
dd1c24f to
1b5e8d7
Compare
* commit '506ac5d4d': Fix JPEG uploads and attachment control overlap (#1068) feat(channels): read, write and look up channel labels (#964) refactor(agents): separate the agent runner and its reads from the desktop app (#961) fix(media): show desktop animated avatars and match dev broker media rules to native (#990) ci(browser): split journeys across nine shards per engine (#989) test(inbox): open the edit journey's conversation by name (#997) test(voice-notes): keep each attachment's own type in the upload fixture (#999) Stabilize main: consolidate WebKit flake and bottom-follow fixes (#1039) Push main-pane threads in and hold threads behind a loading mark (#1018) [buzz-app] Show full sent images and GIFs (#886) feat(agents): say the agents this copy runs are online (#862) [buzz-app] Keep the sidebar in place when switching to Personal space (#945) fix(builderlab): link the Buzz key before attesting remote agents (#978) feat(agents): show agents typing to everyone while they work on a mention (#998) Signed-off-by: John Tennant <jtennant@block.xyz>
* origin/main: fix(linux): use slight GTK hinting for clearer font spacing (#1047) Make Me conversations feel like project chats (#1005) fix(codex): deliver custom instructions without codex truncation (#1059) test(browser): fix the notification-reveal and nested-replies WebKit flakes (#1087) fix(messages): keep following after a notification reveals the latest row (#1085) Add native nsec backup HTTP client (#995) feat(buzz-mcp): add channel and member management tools (#1055) fix: show agent avatars in channel template editor (#1013) Fix JPEG uploads and attachment control overlap (#1068) feat(channels): read, write and look up channel labels (#964) refactor(agents): separate the agent runner and its reads from the desktop app (#961) fix(media): show desktop animated avatars and match dev broker media rules to native (#990) ci(browser): split journeys across nine shards per engine (#989) test(inbox): open the edit journey's conversation by name (#997) test(voice-notes): keep each attachment's own type in the upload fixture (#999) Stabilize main: consolidate WebKit flake and bottom-follow fixes (#1039) Signed-off-by: Sol <49aa1f65411fd096d2e2ec144f1e7aa36fdc76d1b907cfdf7be000c66f9d3b8e@buzz.block.builderlab.xyz>
🤖
Summary
Why
Buzz agents are AI assistants, such as Claude or Codex, that a person sets up in the Buzz desktop app. They answer when someone mentions them in a channel, and they can also watch for events and run on a timer. Today an agent runs only while its owner's desktop app is open, because the code that runs agents lives inside the app.
The goal of this stack is a small command-line program that runs an agent with the app closed. It must use the same code as the app, so the agent behaves the same wherever it runs. The app keeps working exactly as it does today.
Stack
Review in order. Each PR keeps the app working.
buzz-agent-host, the program that runs an agent with the app closed.Details
The runner (
refactor(agents): move the agent runner out of AgentsService)src/features/agents/runner.ts(AgentRunners). It owns the rules for running an agent:RunnerHost. That includes which agents are ready, their saved settings, team prompts, channel membership, the activity view, the typing indicator, and the native key store, which signs the agent's messages and makes its relay requests.AgentsServiceimplementsRunnerHostwith the same code it used before. It keeps connections, the agent list, hosted agents and settings edits.The reads (the
claude-codeandcodexcommits)src/features/agents/reads.ts: theAgentReadsinterface (channel facts, conversation reads, profiles, names, memories, and which community the agent is in), plusownerReads, the app's implementation.ownerReadsis the existing code, moved.ctx.agents.relay(pubkey)). That object exists only inside the app. The interface lists only the reads an agent needs.src/bundled/claude-code/run.tsandsrc/bundled/codex/run.ts: the parts of each agent type that run an agent (model list, run, hand-over deadline), without its settings screens. A program with no user interface can load them. The app registers them with their screens as before.fromOwner, the check that decides who can give an agent instructions, moves next to the signature check it uses. The agents service still exports it. It also accepts mentions from relay workflows (fix(agents): accept verified relay workflow mentions #970). That part needs the community's workflow key and signed profiles, so it now gets them through the same interface (workflowOf).ownerReads. The Codex tests also add one regression test: an agent stopped while its channel is read starts no work. One assertion now names the"foreground"profile priority. That is already the default, so behavior does not change.