feat(mesh): upgrade to v0.74 and make shared agents reply reliably - #3467
feat(mesh): upgrade to v0.74 and make shared agents reply reliably#3467micspiral wants to merge 8 commits into
Conversation
f79a72c to
59eb49c
Compare
Pin mesh-llm to the v0.74.0 tag instead of a commit rev, and carry the desktop-side changes that upgrade needs: catalog/coordinator/recovery updates, the relay-mesh agent preset, and restart behaviour that survives community boundary changes and roster churn. Also promote reasoning to text in the OpenAI-compat parser when `content` is empty and there are no tool calls. Local models served through mesh sometimes return the entire answer as reasoning; with no tool calls there is nothing else to surface, so reporting an empty turn loses the reply. Signed-off-by: Michael Neale <michael.neale@gmail.com>
Publishing a reply required composing a `buzz messages send ...` string inside a `shell` call. That indirection is reliable for frontier models and unreliable for the small local models shared compute exists to serve: against the real ~3.2k-token Buzz system prompt and the real dev-mcp toolset, Gemma 4 E4B delivered a reply in only 2 of 8 samples, answering in prose the rest of the time — and sometimes emitting the literal command text as assistant content. Assistant text is never published, so those replies were silently dropped while the turn reported success. Exposing the publish action as its own typed tool removes the indirection: the model selects a tool by name and fills one string field. Measured on the same prompt and toolset, delivery went 2/8 -> 8/8 on gemma-4-E4B and 6/6 on gemma-4-26B-A4B, and the real ACP path (buzz-acp + buzz-agent + live relay) replied correctly 3 for 3. The tool duplicates no process machinery — it builds an argv, quotes it, and delegates to shell::run, inheriting its timeout, output capping, cancellation and process-group kill. Channel ids are normalized (the `[Context]` block renders `general (#uuid)`, so models pass `#uuid`) and validated to bare ids, and content is single-quoted, so model output cannot reach the shell as metacharacters. Signed-off-by: Michael Neale <michael.neale@gmail.com>
P5 could never pass: install_fake_shell returned the bin directory, but resolve_bash requires BUZZ_SHELL to name a file, so it silently fell back to real bash and the marker was never written. The fake shell also matched `-lc` while shell_flag() emits `-c` on Unix. Return the executable, accept both flags, and append rather than overwrite so a probing model does not erase the recorded send. P5 stops at the shell boundary, so it cannot show a reply ever lands. Add P6: the agent runs the genuine `buzz messages send` (dev-mcp's multicall shim, real buzz-cli) against a running relay, and the harness then queries the relay itself — the agent's prose is not evidence. Infra-gated on BUZZ_E2E_RELAY_URL / _PRIVATE_KEY / _CHANNEL; skipped when unset so P1-P5 still run without Postgres and Redis. Also drive the agent with the real base_prompt.md instead of a terse hand-written string. A short prompt is not a proxy for production: tool adherence in small models depends on it, so testing with 56 characters where Buzz injects ~3.2k tokens measures the harness, not Buzz. Signed-off-by: Michael Neale <michael.neale@gmail.com>
The relay-mesh preset defaulted BUZZ_AGENT_THINKING_EFFORT to "none" to spend the output budget on an answer instead of hidden reasoning. On local models that also switches tool calling off: llama.cpp's Gemma grammar is lazy under `tool_choice: auto`, and `reasoning_effort: "none"` keeps the model on its prose completion path so it never emits the tool-call token. The agent then answers in plain content, which is never published, and the turn reports success with nothing in the channel. Isolated by replaying a captured production request against a local mesh node and varying one field: with the real prompt and toolset, gemma-4-E4B emitted a tool call 0/8 times at "none", 3/4 at "minimal", and 4/4 at "low" (4/4 with the field omitted entirely). Default to "low", which buys enough deliberation to select a tool without spending the budget on hidden reasoning. Still a default, so explicit agent/persona/global values are preserved. The mesh e2e harness claims to supply exactly what apply_relay_mesh_env() does, so update it in lockstep — pinning "none" there would test a config the product no longer ships and hide the regression P5/P6 exist to catch. Signed-off-by: Michael Neale <michael.neale@gmail.com>
0136bef to
0ad81c3
Compare
Shared compute pinned BUZZ_AGENT_THINKING_EFFORT. That is not a sampling hint: mesh translates `reasoning_effort` into the chat template's `enable_thinking` flag, so any value Buzz sends overrides the default the model authors shipped — and the right value is model-specific. Measured against a request captured from the real agent (full ~43k-token system prompt, real dev-mcp toolset, tool_choice=auto): effort gemma-4-E4B Qwen3-8B Qwen3-8B output tokens none 0/8 8/8 ~121 low 4/4 7/8 ~470 unset 6/6 8/8 ~500 `none` (the previous default, chosen when shared compute was Qwen3-only) stops gemma tool-calling outright, so its replies were never published. Enabling thinking instead makes Qwen3 spend ~4x the output budget, which risks the 4096 cap this preset sets. Omitting the field leaves `enable_thinking` as None, so mesh passes no override and each model uses its own template default: 3/3 delivery on gemma-4-E4B, gemma-4-26B-A4B, gemma-4-31B and Qwen3-8B, and 3/3 replies through the real buzz-acp + buzz-agent path against a live relay. Thinking levels are unaffected: an explicit agent/persona/global value is still preserved and applied. Signed-off-by: Michael Neale <michael.neale@gmail.com>
buzz-agent's contract is that output is tool calls: streamed assistant text
is observability only and never published. Capable models honour that and
call `send_message`. Small local models served over shared compute do not
always — on multi-step turns they run their tools, then write the answer as
prose and end the turn, so the turn reports success with nothing in the
channel.
Retain the turn's streamed text in AcpClient (bounded at 8 KiB) alongside a
flag for whether a publish tool call was seen, and on EndTurn publish that
text as a threaded channel reply when the agent published nothing itself.
Gated on EndTurn only: MaxTokens / MaxTurnRequests mean the turn was
truncated, so the text is not a deliberate answer. Bare acknowledgements
("OK", "Done") are filtered, and taking the buffer clears it so the same
text can never post twice.
Off by default via --deliver-plain-replies / BUZZ_ACP_DELIVER_PLAIN_REPLIES:
for cloud models, which reliably publish their own replies, posting streamed
text would double-post. The desktop shared-compute preset opts in, so this
stays scoped to the case that needs it.
Verified end to end against a live relay with stub ACP agents that pin each
branch deterministically: a prose-only agent gets its answer delivered, and
an agent that calls send_message while also streaming prose posts exactly
once with the fallback silent.
Signed-off-by: Michael Neale <michael.neale@gmail.com>
cargo fmt --all skips desktop/src-tauri (separate workspace), so this line only failed under just desktop-tauri-fmt-check in CI's Rust Lint job. Signed-off-by: Michael Neale <michael.neale@gmail.com>
|
ok - yes this is working quite well now - even for a small model. The other fixes for sharing seem ok too. This did require a reply tool added, and a mesh scoped change to scoop up missed replies so it returns to buz, but that seems to work well. Shouldn't impact other models outside of mesh. It also needed some tweaks to ensure thinking modes are used appropriately (very sensitive to those). |
The typed `send_message` reply tool was registered unconditionally, so every ACP agent's toolset grew from seven tools to eight. That changes the prompt each agent sees, and the delivery benefit was only ever measured on a small local model served over shared compute (3/8 -> 8/8 on gemma-4-E4B). Cloud models already publish their own replies by composing a `buzz messages send` shell command, so they were absorbing a behavioural change for no gain. Gate the tool on BUZZ_ACP_SEND_MESSAGE_TOOL, off by default. buzz-acp forwards it to the MCP server as BUZZ_MCP_SEND_MESSAGE_TOOL — via the ServerSpec env, since spawn_one clears the environment and only passes a whitelist — and dev-mcp drops the route when unset. Agents without it publish replies through the shell tool exactly as they did before the tool existed. This gets its own flag rather than reusing --deliver-plain-replies: one controls which tools exist, the other whether the harness publishes on the agent's behalf, and conflating them would make both harder to reason about. The desktop shared-compute preset sets both. Verified over a real MCP handshake on two machines: tools/list returns seven tools with the variable unset and eight with it set, on both the build host and a second Mac running the bundled app. Signed-off-by: Michael Neale <michael.neale@gmail.com>
|
Split requested by Tyler: the mesh-scoped commits from this PR (mesh v0.74 upgrade + shared-compute hardening + thinking-effort defaults) are now separately mergeable as #3741, with authorship preserved. The agent/ACP reply-behavior work (send_message tool, plain-reply delivery, e2e P5/P6, and the llm.rs reasoning→text parser change from 2cd640b) remains here as part 2 — details in #3741's description. |
…(split 1/2 of #3467) (#3741) ## Summary This is **part 1 of 2** split out from #3467 (per Tyler's request), carrying only the mesh-scoped changes. The agent/ACP response-behavior changes and the new `send_message` tool stay in #3467 as part 2. All commits are @michaelneale's work, cherry-picked with authorship preserved. - Upgrade embedded Mesh to v0.74.0 (tag-pinned instead of commit rev) and use canonical Gemma model IDs. - Keep shared compute serving through member joins, roster changes, app recovery, and community switching. - Wait for actual model readiness and avoid resuming incomplete downloads after quit. - Leave `BUZZ_AGENT_THINKING_EFFORT` unset by default so each model's chat template picks its own thinking default (`none` suppressed Gemma tool-calling entirely; pinning `low` made Qwen3 burn ~4x output budget). Explicit agent/persona/global values still win. ## Relationship to #3467 Contains the mesh commits from #3467 (`2cd640b23`, `0ad81c341`, `ad13ed841`) rebased onto current main, with one deliberate exclusion: the `crates/buzz-agent/src/llm.rs` reasoning→text parser change from `2cd640b23` is **not** here. That change unconditionally affects every OpenAI-compat/Responses provider, so it belongs with the reply-behavior work in part 2, where it can be reviewed as what it is. Not included (remaining in #3467 / part 2): - typed `send_message` tool in dev-mcp + `BUZZ_ACP_SEND_MESSAGE_TOOL` gating - plain-reply delivery fallback in buzz-acp (`BUZZ_ACP_DELIVER_PLAIN_REPLIES`) - the mesh_agent_e2e P5/P6 rewrite (exists to prove the reply path) - the two `env.insert` preset opt-ins in `relay_mesh.rs` for the flags above - the llm.rs parser change This PR is independently mergeable; part 2's flags are all off by default so it can land before or after. ## Testing - `cargo test -p buzz-relay --locked` — 780 passed (one telemetry test is order-sensitive under parallel default settings; passes in the pre-push suite and standalone, unrelated to this diff — files untouched here). - `just desktop-tauri-test` (default features) — 1877 passed. - `cargo test --locked --features mesh-llm` in `desktop/src-tauri` — 1961 passed, including the new relay-mesh preset and coordinator/recovery tests. - Both `Cargo.lock`s resolve with `--locked` against the v0.74.0 tag. - Full pre-push hook suite green (rust-tests, desktop-check/test, tauri checks). Live validation of the mesh v0.74 upgrade itself is documented on #3467 (two-Mac cross-version test). --------- Signed-off-by: Michael Neale <michael.neale@gmail.com> Signed-off-by: Tyler Longwell <tlongwell@block.xyz> Co-authored-by: Michael Neale <michael.neale@gmail.com> Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
|
Closing per Tyler — this PR has been split, and the mesh half now lives in #3741. What #3741 carries (rebased onto current main, Mic kept as author on all commits): the mesh v0.74 upgrade + shared-compute hardening ( What still needs a new PR (part 2) — the agent/ACP reply-behavior work from this branch:
None of that work is lost — it's all on Thanks Mic — the commit hygiene on this branch is what made the split clean. |
Pull request was closed
…chive * origin/main: (22 commits) feat(catalog): resolve publisher display name in catalog detail pane (#3640) feat(mesh): upgrade embedded mesh to v0.74 and harden shared compute (split 1/2 of #3467) (#3741) docs(nips): specify kind:30621 multi-repo projects (NIP-MP) (#3163) Refine agent sharing dialog (#3699) desktop: enable getUserMedia in the Linux WebKitGTK webview (#3607) fix: align responsive agent views (#3688) Add macOS agent menu-bar menu (#3565) Fix pending message feedback (#3543) fix(desktop): remove remaining Projects panel fills (#3742) feat(mobile): desktop-parity emoji and thread experience (#3485) desktop: restore direct community member adds (#3634) fix(desktop): explain open agent access (#2561) fix(cli): resolve agents from owner records (#3178) fix(desktop): remove Projects overview card fills (#3416) feat(replica): portable heartbeat-token fence with snapshot-local reader routing (#3268) fix(git): channel binding tooling + author remediation for unbound repos (#3626) feat: configure S3 URL addressing style (#3400) feat: add first-class OpenRouter provider support (#1975) feat(agent,acp): wire provider total_tokens through NIP-AM publish chain (#3593) chore(release): release Buzz Desktop version 0.5.2 (#3624) ... Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
…g-pipeline * origin/main: feat(catalog): resolve publisher display name in catalog detail pane (#3640) feat(mesh): upgrade embedded mesh to v0.74 and harden shared compute (split 1/2 of #3467) (#3741) docs(nips): specify kind:30621 multi-repo projects (NIP-MP) (#3163) Signed-off-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
…-style * origin/main: fix(acp): preserve truncated thread context (block#3340) feat(catalog): resolve publisher display name in catalog detail pane (block#3640) feat(mesh): upgrade embedded mesh to v0.74 and harden shared compute (split 1/2 of block#3467) (block#3741) docs(nips): specify kind:30621 multi-repo projects (NIP-MP) (block#3163) Refine agent sharing dialog (block#3699) desktop: enable getUserMedia in the Linux WebKitGTK webview (block#3607) fix: align responsive agent views (block#3688) Add macOS agent menu-bar menu (block#3565) Fix pending message feedback (block#3543) fix(desktop): remove remaining Projects panel fills (block#3742) feat(mobile): desktop-parity emoji and thread experience (block#3485) desktop: restore direct community member adds (block#3634) fix(desktop): explain open agent access (block#2561) fix(cli): resolve agents from owner records (block#3178) fix(desktop): remove Projects overview card fills (block#3416) feat(replica): portable heartbeat-token fence with snapshot-local reader routing (block#3268) Signed-off-by: Joah Gerstenberg <joah@squareup.com>

Summary
Live validation
Tested across two Macs: a production Buzz 0.73.1 client joined and messaged an agent while this development build hosted Mesh v0.74. The host stayed in serving mode, Gemma completed both turns, and the agent log confirmed both replies were published back to the channel.
Also tested a build https://github.com/michaelneale/buzz-testing/releases/tag/v0.5.1-test1 across 2 unrelated machines and it sustained a connection, shared compute reliably.
Testing
Focused ACP, desktop Mesh, and harness checks pass locally. Full CI is running on this PR.