Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
2527efa
feat(buzz-agent-core): drive buzz-agent's ACP surface on the goose li…
michaelneale Jul 27, 2026
63efbb2
feat(buzz-agent-core): pin goose by git rev, wire session/set_model
michaelneale Jul 27, 2026
4612452
fix(buzz-agent-core): advertise the model catalog on session/new
michaelneale Jul 27, 2026
e9aca45
feat(buzz-agent-core): implement the _Stop veto and _PostCompact re-i…
michaelneale Jul 27, 2026
22f5e70
feat(buzz-agent-core): inject [Reflect] on failed tool results
michaelneale Jul 27, 2026
737bde4
test(buzz-agent-core): validate against the real buzz-dev-mcp
michaelneale Jul 27, 2026
b631552
fix(buzz-agent-core): drain on cancel instead of dropping the stream
michaelneale Jul 27, 2026
fbe120f
test(buzz-agent-core): cover session/steer, the last untested ACP method
michaelneale Jul 27, 2026
3596e8e
fix(buzz-agent-core): correct a false claim about GooseMode::default()
michaelneale Jul 27, 2026
c12853e
build(buzz-agent-core): emit the binary as `buzz-agent`
michaelneale Jul 27, 2026
1bd2d94
docs(buzz-agent-core): add HANDTEST.md and a `just agent-core` recipe
michaelneale Jul 27, 2026
1f6b75a
refactor(buzz-agent): swap the agent loop onto the goose library
michaelneale Jul 27, 2026
ac6aed1
docs(buzz-agent): rewrite HANDTEST.md for the swap
michaelneale Jul 27, 2026
81eec0f
fix(buzz-agent): restore `auth` subcommand and close config compat gaps
michaelneale Jul 27, 2026
43d3698
feat(buzz-agent): restore relay-mesh adaptive MoA routing
michaelneale Jul 27, 2026
55696fc
docs(buzz-agent): pin the one mesh behaviour that is NOT parity
michaelneale Jul 28, 2026
41974bc
fix(buzz-agent): catch mesh-llm's 502 MoA failure path, not just the 503
michaelneale Jul 28, 2026
6c14563
Merge remote-tracking branch 'origin/main' into micn/buzz-agent-goose…
michaelneale Jul 28, 2026
fa8166e
fix(buzz-agent): address expert review — shutdown, cap race, usage model
michaelneale Jul 28, 2026
cca05c0
feat(buzz-agent): restore AGENTS.md hints and load_skill on the goose…
michaelneale Jul 28, 2026
66d1bff
chore(deny): allow MIT-0 for goose's borrow-or-share transitive dep
michaelneale Jul 28, 2026
3a7ffc0
refactor(buzz-agent): dispatch load_skill as a platform extension, no…
michaelneale Jul 31, 2026
1dee392
Merge origin/main into micn/buzz-agent-goose-core
michaelneale Jul 31, 2026
d35f704
Merge origin/main into buzz-agent-goose-core
michaelneale Aug 11, 2026
80a9f9a
chore(buzz-agent): bump goose pin to bf332b9 (unrolled loop) and fix …
michaelneale Aug 11, 2026
9869f24
refactor(buzz-agent): buzz owns the agent loop; goose supplies the parts
michaelneale Aug 11, 2026
1d098d6
test(buzz-agent): hand-test script driving a real provider over ACP s…
michaelneale Aug 11, 2026
20a731b
fix(desktop): admit buzz_agent logs to per-agent log files
michaelneale Aug 11, 2026
626b079
fix(buzz-agent): keep agent conversations out of the user's goose ses…
michaelneale Aug 11, 2026
c918877
fix(buzz-agent): stop rendering every AGENTS.md into the prompt twice
michaelneale Aug 11, 2026
c312a70
fix(buzz-agent): mark steered messages with with_steer()
michaelneale Aug 11, 2026
979eff9
refactor(buzz-agent): express the round budget as a goose Operation
michaelneale Aug 11, 2026
ce84471
refactor(buzz-agent): keep the turn conversation in memory, not sqlite
michaelneale Aug 11, 2026
45584a9
refactor(buzz-agent): hold the provider on buzz's side, not in goose'…
michaelneale Aug 11, 2026
6c7648f
fix(deps): patch eight nostr advisories instead of ignoring them
michaelneale Aug 11, 2026
3190102
merge origin/main into micn/buzz-agent-goose-core-v2
michaelneale Aug 11, 2026
8e1ba34
refactor(buzz-agent): use goose's skills instead of buzz's duplicate
michaelneale Aug 12, 2026
69c6091
feat(buzz-agent): back goose's Databricks provider with buzz's PKCE e…
michaelneale Aug 12, 2026
42fb755
fix(buzz-agent): the agent's configured provider must beat an inherit…
michaelneale Aug 12, 2026
d4db850
fix(desktop): stop an agent inheriting the developer's provider
michaelneale Aug 12, 2026
15f6fd5
refactor(buzz-agent): express the _Stop veto as a goose Operation
michaelneale Aug 12, 2026
ff44cf4
fix(desktop): keep runtime.rs under the file-size ratchet
michaelneale Aug 12, 2026
ab5b289
fix(buzz-agent): restore the env surface main had
michaelneale Aug 12, 2026
aef3921
fix(desktop): strip inherited agent config by prefix, not by name
michaelneale Aug 12, 2026
099bacb
refactor(buzz-agent): express steer and compaction as goose Operations
michaelneale Aug 12, 2026
60e63a0
docs(buzz-agent): record why [Reflect] stays out of the state machine
michaelneale Aug 12, 2026
e0d5f20
test(buzz-agent): cover the steer and compaction operations
michaelneale Aug 12, 2026
0ea48f4
test(buzz-agent): prove turn-to-turn memory across separate prompts
michaelneale Aug 12, 2026
9ca8806
Merge origin/main into micn/buzz-agent-goose-core
michaelneale Aug 18, 2026
e5f73cd
fix(buzz-agent): carry BUZZ_AGENT_LLM_TIMEOUT_SECS onto goose
michaelneale Aug 18, 2026
16a5282
docs(buzz-agent): correct stale env/limit rows after the goose move
michaelneale Aug 18, 2026
678dc68
chore(buzz-agent): drop the resurrected model_capabilities.rs copy
michaelneale Aug 18, 2026
b3d77f2
test(buzz-agent): cover provider failure paths end-to-end
michaelneale Aug 18, 2026
01c9137
Merge origin/main into micn/buzz-agent-goose-core
Aug 20, 2026
913cd56
chore(buzz-agent): update goose to 1.47
Aug 20, 2026
55edc4a
fix(agent): preserve migration behavior
Aug 21, 2026
a0dd2d4
Merge origin/main into micn/buzz-agent-goose-core
Aug 21, 2026
2ea414f
test(agent): serialize provider environment
Aug 21, 2026
e0d7899
Merge current main and update Goose GDK
Aug 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3,767 changes: 2,815 additions & 952 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ members = [
"crates/buzz-audit",
"crates/buzz-acp",
"crates/buzz-agent",
"crates/buzz-model-catalog",
"crates/sprig",
"crates/buzz-test-client",
"crates/buzz-ws-client",
Expand Down
45 changes: 11 additions & 34 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -332,14 +332,16 @@ test-unit:
# because nothing in CI runs `cargo test --workspace` — workspace
# membership alone buys clippy/check, not a single executed test.
cargo nextest run -p buzz-backend-kubernetes
# buzz-agent model-capabilities corpus: the Rust half of the
# cross-language drift guard. `model_capabilities.rs` embeds
# scripts/model-capabilities.json + scripts/normative-corpus.json via
# include_str! and replays the full locked corpus as pure in-process tests (no
# infra). Enumerated explicitly because nothing in CI runs
# `cargo test --workspace`; without this step a manifest edit that
# diverges Rust from the corpus ships green.
# model-capabilities corpus: the Rust half of the cross-language drift
# guard. `model_capabilities.rs` embeds scripts/model-capabilities.json
# + scripts/normative-corpus.json via include_str! and replays all 103
# vectors as pure in-process tests (no infra). It lives in
# buzz-model-catalog rather than buzz-agent because the desktop reads
# capabilities without linking goose. Enumerated explicitly because
# nothing in CI runs `cargo test --workspace`; without this step a
# manifest edit that diverges Rust from the corpus ships green.
cargo nextest run -p buzz-agent --lib
cargo nextest run -p buzz-model-catalog --lib
else
./scripts/run-tests.sh unit
fi
Expand All @@ -350,12 +352,12 @@ test-integration:

# Regenerate the model-capability normative corpus from the production Rust
# resolver. The corpus is a golden snapshot, never hand-edited: this runs the
# `#[ignore]`d writer test in buzz-agent, which serializes `resolve()` over the
# `#[ignore]`d writer test in buzz-model-catalog, which serializes `resolve()` over the
# inputs-only question table to scripts/normative-corpus.json. Run this after
# any model-capabilities.json edit, then commit the regenerated file. The
# `corpus_matches_generated_snapshot` gate fails CI if the committed file drifts.
regen-model-corpus:
cargo test -p buzz-agent --lib model_capabilities::tests::regen_corpus_file -- --ignored --exact
cargo test -p buzz-model-catalog --lib model_capabilities::tests::regen_corpus_file -- --ignored --exact

# Buzz shared compute e2e: current desktop discovery/admission logic and
# Playwright UI coverage.
Expand Down Expand Up @@ -996,31 +998,6 @@ benchmark *ARGS:
uv run --project benchmarks/harbor-buzz-orchestra/testbed \
benchmarks/harbor-buzz-orchestra/scripts/benchmark.py {{ARGS}}

# Run the benchmark adapter + testbed gate exactly as CI does (pytest + ruff, pinned ruff from pyproject)
benchmark-check:
#!/usr/bin/env bash
set -euo pipefail
cd "{{justfile_directory()}}/benchmarks/harbor-buzz-orchestra"
# CI installs the dev extra with pip, so pyproject — not uv.lock — decides
# which ruff lints. Read the pin from there so this recipe cannot drift
# from the workflow (a floating specifier once meant CI failed on RUF100
# while the locked local ruff passed).
ruff_pin="$(grep -oE 'ruff==[0-9.]+' pyproject.toml | head -1 | cut -d= -f3)"
for project in . testbed; do
(
cd "$project"
echo "── harbor-buzz-orchestra/$project (ruff $ruff_pin)"
uv run --frozen pytest -q
uvx "ruff@$ruff_pin" check .
uvx "ruff@$ruff_pin" format --check .
)
done
# The task verifiers live in the sibling benchmarks/buzz-dataset, so they
# need the harness config passed explicitly to stay linted.
echo "── buzz-dataset (ruff $ruff_pin)"
uvx "ruff@$ruff_pin" check --config pyproject.toml ../buzz-dataset
uvx "ruff@$ruff_pin" format --check --config pyproject.toml ../buzz-dataset

# Stop the benchmark Docker stack (state and channels are kept)
benchmark-down:
docker compose --project-name buzz-benchmark down
21 changes: 20 additions & 1 deletion crates/buzz-agent/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,31 @@ name = "fake-mcp"
path = "tests/bin/fake_mcp.rs"

[dependencies]
# For the `buzz-agent auth <provider>` subcommand only. Provider auth for the
# agent loop itself is goose's; this is the interactive PKCE login that seeds
# the token cache that goose (and the desktop model picker) then read.
buzz-model-catalog = { path = "../buzz-model-catalog" }
goose = { git = "https://github.com/aaif-goose/goose", rev = "92caef37f8c80e0ee31ab9acad117662bc169f78", default-features = false, features = ["rustls-tls"] }
goose-provider-types = { git = "https://github.com/aaif-goose/goose", rev = "92caef37f8c80e0ee31ab9acad117662bc169f78" }
# ModelConfig: goose re-exports it from goose-providers; goose::model_config
# keeps its own copy private.
goose-providers = { git = "https://github.com/aaif-goose/goose", rev = "92caef37f8c80e0ee31ab9acad117662bc169f78" }
# Resolution guard: goose pins icu_locale "=2.1.1" (needs icu_collections
# ~2.1.1) while url 2.5.x -> idna -> idna_adapter 1.2.2 pulls icu_normalizer
# 2.2.0 (needs icu_collections ~2.2.0). Only one 2.x icu_collections can be
# selected. 1.2.0 is the last idna_adapter on ICU4X 1.x, which keeps IDNA off
# the icu_collections 2.x line entirely and lets both resolve.
idna_adapter = "=1.2.0"
tokio-util = { version = "0.7", features = ["rt"] }
futures = "0.3"
anyhow = "1"

tokio = { workspace = true, features = ["rt-multi-thread", "macros", "io-std", "io-util", "sync", "process", "time", "net"] }
serde = { workspace = true }
serde_json = { workspace = true }
serde_yaml = { workspace = true }
reqwest = { workspace = true, features = ["json", "rustls", "form"] }
rmcp = { version = "1", default-features = false, features = ["client", "transport-child-process"] }
rmcp = { version = "3", default-features = false, features = ["client", "transport-child-process"] }
arc-swap = "1"
getrandom = "0.4"
tracing = { workspace = true }
Expand Down
122 changes: 122 additions & 0 deletions crates/buzz-agent/HANDTEST.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# Hand-testing `buzz-agent`

This is a **swap**, not a new thing. `buzz-agent` keeps its name, its binary,
its ACP contract and its place in the workspace — only the internals changed.
buzz-agent still owns the agent loop (`src/loop_drive.rs`); what changed is
that the loop now calls the `goose` crate for the model stream, the tool
surface, tool dispatch, the system prompt and compaction, instead of using
buzz-agent's own ~8k lines of provider and MCP code.

So there is nothing special to run. The normal flow *is* the test:

```bash
just dev # the whole app
# or
just relay # terminal 1
just goose # terminal 2 — agent against that relay
```

Both already build and use the swapped `buzz-agent`. If you see a difference,
that is the bug.

## Fastest useful check: the stdio smoke script

`just dev` is the real test, but it needs a relay, a desktop app and a human.
To exercise the loop against a **real provider** in ~30 seconds, drive the
binary over its ACP stdio interface directly:

```bash
cargo build -p buzz-agent -p buzz-dev-mcp
export ANTHROPIC_API_KEY=... # or your provider of choice
python3 crates/buzz-agent/scripts/handtest.py # persona + hints
python3 crates/buzz-agent/scripts/handtest.py --tools # real MCP tools
python3 crates/buzz-agent/scripts/handtest.py --stop-veto # _Stop veto
python3 crates/buzz-agent/scripts/handtest.py --cancel # no stuck spinner
python3 crates/buzz-agent/scripts/handtest.py --steer # mid-turn steer
```

Each mode asserts and prints its own verdict. This is the coverage the
automated suite cannot give you: **the suite uses a fake SSE server, so it
never proves a real provider works.**

## What to look at

Ordered by risk. The automated suite (41 tests) covers each of these at the
stdio layer; this list is the part only a human can judge.

### 1. Persona actually arrives

```
@fizz who are you?
```

Should answer *as Fizz* and know the `buzz` CLI exists. A generic goose answer
means the system prompt was dropped — the exact failure that makes plain-ACP
embedding impossible, and the reason this uses the library API instead.

### 2. `_Stop` veto

```
@fizz make a todo list with 3 items, then stop immediately without doing them
```

Must refuse to stop while items are open. Capped at 3 vetoes, so it ends
eventually regardless. Look for `_Stop hook vetoed end of turn` in the log.

### 3. Streaming feel

The old loop emitted one chunk per round; goose streams token-by-token. Tests
prove the relay is not write-amplified (chunks are coalesced by identity key,
flushed at 500ms, paced at 167ms/90-per-minute), but **only a human can say
whether it feels better or worse in the desktop app.** This is the most likely
source of "something is off".

### 4. Cancel mid-tool

Ask for something long (`count slowly to 100 with a shell sleep`), then stop.
The turn should end promptly with **no tool call left spinning**. Cancellation
is a cooperative drain with a 5s budget — dropping the stream instead would
leave the MCP child running and the spinner stuck forever. That bug existed and
is fixed; this is the visual confirmation.

### 5. Steering

Send a second message while the agent is working. It should be absorbed into
the running turn, not cancel-and-restart it.

### 6. Model picker

Should list models, not just the current one. An absent catalog is degraded UX,
never a session failure.

### 7. Tool hygiene — known deviation

The model can now *see* `_Stop` and `_PostCompact`: goose's allowlist gates
advertising and dispatch through the same cache, so hiding them would also make
them undispatchable and break the veto. A system-prompt extension tells the
model to leave them alone. Watch for it calling `_Stop` itself — if that
happens the guidance needs strengthening.

## Comparing against the old behaviour

The old loop is gone, so A/B means checking out `main` in a second worktree and
running that relay side by side.

## Known gaps

* **Anthropic is the only provider hand-tested end-to-end** (via
`scripts/handtest.py`, see above: persona, AGENTS.md hints, 9-model catalog,
real MCP tool call, `_Stop` veto to its cap, cancel-with-no-stuck-spinner,
and a mid-turn steer absorbed without restarting the turn). The automated
suite otherwise uses a fake SSE server. Databricks *chat* now goes through
goose entirely and is unexercised — though Databricks *model discovery*
still uses our own code, moved to `buzz-model-catalog` (the desktop cannot
link goose: native `sqlite3` collision with its own rusqlite).
* **Relay-mesh MoA is restored but only tested against a fake router.** With
`mesh-llm` running, a relay-mesh agent on `auto` should switch to the virtual
`mesh` model within ~2 turns once ≥2 models are live, and fall back to `auto`
within 30s of the mesh shrinking. Grep the agent log for
`relay-mesh auto:`.
* Binary is roughly +22.7 MiB raw / +6.0 MiB gzip.
* Nothing changed in packaging or the harness catalog — same sidecar name, so
nothing needed to.
36 changes: 23 additions & 13 deletions crates/buzz-agent/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,19 +154,30 @@ Everything is environment variables. No flags, no config files. (We are a subpro
| `BUZZ_AGENT_SYSTEM_PROMPT_FILE` | — | File path. Mutually exclusive with the above. |
| `BUZZ_AGENT_MAX_ROUNDS` | `0` | Tool-loop iteration cap. 0 = unlimited. |
| `BUZZ_AGENT_MAX_OUTPUT_TOKENS` | `65536` | Desired per-call ceiling. Set this at or below the served model's output limit for each agent deployment. Proactive handoff is independently based on 90% of `BUZZ_AGENT_MAX_CONTEXT_TOKENS`. |
| `BUZZ_AGENT_MAX_TOKEN_RECOVERIES` | `3` | Retries after a successful response is truncated at the output-token limit. `0` disables recovery; the finite value and `BUZZ_AGENT_MAX_ROUNDS` prevent infinite retries. |
| `BUZZ_AGENT_MAX_TOKEN_RECOVERIES` | | **No longer read.** buzz's own truncation-recovery loop went with its request transport; goose owns retries. |
| `BUZZ_AGENT_MAX_CONTEXT_TOKENS` | `200000` | Provider context window used by the handoff gate. |
| `BUZZ_AGENT_MAX_HANDOFFS` | `10` | Max context handoffs per session before falling back to truncation. |
| `BUZZ_AGENT_LLM_TIMEOUT_SECS` | `240` | Max seconds with no response bytes before abandoning an LLM call (per-read inactivity, not wall-clock). |
| `BUZZ_AGENT_TOOL_TIMEOUT_SECS` | `660` | Per-tool call timeout in seconds |
| `BUZZ_AGENT_MAX_PARALLEL_TOOLS` | `8` | Max concurrent tool calls per turn (1 = sequential) |
| `BUZZ_AGENT_MAX_HANDOFFS` | | **No longer read.** goose's compaction replaced the handoff mechanism it bounded. |
| `BUZZ_AGENT_LLM_TIMEOUT_SECS` | goose's own (600 s) | Per-request timeout. goose owns provider transport and reads this **per provider**, so buzz projects the value onto the variable belonging to the configured provider — `OPENAI_TIMEOUT` (which covers `relay-mesh` and the other OpenAI-wire providers), `ANTHROPIC_TIMEOUT`, `OLLAMA_TIMEOUT`, `LITELLM_TIMEOUT`. Providers goose gives no timeout knob (databricks) cannot honour it. |
| `BUZZ_AGENT_TOOL_TIMEOUT_SECS` | `660` | Per-tool call timeout in seconds. Projected onto goose's `GOOSE_DEFAULT_EXTENSION_TIMEOUT`. |
| `BUZZ_AGENT_MAX_PARALLEL_TOOLS` | | **No longer read.** All of a round's tool calls are dispatched concurrently; there is no cap to configure. |
| `BUZZ_AGENT_MAX_SESSIONS` | unlimited | Max concurrent ACP sessions. Sessions are cheap; default has no cap. |
| `BUZZ_AGENT_MAX_LINE_BYTES` | `4194304` | 4 MiB. Hard cap on inbound JSON-RPC frames. |
| `BUZZ_AGENT_MAX_HISTORY_BYTES` | `1048576` | 1 MiB. Old turns are evicted past this. |
| `BUZZ_AGENT_MAX_TOOL_RESULT_TEXT_BYTES` | `51200` | 50 KiB. Per-result cap on tool-output text; oversize is middle-elided (head + tail kept) with an inline marker. Images are exempt. |
| `BUZZ_AGENT_MAX_LINE_BYTES` | | **No longer read.** The frame cap is a fixed 16 MiB protocol limit (`config.rs::MAX_LINE_BYTES`). |
| `BUZZ_AGENT_MAX_HISTORY_BYTES` | | **No longer read.** Byte-based eviction is replaced by goose's token-aware compaction; size the window with `BUZZ_AGENT_MAX_CONTEXT_TOKENS`. |
| `BUZZ_AGENT_MAX_TOOL_RESULT_TEXT_BYTES` | `51200` | 50 KiB. Per-result cap on tool-output text. Projected onto goose's `GOOSE_MAX_TOOL_RESPONSE_SIZE` (whose own default is 200 KiB). |
| `BUZZ_AGENT_REQUIRE_REPLY` | `0` (`1` on mesh) | `1` enables the [reply guard](#reply-guard) — remind the model to publish when a turn is about to end with nothing posted to Buzz. Desktop defaults it to `1` for Buzz shared-compute agents. |
| `BUZZ_AGENT_STOP_MAX_REJECTIONS` | `3` | Max consecutive `_Stop` vetoes per turn. `0` disables the `_Stop` veto entirely. |
| `BUZZ_AGENT_NO_HINTS` | `0` | `1` suppresses `AGENTS.md` / `.goosehints` loading (sets goose's `CONTEXT_FILE_NAMES` empty). |
| `BUZZ_AGENT_APPROVAL` | `auto` | Tool-call approval policy passed to goose: `auto` \| `approve` \| `smart_approve` \| `chat`. `auto` matches previous behaviour. |
| `BUZZ_AGENT_HOOK_TIMEOUT_MS` | — | **No longer read.** Hook tools are dispatched through goose's normal tool path and share its extension timeout. |
| `BUZZ_AGENT_PROMPT_CACHING` | — | **No longer read.** goose's providers manage prompt caching. |
| `BUZZ_AGENT_THINKING_SUMMARY` | — | **No longer read.** goose's providers own reasoning-summary handling; `BUZZ_AGENT_THINKING_EFFORT` still applies. |
| `BUZZ_AGENT_MCP_INIT_TIMEOUT_SECS`, `BUZZ_AGENT_MCP_RESTART_*` | — | **No longer read.** goose's `ExtensionManager` owns MCP startup and restart. |


> Variables marked **No longer read** were live in the pre-goose agent. They are
> listed rather than deleted so anyone who set one can see that it now has no
> effect, and what replaced it. Everything else behaves as it did before.

## Reply Guard

Off by default, except on Buzz shared-compute (mesh) agents, where Buzz Desktop
Expand Down Expand Up @@ -299,7 +310,7 @@ The trust boundary is **the operator who launched the agent**. The harness, MCP
| MCP child env | Whitelist (`PATH`, `HOME`, `TERM`, `LANG`, `LC_ALL`, `TMPDIR`) plus what the client explicitly passes. Your `ANTHROPIC_API_KEY` does not leak into MCP children. |
| MCP child lifetime | Process group via `setpgid(0,0)` in `pre_exec`. On transport break or shutdown: `killpg(SIGKILL)`. Grandchildren die too. |
| Server poisoning | After a timeout or transport break, the offending server is marked dead. Future calls trigger a lazy restart with exponential backoff. Other servers keep working. |
| Frame size | `BUZZ_AGENT_MAX_LINE_BYTES` (default 4 MiB). Oversize → connection killed. |
| Frame size | Fixed 16 MiB (`config.rs::MAX_LINE_BYTES`). Oversize → connection killed. |
| LLM response size | 16 MiB hard cap. Both `Content-Length` precheck and streaming-buffer cap. |
| Cancellation | `tokio::select! { biased; _ = cancel.changed() => ... }` at every loop boundary. Cancel always wins the race. |
| Session isolation | Unlimited concurrent sessions by default (configurable via `BUZZ_AGENT_MAX_SESSIONS`). One prompt per session at a time. Each session gets its own MCP servers. |
Expand All @@ -309,20 +320,19 @@ The trust boundary is **the operator who launched the agent**. The harness, MCP

| Limit | Default | Where |
|---|---|---|
| Inbound JSON-RPC frame | 4 MiB | `BUZZ_AGENT_MAX_LINE_BYTES` |
| Inbound JSON-RPC frame | 16 MiB | `MAX_LINE_BYTES` (fixed) |
| Single prompt | 1 MiB | `MAX_PROMPT_BYTES` |
| History window | 1 MiB | `BUZZ_AGENT_MAX_HISTORY_BYTES` |
| LLM response body | 16 MiB | `MAX_LLM_RESPONSE_BYTES` |
| LLM error body | 4 KiB | `MAX_LLM_ERROR_BODY_BYTES` |
| Tool result body (total, incl. images) | 8 MiB | `MAX_TOOL_RESULT_BYTES` |
| Tool result text | 50 KiB | `BUZZ_AGENT_MAX_TOOL_RESULT_TEXT_BYTES` |
| Tool result text | 50 KiB | `BUZZ_AGENT_MAX_TOOL_RESULT_TEXT_BYTES` (projected onto goose's `GOOSE_MAX_TOOL_RESPONSE_SIZE`) |
| MCP servers / session | 16 | `MAX_MCP_SERVERS` |
| Tools / session | 128 | `MAX_TOOLS_PER_SESSION` |
| Tool description bytes | 1 KiB | `MAX_DESCRIPTION_BYTES` |
| Tool schema bytes | 4 KiB | `MAX_SCHEMA_BYTES` (oversize → replaced with `{}`) |
| Tool calls per turn | 64 | `MAX_TOOL_CALLS_PER_TURN` |
| Loop rounds | 0 (unlimited) | `BUZZ_AGENT_MAX_ROUNDS` |
| LLM read inactivity timeout | 240 s | `BUZZ_AGENT_LLM_TIMEOUT_SECS` |
| LLM request timeout | goose's own (600 s) | `BUZZ_AGENT_LLM_TIMEOUT_SECS` (projected per provider) |
| Tool call timeout | 660 s | `BUZZ_AGENT_TOOL_TIMEOUT_SECS` |

## What This Is NOT
Expand Down
Loading
Loading