docs: field writeup — running a local multi-model debate lab on a self-hosted relay - #5496
Open
artemtrofymenko wants to merge 15 commits into
Open
docs: field writeup — running a local multi-model debate lab on a self-hosted relay#5496artemtrofymenko wants to merge 15 commits into
artemtrofymenko wants to merge 15 commits into
Conversation
Runbook for standing up a multi-model debate on a self-hosted relay: several agents in one channel, one on Claude Code over OAuth, the rest on local models via Ollama. Alongside it, two documents that exist because the setup was not straightforward: - DEBATE_SETUP.md records the measurement behind every config value. Notably the served Ollama context window (8192) versus the one the models advertise (262144), and the output budget these reasoning models need before any visible text appears. - FINDINGS.md records product behaviour that cost time to diagnose. Two are candidate bug reports: a self-hosted relay configured with `localhost` in RELAY_URL silently breaks every agent, because the desktop normalizes loopback to 127.0.0.1 before handing the URL to the harness while the relay resolves the community from Host; and `claude auth status`, which the desktop uses as its auth probe, consults a different credential store than the ACP adapter and was observed disagreeing with runtime reality in both directions on the same machine in one day. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Artem Trofymenko <99894081+artemtrofymenko@users.noreply.github.com>
Setting BUZZ_AGENT_MAX_HISTORY_BYTES to 48000 looked reasonable next to a 32k-token window, but the crate validates it against MAX_PROMPT_BYTES and refuses to start below 1 MiB — all ten harness processes exit immediately with "all 10 agents failed to start". It is a byte cap on the transcript, not a competitor to the token window. Drop the variable from the recommended config and document the three values the crate validates at startup, including that the real default is 16 MiB rather than the 1 MiB the crate README states. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Artem Trofymenko <99894081+artemtrofymenko@users.noreply.github.com>
…odel A local model's fitness for this lab turns out to hinge on whether it holds the shape of the publish command, not on how well it argues. Those are different abilities and the second is invisible in general benchmarks: qwen3.5 wrote sound rebuttals and then passed the prose itself as the shell command's argument, so nothing ever reached the channel. Five runs per configuration, validating that the tool call really is a `buzz messages send --channel <uuid> --content "…"` invocation rather than merely containing those words: gemma4-debate 15/15 across three prompt variants qwen3.5-debate 4/5 short prompt, 2/5 once extra rules were added ornith:9b 3/4 lfm2.5:8b 0/4 Two lessons went into the guide. Extra rules in the system prompt hurt the weaker model rather than helping it, so roles moved out of the prompt and into the round message, leaving the prompt to answer only "how do I publish". And the Activity panel — not the harness log — is where a silent agent is diagnosed, because the log at info level never shows tool calls at all and so looks identical whether the call was malformed or never attempted. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Artem Trofymenko <99894081+artemtrofymenko@users.noreply.github.com>
A Workflow paces turns perfectly — delays honoured to the second, mentions resolved — and the agents still never answer, because the engine signs its messages with the relay key while every managed agent is stuck on an owner-only gate that admits only the owner and their own verified agents. The gate being stuck is block#2501, open since 0.4.23 and confirmed through 0.5.8, with its fix (block#2505) unmerged. Worth recording here because it derailed the diagnosis: managed-agents.json keeps two records per agent, the UI writes the choice onto the definition record, and the harness gates on the instance record. The raw file therefore shows both "allowlist" and "owner-only" at once, and reading the wrong one leads straight to a false conclusion — as it did for me before the upstream thread set it right. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Artem Trofymenko <99894081+artemtrofymenko@users.noreply.github.com>
Editing respond_to and respond_to_allowlist directly on the instance records, with the app closed, survives startup: the harness came up with respond_to=allowlist(1) and an agent answered a workflow-generated mention 52 seconds later, where every mention before the restart was dropped. Nobody in block#2501 has posted a working workaround, so record the fragility too: the app rewrites instance records during its own operations, which means editing the agent in the UI can silently restore owner-only. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Artem Trofymenko <99894081+artemtrofymenko@users.noreply.github.com>
Three sessions in, the interesting difference between the models is not what they know — all four understand the subject — but whether they can run an argument: spot a specific flaw, concede their own, and narrow a claim under criticism. Codex caught Claude proposing nftables owner-match for a macOS example; Claude took the correction and listed its own error among the session's unsupported claims. Neither local model did anything of the kind, and qwen3.5 twice answered a direct request to concede by asserting its objection had survived. Also records the structural mistake that nearly produced the wrong conclusion: the first run put the topic only in the opening step, so the local models went looking for it in a channel already full of previous sessions and picked up the wrong one. With the topic injected into every step they were on topic immediately — so the failure was the harness, not the model size. Ships the workflow that ran the clean session, plus the two rules it encodes: topic in every step, and str_starts_with as the trigger so a step may quote the topic without re-triggering the run. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Artem Trofymenko <99894081+artemtrofymenko@users.noreply.github.com>
The Workflows screen said "No workflows yet" while the workflow was triggering and posting. Upstream already has the cause — the HTTP bridge narrows a multi-value #h filter to the lexicographically smallest channel UUID (block#5053, block#4659, block#4804) — and our channel list matches it exactly: the debate channel holds the largest UUID of four, so the query resolved to a different channel entirely. Recorded because the cost is larger than a missing list. With no row in the UI there is no way to disable or delete, and three runs ended up overlapping, each posting round instructions into the same channel with no way to stop them from the app. The screen also reports the empty result as "nothing created" rather than as a query that returned nothing, which is what kept us looking in the wrong place. Includes the SQL workaround, since creation still works and only the listing is broken. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Artem Trofymenko <99894081+artemtrofymenko@users.noreply.github.com>
Gemma4 answered at 110s in one session against a 110s budget — it finished exactly as the engine fired the next mention. Its latency varies about twofold across runs (66s to 110s observed), so the budget after its steps goes to 150s. The cloud agents stay at 60s against measured 19-25s. Also records two engine limits found in the tracker rather than the hard way: a single delay cannot exceed 270 seconds, which the documented "5m" and "1h" examples both violate (block#3021), and the YAML `enabled` field is inert because the scheduler reads a separate database column (block#4639) — so disabling by editing YAML does nothing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Artem Trofymenko <99894081+artemtrofymenko@users.noreply.github.com>
artemtrofymenko
force-pushed
the
docs/local-debate-lab
branch
from
August 10, 2026 17:33
090a69c to
57276cb
Compare
block#2698 recommends a one-shot exemplar — a fabricated prior turn showing the correct tool call — as the most reliable way to get a local model to publish. Measured against a plain instruction on our two models it was worse: qwen3.5 dropped from 5/5 to 3/5 correct commands, and the failures show it transcribing the channel UUID out of the exemplar and corrupting it. The exemplar states the UUID twice instead of once, which helps a model that fails to choose the tool and hurts one that chooses correctly but copies badly. The competing-tool failure from the same issue did not reproduce: with a second plausible tool present in every run, both models picked the right one 20/20. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Artem Trofymenko <99894081+artemtrofymenko@users.noreply.github.com>
…e harness Two consecutive workflow sessions, same prompts and config, different topics: the cloud agents answered 7/7 both times, the local ones 5/5 and then 1/5. In the failing session qwen3.5 was not idle — the harness log shows two LLM calls seconds after the mention, then a clean end of turn with nothing published, which is block#2698. That bounds the reliability table recorded earlier. Those runs went straight to Ollama with one system prompt and an empty history; the harness adds buzz-agent's own prompt, the channel history and the full MCP tool set, and the same models degrade there — unpredictably between sessions. The isolated number is a ceiling on capability, not a measure of reliability, and the document now says so. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Artem Trofymenko <99894081+artemtrofymenko@users.noreply.github.com>
Finding 6 said owner-only admits sibling agents, sourced from a comment in access_policy.rs — an intent, not an observation. Upstream then reported the opposite, and the counter-example I reached for first was contaminated: the kickoff message had @-mentioned every participant, so nothing distinguished an agent waking an agent from a human waking both. Redone so only an agent-authored mention could have woken the target — the human mentioned Claude alone, "Codex" stayed plain text with no p tag — and Codex answered Claude 23 seconds later. Codex carries an allowlist of one entry that is not Claude, so `allowlist.contains(author)` was false and `is_owner_or_sibling` is the only branch that could have admitted it. Also records where to look when it fails: that function fails closed on a transient REST error and caches the negative for the process lifetime (block#5450), which reads as selective deafness rather than a fault. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Artem Trofymenko <99894081+artemtrofymenko@users.noreply.github.com>
Three files claimed Buzz desktop 0.5.5 while FINDINGS.md's own 0.5.8-only section said otherwise — the app updated at the start of the session and the header never caught up. A reader weighs this whole report by that number, so a stale one is not a typo. Also state plainly which versions were never tested: 0.5.11 and 0.5.14 are already being reported in the tracker, and nothing here was re-run on them. Signed-off-by: Artem Trofymenko <99894081+artemtrofymenko@users.noreply.github.com>
The desktop auto-updated 0.5.8 -> 0.5.14 mid-session, so every finding got a second look. None of them fell over: the loopback host check, the auth probe, the README's key claim, the model filter, the author gate and the liveness probe all behave exactly as recorded. Findings 8 and 10 are marked not re-tested rather than quietly assumed. Two things the update itself taught us. The managed-agents.json workaround survived it — the harnesses came back up with respond_to=allowlist(1), so the compiled owner-only clamp is not enabled in this build. And the harness log is appended to across runs, not recreated, so "read the first line" sent me to a week-old startup line and briefly convinced me the update had broken the gate. It is the last one that describes the running process. Signed-off-by: Artem Trofymenko <99894081+artemtrofymenko@users.noreply.github.com>
Finding 8 is now reproduced rather than recalled: a standalone buzz-acp pointed at an unmapped host logs the 404 as a terminal error and exits without a single retry attempt — while the same binary, seven minutes earlier, rode out a relay restart with "autonomous reconnect attempt 3/5 … succeeded". The retry machinery works; 404 just never reaches it. That makes the finding about error classification, which is a smaller and more fixable claim. Finding 10 gets its actual culprit: extract_channel_id_from_filter in the relay returns the first parseable #h value and scopes the SQL to it, so the post-filter can only narrow what that one channel returned. Two functions share the name and disagree — the one in api/bridge.rs returns None for multi-value filters, the deciding one in handlers/req.rs does not. Finding 1 needed a correction I earned the hard way. The relay is fail-closed only for hosts it does not know; for its own configured host it creates a fresh empty community instead. Breaking the host map to force a 404 produced a second community and a working connection into an empty workspace, which is a far more confusing failure than the 404 it replaced. Signed-off-by: Artem Trofymenko <99894081+artemtrofymenko@users.noreply.github.com>
Turned on statement logging, opened the Workflows screen, watched it say "No workflows yet", and read back the query the relay issued. The desktop asked for four channels; the SQL carries `channel_id = $2` with a single value next to the four-value IN list, and $2 is the smallest UUID — the one channel that has no workflow in it. The chain now runs from the click to the row filter with nothing inferred in between, which is the difference between confirming someone else's diagnosis and having one. Signed-off-by: Artem Trofymenko <99894081+artemtrofymenko@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this is
A field writeup from standing up Buzz as a local multi-model debate lab on Windows: a self-hosted relay in Docker, the packaged desktop, and four agents — Claude Code and Codex over OAuth, plus Qwen3.5 9B and Gemma4 12B locally through Ollama — arguing in one channel with turns driven by a Workflow.
Four documents under
docs/local-debate-lab/, plus the workflow definition that ran the sessions.The docs are written in Ukrainian. I am opening this as offered rather than assumed — if a non-English doc set is not something the project wants to carry, say so and I will close it, or translate it if that would make it useful.
Why it might be worth carrying
Most of what cost time here is already known to this tracker. What the writeup adds is the sequence: which symptoms lead to which cause, and what a self-hoster sees before they know the vocabulary to search for.
RELAY_URL=ws://localhost:3000silently breaks every managed agent. The relay resolves the community fromHostand fails closed; the desktop normalizes loopback to127.0.0.1before handing the URL to the harness. The relay is healthy,/_livenessreturns 200, the desktop works, the agent looks running — and the only evidence is a 404 in a harness log nothing points you to. Related: Managed agents bind to a different relay community than the owner on loopback, discover 0 channels, and stay idle #4147, Local dev: loopback hosts are seeded as separate communities, so desktop-managed agents silently discover 0 channels #3283, Managed agents sit idle in an empty community: launcher pins 127.0.0.1 while the app UI uses localhost (self-hosted dev) #3505./_livenessis not a readiness signal for agents. It answers 200 on hosts that map to no community. The WS upgrade (101 vs 404) is the check that distinguishes them.What this does not touch
No code. Documentation only.