Commit Graph

38 Commits

Author SHA1 Message Date
Alex Rosenzweig
4f51b9e101 fix(mcp): support Goose discovery handshake (#8037)
## Summary

Goose 1.52 starts MCP connections with `server/discover`. `buzz-dev-mcp`
rejects that request with `ExpectedInitializeRequest`, preventing Goose
from loading its Buzz tools.

Upgrade the tool server to rmcp 3.4.1 and adopt the renamed SDK types.
Subprocess tests exercise discovery and legacy initialization, tool
listing, file reads/edits, invalid arguments, connection survival, and
EOF shutdown. Include the server in both Rust unit-test runners and make
the cancellation regression approve its shell call before testing
cancellation.

The native agent retains its independent rmcp 1.x client. Cargo also
unifies the existing 3.x dependency used by the mesh smoke harness.

## Validation

- Normal `git push` passed all hooks, including Rust tests, Tauri
checks, and repository policy checks.
- `just ci` passed on the final stack: repository checks, Rust and Tauri
tests/lint, desktop tests/build, web build, and mobile checks/tests.
- MCP: 78 existing tests and two subprocess lifecycle tests passed; the
discovery regression fails with the old SDK.
- All 54 native-agent regressions passed on the final stack, including
cancellation of a real MCP shell process.
- Previously exercised unmodified Goose 1.52 and 1.47 with Sandpit and
the real tool server. Allowed workspace operations and permitted network
traffic succeeded; disallowed file and network operations were denied.
- Live staging desktop check with Goose 1.52: the reply appeared in
Buzz, an allowed request returned HTTP 200, and a disallowed request
returned CONNECT 403.

## Stack

1. **#8042 → `main`**: deterministic handoff regression.
2. **This PR → #8042**: Goose MCP discovery compatibility.

Merge the lower PR first, then retarget this PR to `main`. This diff
contains only the compatibility change and its tests; the relay build
repair is already on `main` in #8036.

---------

Signed-off-by: Alex Rosenzweig <arosenzweig@squareup.com>
2026-10-02 11:24:25 -05:00
Alex Rosenzweig
2c270cf9d7 test(agent): synchronize handoff steering with tool approval (#8042)
## Summary

`handoff_cap_binds_within_a_single_turn` can finish the turn before its
steer is accepted. It also requests `test_tool`, which the fake MCP
server does not expose, so the supposed tool round fails immediately.

Call the real `cap_test__tool_0` fixture and hold its permission
response until the steer is acknowledged. Assert that the next model
request contains the steer and the handoff cap still prevents a second
summary. This changes one regression test; production code is unchanged.

## Validation

- Reproduced the exact CI failure on unchanged `main` with a temporary
250 ms reader delay; the repaired test passed with the same delay. The
delay is not committed.
- Temporarily reset the handoff counter when a steer is queued: the
repaired test failed. Restored production code afterward.
- All six handoff regressions passed: `cargo test -p buzz-agent --locked
--test regressions handoff_`.
- `just ci` passed on the combined stack: repository checks, Rust and
Tauri tests/lint, desktop tests/build, web build, and mobile
checks/tests.
- All 54 native-agent regressions passed on the combined stack,
including this test.

## Stack

1. **This PR → `main`**: deterministic handoff regression.
2. **#8037 → this branch**: Goose MCP discovery compatibility.

Merge this PR first, then retarget #8037 to `main`.

Signed-off-by: Alex Rosenzweig <arosenzweig@squareup.com>
2026-10-02 11:10:33 -05:00
Kalvin C
48884848f5 fix: route databricks claude fqns to anthropic messages (#7829)
Fix Databricks V2 Unity Catalog Claude service routing without erasing
verified model capabilities.

- preserve exact provider-qualified record precedence for
`data_workflow_tools.goose.goose-claude-opus-5-5`, including adaptive
thinking, explicit `output_config.effort` (default medium), and signed
native tool continuation
- route other boundary-matched `claude-*` service components to
Anthropic Messages while preserving the full FQN and advertising no
unverified effort controls
- keep GPT-5+ service components on OpenAI Responses and other uncurated
FQNs on MLflow Chat Completions; catalog/schema text does not affect
routing
- keep Rust and TypeScript resolver precedence aligned and regenerate
the 168-vector normative corpus

Validation at `1204f1eacff17ebe05eb2bba06b5eee210e21c25`:
- `cargo test -p buzz-agent`
- `pnpm -C desktop test` — 6,628 passed
- `pnpm -C desktop typecheck`
- `cargo fmt --all -- --check`
- `cargo clippy -p buzz-agent --all-targets -- -D warnings`
- `just test-unit` — passed on isolated rerun
- pre-push file-size, desktop check, desktop typecheck, desktop tests,
and Tauri checks passed; the repository-wide Rust lane exited without
identifying a failing test in two concurrent hook runs, while its
isolated `just test-unit` rerun passed

---------

Signed-off-by: Kalvin Chau <kalvin@block.xyz>
Co-authored-by: am <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz>
2026-09-23 12:09:27 -07:00
Wes
0cc63fe3e6 fix(agent): preserve Databricks Opus UC reasoning and tool continuation (#7840)
## Summary
Fix `reasoning_effort: Extra inputs are not permitted` for Databricks v2
model `data_workflow_tools.goose.goose-claude-opus-5-5` without dropping
explicit High.

- Add one provider-qualified exact capability record: Anthropic
Messages, adaptive thinking, `output_config.effort`, medium upstream
default. Mirror exact-record precedence in Rust/TS; keep uncurated UC
services neutral and existing GPT routing unchanged.
- Preserve ordered native content, including signed/empty thinking and
redacted blocks, across tool results. Do not cache-stamp thinking blocks
or leak native state into Chat reasoning details. Discard truncated
native state; reject more than 64 native calls before any tool executes
instead of altering signed content.
- Add production-dispatch/ACP regressions and six shared capability
vectors. No relay/ACP protocol, schema, settings, inheritance, retries,
or other harness changes.

## Validation
At final head `82f43f8d07bfcc3f4af492afc0a58a02f8e549c4`, clean tree:
- Rebuilt real `buzz-agent`, drove ACP with a harmless MCP fixture
against the actual service. Two POSTs to
`/ai-gateway/anthropic/v1/messages`, both HTTP 200, exact FQN, High via
`output_config`, adaptive/summarized thinking, no `reasoning_effort`.
First response contained signed thinking + tool use; second request
preserved signature/content order and paired result; final ACP stop
`end_turn`. No credentials, signature contents, or conversation data
included here.
- Mandatory pre-commit/pre-push checks passed: formatting/lint,
file-size gate, Rust test lanes, desktop typechecking and full JS tests,
Tauri clippy/tests.
- Full `cargo test -p buzz-agent` passed on the pre-commit tree (only
hook formatting followed), including the new native truncation/65-call
ACP regressions, ordered interleaved signed/redacted two-tool capture,
nearby-ID negatives, and existing provider regressions.
- Independent API/replay, regression-boundary, and Rust/TS capability
reviews cleared.

Full `just ci`, packaging and manual desktop UI testing were not run
locally; repository CI remains the merge gate. Cross-harness internals
and other UC service contracts are outside scope.

## References
- [Databricks model
services](https://docs.databricks.com/aws/en/ai-gateway/query-model-services)
- [Anthropic thinking
preservation](https://platform.claude.com/docs/en/build-with-claude/thinking#thinking-block-preservation-by-model)
- [Anthropic
effort](https://platform.claude.com/docs/en/build-with-claude/effort)
- Originating Buzz channel: `fccdea96-0f31-4343-ba6a-324214dc8d73`
- Originating thread:
buzz://message?channel=fccdea96-0f31-4343-ba6a-324214dc8d73&id=33f242923a317e269c1ff7b960cacd6ec1f3371eff2f97592857804c50fb100c

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Signed-off-by: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@buzz.block.builderlab.xyz>
Co-authored-by: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@buzz.block.builderlab.xyz>
2026-09-23 09:44:13 -06:00
Wes
5511b56fcf fix(relay): exclude ephemeral activity from message quota (#7736)
## Summary

Stop charging ephemeral WebSocket activity against the per-minute
`Messages` quota. Presence, typing, and observer traffic should not
consume the allowance intended for persistable event attempts.

- Use the existing `buzz_core::kind::is_ephemeral` predicate (kinds
`20000..=29999`) at the second admission gate. All authenticated
EVENT/REQ/COUNT traffic still consumes the shared `WsEvents` budget
first. Non-ephemeral EVENT attempts remain charged, including attempts
later rejected by authorization or validation.
- Add bounded `bucket` labels (`messages`, `ws_operations`, `api_calls`)
to `buzz_admission_rejections_total`. HTTP changes are metric labeling
only. Wire rejection text remains byte-for-byte unchanged, preserving
existing client parsers.
- Add real-Redis admission regressions and select them in the existing
relay integration CI job. No quota increases, identity/keying changes,
client changes, new limiter, or deployment. Downstream authorization and
fail-closed Redis behavior are unchanged.

### Related issue

Duplicate search found overlapping work: open #4912 and closed #4902.
They propose the same ephemeral-accounting correction alongside broader
wire/client/config changes. The relay fix is a deliberately narrower
four-file implementation on the current admission module, with
metrics-only diagnostics and real-Redis regression coverage. A separate
test-only CI repair is documented below. It does not close or claim to
supersede those PRs.

Related symptom: #7411. This PR does **not** resolve subscription bursts
exhausting the shared WS budget; that budget is intentionally preserved.
It also does not establish or fix the cause of HTTP quota exhaustion.

### Testing

Validation ran against base `779af8886caae1317b4de962082429867ab61503`
plus the exact patch now committed as
`70ea0528594e25e6dcb40cd74620bd5ef0177592` (binary diff SHA-256
`5064e1cbb87ecb9eff10336bae14b912cac442e1edc247aa4f093de30e4f9717`).
Normal commit/push hooks subsequently passed on the committed head,
without bypass.

Passed:
- Independent review and rerun: 11 rejection tests (including real
Redis) plus 4 admission tests. Covers ephemeral range boundaries,
human/agent budgets, persistable events, ephemeral admission after
message exhaustion, mixed EVENT/REQ/COUNT flood exhaustion, Redis
outage, exact wire payloads, and bucket metrics. Three mutations were
caught: reintroducing ephemeral billing, exempting stored events, and
bypassing WS rejection.
- `just test-unit`: all 17 runner groups; `just test-integration`: all 3
runner groups. Auth integration reported no tests; ignored database
integration tests are not claimed as executed.
- Relay all-target Clippy, formatting, workflow actionlint,
pre-commit/signoff hooks, and all applicable pre-push lanes
(`push-head-scope`, `branch-skew`, `file-size-check`, `rust-tests`,
`desktop-tauri-checks`).
- Release relay/CLI/admin build and isolated release-binary workflow:
channel creation, signed root/reply sends, exact-ID readback, and thread
retrieval. Debug and release WS smoke admitted three presence events
with a Messages limit of two, admitted two persistable events, rejected
the third, still admitted presence afterward, and rejected author
mismatch. Debug Prometheus confirmed `bucket="messages"`.

**Outstanding validation gap:** the additional `cargo test -p buzz-relay
--no-fail-fast` run was not green: library 1,055 passed / 2 failed / 100
ignored; binary 13 passed / 1 ignored; boot integration 9 passed.
Failures were
`api::mesh_demo::tests::demo_join_forwarded_arm_round_trips_echo` (504
instead of 200) and
`telemetry::tests::trace_context_lookup_does_not_enable_callsites`
(tracing assertion). Neither failure was diagnosed or repaired in this
scope, and this PR does not claim a fully green package suite. `just ci`
was not run locally; hosted CI remains to validate the PR.

All runtime tests used isolated local services. Owned services have been
stopped; nothing was deployed and no production data was touched.


### CI cancellation-test repair

At the original relay head, hosted Rust unit tests failed on the
pre-existing cancellation harness race tracked in #4945. With owner
authorization, this PR now adapts the one-file repair from #5003
(original commit `cc8dc6124957acfd67fe2336396b58e0ff43ec60`), preserving
author credit. #5003's old hosted workflows failed without starting
jobs; their status is not used as validation evidence.

Commit `55fe5f16618a51d03ad48d6f2f97b2f8b463607a` holds the fake
provider response until a successful cancellation acknowledgement, then
replays all pre-acknowledgement usage/prompt frames in order. It retains
the cancellation and usage-before-response assertions and explicitly
requires prompt completion. No production code changes in this
follow-up.

Validation on parent `70ea052859` plus this test patch, before the
pre-commit-only assertion wrapping: full `cargo test -p buzz-agent
--no-fail-fast` passed (715 tests, one ignored); 100 targeted
repetitions passed with four concurrent workers; all-target buzz-agent
Clippy passed. The unmodified parent also passed 50 local repetitions,
so this is not claimed as a local reproduction of the intermittent CI
failure. The synchronization defect was confirmed from the source,
original CI log, and #4945. Hosted CI must validate the updated head.

Independent source review cleared exact commit
`55fe5f16618a51d03ad48d6f2f97b2f8b463607a`. Normal pre-commit/signoff
and all applicable pre-push hooks passed without bypass. The original
relay-package mesh-demo and telemetry failures remain disclosed above
and were not modified by this test-only repair.

---------

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <32a2e2c9d428ee08902cab75d956da2c1d235a22d4766b0dd4138bf6e2e5db1d@buzz.block.builderlab.xyz>
Co-authored-by: kiranmagic7 <262980978+kiranmagic7@users.noreply.github.com>
2026-09-19 02:39:37 -06:00
Wes
4cd82f5132 feat(buzz-agent): add opt-in workspace-scoped Databricks reuse (#7606)
Posted by Brain, Wes Billman’s AI assistant, on behalf of `wesbillman`.

## Summary

Add an opt-in `databricks::DatabricksConnection` over the existing
PKCE/coordinator and v2 catalog machinery. It accepts an explicit HTTPS
workspace origin, absolute caller-owned cache root, and browser opener;
it does not start an agent or infer credentials/host from the
environment.

- Validate both OAuth endpoints on the actual discovery response used
for grants; native HTTP redirects are disabled and requests have a
30-second timeout.
- Keep strict cache/single-flight state in a separate namespace. Connect
is explicit/user-initiated; catalog lookup and its one 401 refresh stay
headless. Tokens are not exposed by the connection API.
- Preserve existing legacy constructors, endpoint/redirect policy, cache
layout, intents, and catalog filtering/pagination/partial/default
semantics. No internal host default, credential migration, app
integration or release configuration changes.

### Intentional shared changes

OAuth success/discovery JSON is capped at 1 MiB; error JSON at 16 KiB.
Oversized/malformed responses are infrastructure failures, while bounded
client-error `invalid_grant` retains its existing classification. OAuth
diagnostics omit raw bodies/URLs/opener/callback details. Overflowing
token expiry returns an infrastructure failure. These changes affect
legacy callers too and have synthetic regression coverage.

See [the reuse contract](crates/buzz-agent/DATABRICKS_REUSE.md) for API,
compatibility and caller obligations. Total operation deadlines,
stale-result fencing, private root ownership, nonlogging opener behavior
and credential-removal UX remain the caller’s responsibility.

### Related issue

Related discovery work: #6918. Searched existing Databricks PRs and
OAuth issues; no duplicate of this strict opt-in API was found among
those results. This is a new reuse boundary, not a claim of a live
credential leak.

### Testing

Published head: `cf33eb2f4f267d5808e068e35127d5504f0bc674`, based on
main `78618804ec86a014524ad7d1fb55928e8f5c3edf`.
Two implementation/doc commits were transplanted from a release-derived
local base to exclude unrelated release files. `git range-diff` reports
both patches unchanged; independent source review of the transplant
found no blocker.

**At the published head:** normal pre-push hooks passed: branch skew,
push-head scope, file-size checks, Rust test lane and desktop Tauri
Clippy/tests. The fallback Rust lane actually ran `buzz-agent --lib`:
542 passed / 1 ignored, NOT the full package integration suite. Hosted
full package/repository CI remains pending. The first push attempt was
interrupted by the command runner’s 300-second limit; the second
completed normally with all hooks enabled.

**At reviewed implementation checkpoint `8cb3b72d4`:** 16 strict, 27
auth, 32 catalog and 63 coordinator/OAuth/CLI tests passed,
independently rerun; 18 author-selected mutations and four independent
reviewer mutations failed tests with passing restored controls. The
subsequent `539319411` delta changed only two doc-comment blocks. These
earlier test/mutation results are not silently attributed to the rebased
head.

Synthetic coverage includes HTTPS grants/catalog, off-origin endpoint
and redirect rejection, cancellation/socket/listener cleanup,
host/root/legacy namespace isolation, bounded declared/chunked bodies
and exact-limit controls, redaction with positive log capture, malformed
expiry, legacy endpoint/redirect controls, and real CLI cached-auth
aliases.

Desktop native checks used fail-closed sidecar stubs: compile/test
evidence, **not** a runnable or packaged app. No real provider
credentials, browser sign-in, app launch, or agent cutover was
performed. Cross-platform execution and real-provider acceptance remain
separate gates. No merge or downstream adoption is requested by this PR.

---------

Signed-off-by: Brain <1a02c72794dcd0f07058a353bc3a81f4028b8c77c92c87fce6d5c8b85970a20b@buzz.block.builderlab.xyz>
Co-authored-by: Brain <1a02c72794dcd0f07058a353bc3a81f4028b8c77c92c87fce6d5c8b85970a20b@buzz.block.builderlab.xyz>
2026-09-13 08:04:56 -06:00
Bradley Axen
cd54e26823 fix(agent): route GPT-5+ model-service FQNs to Responses (#7358)
## Summary
Route Databricks Unity Catalog model services to OpenAI Responses when
their service name matches GPT-5 or newer. These models can reject tools
plus reasoning on Chat Completions.

Match only the service component, using the existing family-token
boundaries and a numeric major version. Catalog and schema names cannot
select the protocol. Keep neutral effort capabilities and the full model
ID unchanged; other services still use MLflow Chat Completions.

Keep the Rust and desktop resolvers in sync, add shared boundary cases
and a captured-HTTP regression for completion and summarization, and
update the documented FQN rule.

### Related issue
No duplicate found in searches for “FQN responses” PRs or “astra”
issues. Related: #6918 introduced Unity Catalog discovery.

Originating conversation:
buzz://message?channel=0b881928-a3a6-4c01-b981-8e64268f01ce&id=770949343bc96a9ed88acd90a1b37d358a0efc52c79237d0fdb491ce02b8d4ed

### Testing
No live Databricks inference test. The gateway must accept the full
model-service ID on its OpenAI Responses route; this remains the
integration risk.

The local `just ci` attempt exceeded its five-minute deadline during
`mobile-check`, so the full repository gate was not completed. All
push-hook checks passed.

Generated with Codex

Signed-off-by: Fizz <400e8babadcee6a7f420103f10a2849d84c4a9c71d5bd04f3948c814216648a3@buzz.block.builderlab.xyz>
Co-authored-by: Fizz <400e8babadcee6a7f420103f10a2849d84c4a9c71d5bd04f3948c814216648a3@buzz.block.builderlab.xyz>
2026-09-08 08:23:48 -06:00
Logan Johnson
d5a73b9f35 fix(desktop): derive agent availability from relay presence (#7127)
🤖

## Requested rebase published — ef40744b

Rebased onto fetched main **47d068e2109d077414cbf2f4f1c927f6d051037a**,
published **ef40744b3aeb4baaf8c81416e1a644fb5b315f91** with the exact
expected-old `df7fad6a` force-with-lease. No merge.

Manual conflicts were additive: preserve main's exact-key identity
documentation alongside the availability contract, and retain both
Bestie props and the shared availability reader in
`UnifiedAgentsSection`. Range-diff confirms unchanged lifecycle policy:
exact-key action-time authority, Unknown versus Offline, rejected
shutdown retains record/memberships, and separate local/provider/owner
gates. Main's exact-key profile routing survives. Both test-only CI
synchronization repairs (natural toast expiry and bounded stderr wait)
are byte-identical to the prior head. All ten original
authors/messages/DCO/material coauthor trailers are preserved;
configured signing policy was not changed.

Fresh checks on the rebased candidate:
- TypeScript, Biome on 26 changed TypeScript files, differential
file-size gate, and diff whitespace: pass.
- Focused production-hook/card/profile units: **58/58**.
- Fresh E2E build, availability/deletion browser: **11/11**, no retries.
- Main exact-key profile cases plus failed-DM send/startup retries:
**6/6**, no retries.

Previously reviewed full Desktop/buzz-agent package and mutation
evidence is reused for unchanged behavior; no ceremonial full suite, new
native/provider test, or `just ci` pass is claimed. Local configs,
dependency links, and historical artifacts are preserved.

Hosted observation: **MERGEABLE**, **BLOCKED / REVIEW_REQUIRED**, no
new-head formal review. [CI
33699735990](https://github.com/block/buzz/actions/runs/33699735990) is
running (including Rust and Desktop lanes), not a completed success. DCO
and required Security aggregate passed at the observation; the separate
Codex advisory review was skipped. No completed failing check or new
inline feedback observed. Historical approvals are not new-head
approvals. No reviewer/security authorization or merge action was
performed.

---

## Feature summary and retained pre-rebase evidence

## Summary

In Buzz Desktop, an agent could look online just because it had been
started or deployed, even when there was no current sign it was
connected. Cards and profiles now show availability from the agent's
relay presence rather than a saved launch record, so you can distinguish
an online agent from one that was merely deployed.

Agents cards and profiles use presence reported through the shared
server (the relay). A successful presence read with no online agent
shows Offline; failed/disconnected evidence shows unknown, rather than
retaining a misleading cached Online state.

Lifecycle actions remain separate. An offline agent may still have a
Shutdown action because the deployment record exists. Shutdown reports a
**request**, not proof the process stopped. Offline does not imply that
starting a duplicate agent is safe, and Online does not promise a
response.

### Related issue

Independent base: `main`; no stack parent or child among the
replacements. Extracted from
[#7114](https://github.com/block/buzz/pull/7114), retained as historical
source (`98fe33ec`).

[Behavior
contract](f4bb2ed44e/docs/agent-availability.md).
Originating [Buzz
discussion](buzz://message?channel=f7a9536a-1738-4bad-a888-b3ea25010ef1&id=7aa1f0ab23dce514bd8a0221441cf005bf428914621171472b79747c50820848)
· channel `f7a9536a-1738-4bad-a888-b3ea25010ef1`.

### Testing

The same saved provider-backed agent, with only authored presence
changing. These are mock-browser states, not a before/after deployment
or live relay transport test; production UI is unchanged by the later
fixture repairs.

**No online presence:** gray dot, existing Shutdown control retained.

![Offline presence does not remove the deployment lifecycle
control](https://raw.githubusercontent.com/block/buzz/ed68b1b4597dde47118b7591b1e8ebf3702447ca/pr-7127--offline-deployment.png)

**Online presence:** green dot, same lifecycle control.

![Authored Online presence changes availability without changing the
deployment](https://raw.githubusercontent.com/block/buzz/ed68b1b4597dde47118b7591b1e8ebf3702447ca/pr-7127--online-deployment.png)

[Capture
details](https://github.com/block/buzz/pull/7127#issuecomment-5482264824).
To check manually, compare runtime-only transitions with presence
updates, then disconnect/fail the presence read and verify it does not
stay Online. A Shutdown request should not immediately claim confirmed
termination.

#### Historical pre-rebase evidence and limitations (df7fad6a)

Lifecycle production source remains
**`f4bb2ed44e5a989d93c5f51e93c0bbd2dca941be`**. Current published head
is **`df7fad6ae65dda78508317186a95522d1bb22ed9`**: the prior browser
synchronization at `b78d093e` plus an additive two-file Rust
test-harness synchronization described below. No production bytes,
dependency/configuration files, or prior commits were changed; no
rebase. Current live main `0dbd036f5bff33e7ade75e7639f3218d424a6e73` has
identical failing-test/toaster/send-flow source; the causal browser
comparison used latest successfully tested main
`04babf02655440b4dfd37f2e2df605ead0a030d8`.

**Lifecycle/deletion correction:** both Agents and actual profile
deletion now pass the shared exact-key availability reader, not raw
cached data. It reads the canonical query state and connection at action
time, including after awaited channel discovery.
Failed/disconnected/pending evidence and unqueried persona siblings are
unknown; successful missing means Offline only for a requested key.
Successful background refetch cache remains usable; settled failure
revokes it. No second cache or per-row polling was added.

Provider record + channel + Online/Away/**unknown** awaits shutdown
submission before local removal; rejection preserves record/membership
for retry. Established Offline preserves intentional no-request removal.
No route preserves warned local removal. Local agents retain native
stop-before-remove, independent of presence. Profile consent now
describes a shutdown **request**, not remote deletion or guaranteed
termination. Existing ownership and force gates are unchanged.

**Verified, reused exact-candidate validation:** the independently
approved eleven-file patch (SHA-256
`2f69fe12ef0420e62dea1fd8db28cfa22cde5eecaf8080e656310a3e60d0cf86`) was
committed without byte changes. Desktop **5,921 passed, 0
failed/skipped**, including **26 new mounted production hook/IPC
regressions**; rebuilt availability browser suite **11/11 passed, no
retries**, including four actual profile Delete journeys. Desktop check
(existing 4 warnings/5 infos), typecheck, production/protected-feature
artifact matrix, differential file-size/policy and diff checks passed.
No blanket rerun or new full-repository `just ci` is claimed for this
frontend correction.

Production regressions cover cached Online **and Offline**
failure/disconnection, genuine missing/Offline, pending, successful
inflight refetch versus settled error, retained reader, error during
awaited channel discovery, unqueried persona sibling, shutdown
rejection/order/cancel, no route and local authority. Browser fixtures
use safe mock IPC and a retained provider receipt, not a real
deployment. Three restored mutation controls fail: unknown → skip
shutdown (**15** regressions), Agents raw-cache reader (**6**), actual
profile raw-cache caller (**1 browser journey**, false removal on failed
cached Offline). Independent review approved the exact frozen bytes and
added **4/4 cached-empty failure/disconnection probes** across both
callers. This is local independent approval, not formal GitHub/A Team
clearance.

The prior native propagation/poll-count defects remain closed ([earlier
response](https://github.com/block/buzz/pull/7127#issuecomment-5512278755)).
The prior hover-popover correction at `b55423f6` remains covered by the
full 11-journey browser run: pending/failed/disconnected means no badge
or accessible status, genuine missing/Offline retains an Offline badge.
Its earlier fallback-restoration mutation failed as expected (badge
count 1 rather than 0); that historical witness is reused, not rerun.

**Reused unchanged native/system boundary:** local `just ci` at
`c59067d8` passed workspace/Tauri fmt/clippy, static/policy checks, Rust
unit recipe, native workspace **3,159 passed / 20 ignored**, Web build
and **2,019 mobile tests**. No native implementation changed in this
lifecycle correction. These are historical boundary results, not
new-head native/live certification. [Parent
CI](https://github.com/block/buzz/actions/runs/33650549130) passed with
**14 retry-recovered browser flakes**, not retry-free. Old-head
CI/reviews are not current-head clearance.

**Hosted gates:**
[CI33662151103](https://github.com/block/buzz/actions/runs/33662151103)
on `f4bb2ed4` **FAILED**: smoke shard1 had 322 pass, one failure, one
retry-recovered flaky, two skipped. The failed first-DM retry test timed
out on all three attempts because the error toast intercepted Send. That
failure is preserved, not waived; the scoped test repair below is
published as `b78d093e`.
[CI33668171165](https://github.com/block/buzz/actions/runs/33668171165)
on `b78d093e` subsequently **FAILED** the Rust unit budget regression
described below. Both original failures remain visible; neither was
retried to green. Exact `f4bb2ed4` and `b78d093e` APPROVED reviews cover
unchanged reviewed bytes, not formal approval of the new head. Fresh
exact-head CI and the established automated technical rereview are the
next gates for `df7fad6a`. Historical deletion responses remain
([5092381800](https://github.com/block/buzz/pull/7127#issuecomment-5513759316),
[5092391193](https://github.com/block/buzz/pull/7127#issuecomment-5513763497)).
No formal review dismissed. The [security
notice](https://github.com/block/buzz/pull/7127#issuecomment-5482231278)
and latest-push maintainer/codeowner policy remain separate actionable
gates: eligible Block organization members own current-range
authorization. No merge/security authority exercised.

**CI causal repair (`b78d093e`, test only):** the error `Message failed
to send: Mock first DM send failed.` is deliberately injected by the
existing fixture. CI screenshot and retry trace show the bottom-right
Sonner notification over the actual enabled Send button. `fill()` leaves
the pointer parked there; Sonner pauses its 4-second lifetime while
hovered. A fast run can click before animation settles (unchanged local
test passed in 2.7s; two actual tested-main CI cases passed first
attempt in 3.3s), which does not disprove the failure. Independent
controlled browser runs on `f4bb2ed4` and tested main `04babf` both
reproduced the same toast hit-test at Send `(1203,627,32,32)`,
persistent hover beyond 4s, and intercepted ordinary click with no
second send. Moving the real pointer to the editor allows natural expiry
and successful ordinary retry, preserving all original
DM-channel/recipient assertions. This same synchronization already
exists in the neighboring agent-startup-failure test.

The one-file correction keeps the visible error assertion, scopes its
toast locator, moves the pointer back to the editor and observes normal
toast removal (bounded 10s) before retry. No forced click, direct toast
dismissal, mocked clock, CSS override, skipped test, production behavior
change, or new backend mock. Six focused browser executions pass
(first-send/startup-failure, three repeats each, no retries); the
held-toast control fails on original bytes at the Send click while the
exact repaired test passes. Biome and diff checks pass. Reuse unchanged
5,921 Desktop / 11 availability browser / four independent probes above;
no semantic production change warrants repeating those suites. Original
failed CI attempt/retries, local fast pass, deliberate failing control
and all traces remain in `WORK_LOGS/AVAILABILITY_CI_B9210A40`. Browser
evidence is mock-IPC Chromium, not native/live-relay certification. The
UI still temporarily overlays Send while a notification is hovered; the
test exercises its real move-away/expiry recovery, not immediate
click-through.

**Rust CI causal repair (`df7fad6a`, test only):** [original Rust / Unit
Tests failure,
job100375291370](https://github.com/block/buzz/actions/runs/33668171165/job/100375291370)
tested GitHub merge `223dee91a396d8cb4ebf18b9b8559e5a54951235`.
`context_recovery_budget_exhaustion_surfaces_the_error` failed at
`regressions.rs:2756` in **0.091s** because its immediate stderr
snapshot lacked `context recovery budget spent`. ACP context-error
assertions had already passed. The captured prefix shows all three
budgets **32768 → 16384 → 8192 bytes**, above the 4096-byte floor, and
ends during the third attempt. This is **not evidence of floor
exhaustion**. The collector is an independent Tokio task; a stdout
response is not a stderr barrier. Recovery, harness and test blobs were
identical across the compared base/head/merge parents; no production
regression was implicated.

The shared test Harness now provides a bounded event/condition wait,
registering for collector notifications before reading the buffer to
avoid lost wakeups. The budget and adjacent terminal floor assertions
wait for their own diagnostic and retain the matching snapshot. The
budget test still requires the provider's ACP context error and exactly
three recovery rungs, now corroborated by **exactly four provider
calls** and no floor diagnostic. Timeout remains a real failure with
captured stderr. No fixed sleep, weaker assertion, skip,
provider-limit/logging change, dependency/config edit, or production
change.

**Deterministic causal control:** the same real agent/HTTP-provider/ACP
scenario holds only stderr collection behind a one-shot gate until after
stdout responds. The old immediate snapshot fails the original budget
assertion (intentional exit101); the repaired wait explicitly remains
Pending while held, then passes after release. No scheduler-speed
assumption or fixed sleep. This reproduces the observation race under
controlled delay, **not the exact historical CI schedule**. A
missing-diagnostic test proves the wait actually times out. Original
failure and deliberate failing-control logs/patch remain in
`WORK_LOGS/RUST_TRIAGE_06E32D2D` and `WORK_LOGS/RUST_SYNC_BC5758B9`.

**Final candidate validation:** focused recovery **9/9**, floor **1/1**,
absent-diagnostic timeout **1/1** pass. One full touched-package run,
`cargo test --locked -p buzz-agent`: **695 passed, 0 failed, 1 existing
ignored**, including all **54 regressions**. Local nextest was
unavailable, so this uses the repository-supported cargo-test fallback,
not a claim of nextest reproduction. `cargo fmt --check`, package-scoped
Clippy all-targets with `-D warnings`, differential file-size/policy and
diff checks pass. Previously reviewed availability production and the
Desktop/browser evidence above are unchanged and reused; no all-native
blanket rerun. The test-only delta was self-reviewed against collector
ordering, timeout and falsification evidence. Existing production
approval remains valid for those bytes; exact-new-head technical/CI
clearance is not assumed. The required **Security aggregate** is
distinct from optional Codex advisory feedback; no security
authorization, human review contact, or merge was requested.

**Remaining policy limits:** shutdown submission is not harness
acceptance or process termination; confirmed Offline/no-route local
removal may leave a remote process; route discovery is best effort,
membership cleanup uses `Promise.allSettled`, and multi-instance
deletion is sequential/non-atomic. No distributed singleton,
provider-health, tenant-switch cancellation, live relay TTL or packaged
WebView/VoiceOver certification is claimed. The pre-existing DM-header
raw-presence fallback (`ChannelScreenHeader`/`useActiveChannelHeader`)
remains outside this repair and uncertified. Screenshots above remain
historical mock-browser illustrations, not new deletion or native
transport evidence.

---------

Signed-off-by: Logan Johnson <loganj@squareup.com>
Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
2026-09-02 21:53:15 -04:00
Will Pfleger
5aed49b505 feat(buzz-agent): add DatabricksAuthCoordinator single-flight OAuth (#5545)
## What

Consolidates all Databricks OAuth acquisition behind one coordinator on
`PkceOAuthTokenSource`. Every entry point — the four `TokenSource`
methods (`bearer`, `bearer_no_browser`, `refresh_now`,
`interactive_login`) plus the public `acquire_with_intent` — routes
through a single `acquire()`/`acquire_locked()` core that owns browser
and cooldown policy.

Before this, acquisition logic was scattered across those methods with
no coordination: concurrent callers (Desktop discovery, the saved-agent
model picker, managed-runtime inference) could each pop their own
browser, and a just-denied attempt would immediately re-prompt on the
next passive read.

## How

- **Intent policy.** `AuthIntent::{Auto, UserInitiated, Headless}`
decides whether a caller may open a browser and whether it honors the
cooldown. `Headless` never browses; `Auto` browses but honors an
unexpired cooldown; `UserInitiated` browses and bypasses+clears the
cooldown.
- **Two-layer single-flight per cache key.** An in-process registry
(`INFLIGHT`) coalesces same-key, same-intent callers onto one leader's
attempt before the file lock. The slot key is `(lock_path, AuthIntent)`,
so a `UserInitiated` sign-in never inherits an `Auto` leader's result. A
joined result is revalidated against the waiter's own contract. Across
processes, callers serialize on a `flock`-based advisory lock and share
success through the on-disk cache. RAII `Drop` releases both lock and
leader slot.
- **Joiner credential-state reconciliation.** `SlotPublish` carries the
full `CachedToken` on success. Each joiner reconciles its own
independent `state` cell under `state.lock().await` before returning:
adopt when absent, expired, or matching the joiner's rejected
credential; preserve any distinct newer usable credential. On a matching
shared failure, neutralize the joiner's in-memory rejected entry under
lock via `expire_rejected_memory` — durable disk mutation is reserved
for `acquire_locked` under the cross-process file lock. Without this, a
joining source's state remains stale or empty and subsequent plain
`bearer()` calls resurface the rejected or absent credential.
- **Validate-before-persist boundary.** `finish()` is the
candidate-token persistence boundary for refresh and browser results.
Before a token is written to cache or the cooldown is cleared, a bearer
equal to the caller's rejected bytes yields a typed failure.
- **Token neutralization.** When `acquire_locked` enters with `rejected
= Some(bytes)`, it calls `expire_rejected()` under the state lock before
any cache check.
- **Cross-process failure single-flight.** An `AttemptRecord` sidecar
records a monotonically-increasing generation, intent, result code, and
SHA-256 digest of the completing caller's rejected token. Adoption is
temporal (pre-queue snapshot predates current generation) and
digest-matched.
- **Typed outcomes.** `AuthError` with stable `code()`/`from_code()`
replaces display-text matching.
- **Durable cooldown sidecar.** Every failed browser attempt is recorded
next to the cache key. 5-minute expiry.
- **Windows disk persistence disabled.** On non-Unix platforms,
`persist()` is a no-op. Lock, cooldown, and attempt sidecars are active
on all platforms. Tests that seed or assert on the on-disk token cache
are `#[cfg(unix)]`-gated.
- **Injected browser opener** invoked while the localhost callback
listener is live.

## Tests

- `crates/buzz-agent/tests/databricks_auth_coordinator.rs`:
browser/cooldown/classification acceptance matrix with a scripted
`BrowserOpener` and stub OIDC provider. P1 regressions exercise the full
`finish()` → `acquire_locked()` → `acquire_leader()` →
`LeaderGuard::complete()` → joiner wiring:
- `test_inprocess_joiner_reconciles_stale_state_after_shared_success`
(Unix): two real sources both loaded locally-fresh-but-rejected X; after
shared success Y, subsequent plain `bearer()` on both returns Y, not X.
-
`test_inprocess_joiner_neutralizes_rejected_on_matching_shared_failure`
(Unix): B's matching rejected X is force-expired in memory after shared
`RefreshRejected`; subsequent read cannot return X.
- `test_inprocess_joiner_populates_empty_state_no_second_acquisition`
(non-Unix): empty A/B join a browser success; B's subsequent headless
read returns Y without a second browser (no disk fallback on non-Unix
exposes the regression).
- `test_crossprocess_userinitiated_waiter_adopts_predecessor_denial`
(snapshot-marker barrier replacing an earlier sleep for deterministic
generation ordering).
- `auth.rs` in-crate tests: lock-primitive edges, disk-recheck on shared
failure (`#[cfg(unix)]`), and:
- `test_joiner_reconciliation_blocked_until_state_lock_released`:
deterministic direct-poll proof that awaited reconciliation requires
`state.lock().await`. The test task holds B's state mutex and manually
polls a pinned real `acquire()` future — Poll 2 (slot published, mutex
still held) must return `Pending` because `lock().await` blocks; with
`try_lock` instead, Poll 2 returns `Ready`, failing the assertion.
- `test_joiner_preserve_distinct_newer_credential`: deterministic direct
polling parks B at `slot.wait()`, then writes Z directly into B's state
in the same task, then publishes Y and awaits completion. B must return
Y but leave state == Z. Mutation check: unconditional adoption
overwrites Z with Y, failing the state assertion.
- `test_joiner_shared_failure_recovers_disk_replacement` (Unix): the
matching-failure joiner enters the recovery branch — after
`expire_rejected_memory` (in-memory, empty state no-op) it reads a
sibling-written disk replacement via `usable_from_disk` and returns it.
Mutation check: removing the `usable_from_disk` recovery branch returns
`Err(RefreshRejected)`.
- `test_joiner_failure_does_not_write_disk` (Unix): byte-for-byte
disk-invariance regression — a matching-failure joiner calls
`expire_rejected_memory` and must not touch the on-disk cache. An
independent process C may write a valid replacement between A's failure
and B's reconciliation; this guard ensures B's unfenced in-memory
neutralization cannot overwrite C's concurrent disk write. Mutation
check: reverting to `expire_rejected` rewrites the file (`expires_at =
0`), changing the bytes and failing the assertion.
- `test_lock_timeout_leaves_cooldown_sidecar_byte_for_byte_untouched`: a
waiter past its deadline returns `LockTimeout` before entering
`acquire_locked`; the cooldown sidecar bytes are unchanged. Documents a
pre-lock limitation: `LockTimeout` callers do not neutralize state or
sidecars.

## Scope / follow-ups

- **Runtime 401 handling is deferred.** This PR owns acquisition
single-flight and policy.
- **Desktop wiring is Phase 2** (not in this PR's boundary). Confined to
`crates/buzz-agent/`.
- **Windows DACL** is a follow-up once the `windows-sys` binding is
available.

## Stack

Built on [#5534](https://github.com/block/buzz/pull/5534)
(`hayt/databricks-oauth-cache-hardening`), now merged. Retargeted to
`main`.

---------

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
2026-09-01 15:59:21 -04:00
Kalvin C
be8d7756d5 feat(agent): discover Databricks Unity Catalog models (#6918)
## Summary

Add Unity Catalog model-service discovery to `buzz-agent` Databricks v2,
preserve full Unity Catalog FQNs through ACP model discovery, route
model services through MLflow chat, and add the optional
`DATABRICKS_MODEL_FILTER` visibility filter.

The resilience follow-up bounds each catalog attempt across headers,
bounded body reads, and JSON parsing; retries transient 499/5xx,
transport/body failures, malformed JSON, and attempt timeouts; and emits
only safe catalog/status diagnostics.


## Validation

The live validation and test gates below were run on the pre-rewrite tip
`1cf6c66e7c5bf9a00c357bda24df48e3beeb310b`. The rewritten tip
`0364cf44f3eb5cfbd5771b0a0dd7868ce0ad2c19` has identical trees for both
commits, so the attribution rewrite changes no tested code.

- 31 catalog tests passed
- 19 Databricks OAuth/routing integration tests passed
- `cargo check -p buzz-agent` passed
- `cargo clippy -p buzz-agent --all-targets -- -D warnings` passed
- `cargo fmt --check` passed
- `git diff --check` passed
- Desktop Tauri validation passed through the repository-supported setup
path, including 2,777 library tests and auxiliary terminal suites

The full `buzz-agent` package has one separately reproduced pre-existing
failure: `regressions::cancel_kills_inflight_tool_via_mcp_notification`,
failing on both baseline `eb8e97cfe` and the follow-up worktree. No
Databricks path reaches that test. The live validation proves discovery
and ACP exposure, not inference against every listed model.

---------

Signed-off-by: Kalvin Chau <kalvin@block.xyz>
Signed-off-by: am <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz>
Co-authored-by: am <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz>
2026-08-27 13:37:12 -07:00
Will Pfleger
a1219070fa feat(buzz-agent): gate LLM tool calls on session/request_permission (#5712)
## Summary

buzz-agent now authorizes every LLM-issued MCP tool call through
`session/request_permission` before it executes, instead of running
tools unconditionally. The agent **always asks**; the client applies
`BUZZ_ACP_PERMISSION_POLICY` and answers. buzz-agent never reads the
policy — this keeps the policy decision on the client side, matching the
layering of the other ACP harnesses, and avoids duplicating policy logic
that would drift.

### Broker

A crate-local `PermissionBroker`
(`crates/buzz-agent/src/permission.rs`), owned by `App` for the
connection lifetime, owns the full request-correlation lifecycle:

- **Process-wide admission.** A global `Semaphore`
(`BUZZ_AGENT_MAX_PENDING_PERMISSIONS`, default 32, validated `>= 1`) is
acquired *before* any correlation entry is inserted. This is the
security bound: the per-turn tool semaphore is constructed fresh per
turn and `max_sessions` is unbounded by default, so neither bounds
simultaneously-outstanding asks process-wide.
- **Abort-safe cleanup.** A successful admission returns a
`PendingPermission` lease that owns the admission permit and the
correlation id. Its `Drop` synchronously removes the still-pending entry
and releases the slot, covering task abort/panic that bypasses the
normal `run_prompt` tail.
- **At-most-once resolution.** `deliver` claims (removes) the entry
*before* waking the waiter, so each id resolves once and a later lease
`Drop` is a no-op. Unknown/late ids are logged and dropped; only ids the
broker minted (`perm-<n>`) are recognized.
- **Single absolute deadline.** Admission, request enqueue, and response
wait all share one deadline (`BUZZ_AGENT_PERMISSION_TIMEOUT_SECS`,
default 330s, validated `>= 1`), so a saturated call cannot outlive one
timeout window even when a stalled writer blocks the enqueue.
Cancellation races inside every wait — resolution never depends on the
outer abort drain. A writer that dies mid-connection (stdout closed, or
a blocking write that only surfaces its error at flush) is
connection-fatal: it cancels all sessions, which resolves any ask
waiting on a reply that can never be written.

### Wire

`request_permission_params` (`crates/buzz-agent/src/wire.rs`) is
version-aware, keyed on the protocol version negotiated at `initialize`
and stored on `App` for the connection lifetime (never derived from a
later mutable session field). v2 nests the tool call under `subject:
{type: "tool_call", toolCall}` with top-level `title`/`options`; v1 uses
the legacy top-level `toolCall`. No hybrid shape. Both offered options
(`allow_once`, `reject_once`) carry `optionId == kind`, so the client's
`kind`-based selection and this side's `optionId`-based predicate agree
without a lookup table.

### Gate

In each spawned tool task (`crates/buzz-agent/src/agent.rs`) the
sequence is: acquire per-turn permit → argument-shape validation →
broker admission + request + wait → cancellation recheck →
`emit_in_progress` → `mcp.call`. Argument-shape validation is hoisted
out of `mcp.rs::do_call` into `validate_arg_shape` so a malformed
non-object argument is rejected locally without prompting for a call
that could never execute.

Authorization is fail-closed, stated once in `evaluate`: execute IFF
`outcome.outcome == "selected"` **and** the selected `optionId` equals
the offered allow option. Every other shape (reject, cancelled, JSON-RPC
error, malformed, unknown outcome, wrong/unknown option, timeout,
wire-channel closure) denies with a synthetic tool error, and the turn
continues.

### Scope

Only LLM-issued MCP calls are gated. The built-in `load_skill` tool and
`call_hooks` lifecycle calls (`_Stop`, `_PostCompact`) are exempt — they
are not model-issued. `readOnlyHint` is never treated as a security
boundary.

First cut ships `allow_once`/`reject_once` only; session-scoped grants
are deliberately out of scope.

### Related issue

Part of #4938. This PR and
[#5106](https://github.com/block/buzz/pull/5106) jointly implement the
feature: #5106 is the client-side policy engine and permission cards;
this PR is buzz-agent's asking side (`session/request_permission`).
Neither closes #4938 alone.

---------

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Hayt <41ea58f1e64c243627e8acde7c89be667052ee6e17d8f021c1195be4324ebf04@buzz.block.builderlab.xyz>
Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
2026-08-25 11:49:07 -04:00
Atish Patel
72d56e7bd3 fix(agent): raise output limit and allow 3 recoveries (#5475)
## Summary

Raise the built-in output and recovery defaults so long-running agents
have more room to finish useful work instead of terminating after
repeated 32,768-token reasoning-only responses.

- Raise `BUZZ_AGENT_MAX_OUTPUT_TOKENS` from 32,768 to 65,536
- Raise the finite output-truncation recovery allowance from 2 to 3 via
`BUZZ_AGENT_MAX_TOKEN_RECOVERIES`; `0` still disables recovery
- Strengthen the recovery prompt so the model stops prolonged reasoning,
uses tools immediately, and builds scripts or artifacts in small
verifiable steps
- Preserve the safety invariant that incomplete truncated tool calls are
discarded and never executed
- Keep proactive handoff independently at 90% of
`BUZZ_AGENT_MAX_CONTEXT_TOKENS` (180,000 tokens with the 200,000
default), regardless of the output allowance
- Add request-loop and configuration regressions for exact-N recovery,
disabled recovery, successful tool-first recovery, discarded truncated
calls, and finite round bounds

`BUZZ_AGENT_MAX_OUTPUT_TOKENS` remains an explicit per-agent deployment
setting. Operators should configure it at or below the served model's
output limit; this PR does not perform live provider capability
discovery or automatic clamping.

**Risk:** Medium — this increases the default request size and permits
one additional recovery attempt by default. Recovery remains finite and
bounded by `BUZZ_AGENT_MAX_ROUNDS`. Deployments whose served model
rejects 65,536 output tokens must set a lower per-agent value.

Current output limits
- model - output token max
- DeepSeek V4 Flash - 384,000 tokens
- Qwen 3.8 (Max) - 131,072 tokens
- GLM 5.2 - 131,072 tokens
- GPT 5.6 - 128,000 tokens
- Claude Opus 5 - 128,000 tokens
- Gemini 3.6 Flash - 65,536 tokens
- Kimi K3 (Moonshot)- 131,072 tokens

### Related issue

None found. Originating benchmark analysis:
`buzz://message?channel=c3252dd2-0142-4e01-88c7-a2183c3960a5&id=91e991aab5fd49094583c3937477f6c12db57a41d86edf7fd4745d0d57d10017`

### Testing

- `cargo fmt --all -- --check`
- `cargo test -p buzz-agent` — 595 passed, 0 failed, 0 ignored at
`bd6de557b367850f50325bafdd3c046131942bef`
- `cargo clippy -p buzz-agent --all-targets -- -D warnings`
- Previously failing
`cancelled_turn_with_usage_emits_notification_before_response` passed
alone and in the full rerun
- Push hooks passed: organization guard, branch skew, Rust tests, and
Desktop Tauri checks

### Update — 2026-08-11

Per review feedback, the recovery default is 3. The OpenRouter live
`/models` output-cap discovery, cache, request clamp, and related
tests/documentation were removed. Per-agent output configuration is now
the sole output-cap mechanism. Proactive handoff and its pre-usage byte
fallback now depend only on 90% of `BUZZ_AGENT_MAX_CONTEXT_TOKENS`; with
the 200,000 default, the handoff threshold is 180,000 regardless of
`BUZZ_AGENT_MAX_OUTPUT_TOKENS`.

Generated with Brainy Bumble


### Targeted validation — 2026-08-11

Ran the exact PR binary once on each of the 11 benchmark tasks causally
affected by the previous 32,768-token ceiling, using OpenRouter with
`deepseek/deepseek-v4-flash-0731` pinned to Fireworks and maximum
reasoning effort. Relay-429 collection failures were excluded and rerun
at concurrency 2.

- **6/11 passed:** `circuit-fibsqrt`, `feal-linear-cryptanalysis`,
`model-extraction-relu-logits`, `path-tracing`,
`schemelike-metacircular-eval`, and `sqlite-db-truncate`
- **5/11 reached the benchmark deadline:** `adaptive-rejection-sampler`,
`dna-assembly`, `path-tracing-reverse`, `regex-chess`, and
`write-compressor`
- `regex-chess` reached exactly 65,536 output tokens, triggered one
output-limit recovery, and then reached the deadline. This directly
confirms that the larger ceiling and recovery path were active, but not
that recovery guarantees completion.

For context, ten of these tasks were 0/5 in the historical baseline;
`sqlite-db-truncate`, the clean control, was 4/5. This is targeted
one-attempt-per-task validation rather than a statistically powered
comparison. The result should not be attributed solely to the recovery
default of 3: this PR also raises the output ceiling and strengthens
recovery behavior, and OpenRouter routing conditions may differ from the
historical direct-Fireworks runs.

Generated with Brainy Bumble

---------

Signed-off-by: Atish Patel <atish@squareup.com>
Signed-off-by: Brainy Bumble <0ed7657b57c0e8a9f5288390dd6c8d5d0a3a06abe9b01b9006814f52077d6cdf@buzz.block.builderlab.xyz>
Signed-off-by: Hardworking Honey <c5c455215c2506cb8ba776518cec804af62d3a0526e32d496a22072e395042b9@buzz.block.builderlab.xyz>
Co-authored-by: Brainy Bumble <0ed7657b57c0e8a9f5288390dd6c8d5d0a3a06abe9b01b9006814f52077d6cdf@buzz.block.builderlab.xyz>
Co-authored-by: Hardworking Honey <c5c455215c2506cb8ba776518cec804af62d3a0526e32d496a22072e395042b9@buzz.block.builderlab.xyz>
2026-08-12 16:57:11 -04:00
Will Pfleger
5e4c05f90b feat(desktop): NIP-AM agent-usage backend — P2 emission/transport/archive + P4a aggregation/D6 (#4000)
## What

Implements Phases 2 and 4a of the Usage v2 plan (plan events
`d0268cd0`/`0e95b035`), extending the archive backend to emit,
transport, archive, and aggregate both cache categories and billing
identity fail-closed.

### P2 — emission, transport, archive

**Tri-state accumulators** (`Unseen`/`Exact`/`Unknown`) for cache-read
and cache-write in `buzz-agent` turn and session state. Absent field =
Unknown (never zero) through the full pipeline. No `unwrap_or(0)` on the
cache path. Both cache folds are gated on usage-bearing responses (same
gate as the total-state and identity folds) — a response with no usage
at all must not poison either accumulator.

**Overflow-aware input token parsing and accumulation** — closed
end-to-end from parse through wire to ACP:
- `sum_usage()` returns `SumUsageResult` (`Exact(u64)` | `Overflow`) —
checked arithmetic, never clamps. `anthropic_input_tokens()` returns
`Option<SumUsageResult>` since it sums three fields (`input_tokens +
cache_read_input_tokens + cache_creation_input_tokens`) that can
collectively overflow. Single-field callers (`prompt_tokens`,
`completion_tokens`, etc.) convert via `.into_exact()` — their
single-field sums cannot overflow.
- `LlmResponse.input_tokens_overflowed: bool` propagates the parse-layer
signal into the run loop. When set, `input_tokens` is `None` (clamped
value discarded), the context-gate baseline
(`last_request_input_tokens`) is frozen at its prior reading, and
`turn_input_tokens` is poisoned to `TurnIOState::Poisoned` before any
emission — including mid-turn `emit_usage_update` calls. A dedicated
enum on `LlmResponse.input_tokens` would ripple into ~20 existing test
assertions on `r.input_tokens == Some(...)`; the bool flag confines the
change to the two call sites that check it.
- `TurnIOState` (`Unseen`/`Exact`/`Poisoned`) for input and output:
per-round fold uses `checked_add`; overflow poisons permanently at turn
and session level, no healing. Absence does not poison (pass-2-cleared
contract unchanged). Wire emission omits
`accumulatedInputTokens`/`accumulatedOutputTokens` when poisoned — never
null, never `u64::MAX`. ACP treats absent = publisher-poisoned:
`delta_reliable: false`, null turn fields, null cumulative for that
category; session cumulative stays unknown for all subsequent turns once
poisoned.

**Conditional wire emission** for `accumulatedCachedInputTokens` and new
`accumulatedCacheWriteTokens`: fields are omitted when the cumulative is
Unseen or Unknown. ACP `_goose/unstable/session/update` contract
documented next to the payload with tests for all absence/zero variants.

**`PricingIdentity` stamping (publisher-side)**:
- `pricing_authority()`: canonical parsed-URL endpoint comparison
against the official allowlist — HTTPS only, exact allowlisted host
(lookalike-safe), default port (omitted or explicit :443), required API
base path, rejects userinfo/query/fragment/path-prefix lookalikes.
- Model: the actually-requested `request_model` after mesh/auto
resolution (not `effective_model_str`).
- Turn discipline: identity retained only while ALL usage in the current
turn carries one identical proven identity; any mismatch,
unproven-usage-bearing response, or unpaired cumulative snapshot poisons
to absent; a later matching notification does not heal a mixed turn.

**ACP `UsageTracker` identity fold**: per-in-flight-turn tri-state
identity accumulator replacing last-update-wins. Any absent identity on
a token-advancing notification or exact mismatch poisons to absent;
poison survives later updates; reset in `begin_turn()`/`take()`; reset
also when a request fails (baseline cleared so preflight gate cannot
stay frozen sub-threshold on retries).

**M3 migration**: adds `turn_cache_write_tokens`,
`cumulative_cache_write_tokens`, `pricing_authority`, `pricing_model`,
`pricing_cache_class` to `agent_metric_index`. Additive, idempotent,
guarded per-column by marker. M2 migration also guarded per-column (turn
and cumulative cache-read columns checked and added independently;
marker commits only after both are present). Fresh-DB schema includes
all columns.

**First-turn baselines**: `seed_zero_baseline` seeds `last_input:
Some(0)`, `last_output: Some(0)`, `last_cached_input: Some(0)`,
`last_cache_write: Some(0)`, and `last_total: Some(0)` — all have the
known-zero-at-spawn argument. Absent fields from incoming snapshots
still produce unknown (tri-state unchanged). Sessions buzz-acp did not
spawn (no seed) remain fail-closed on turn one.

**`ReportedUsage` TS mirror**: `cacheReadTokens`, `cacheWriteTokens`,
`freshInputTokens` added to `tauriArchive.ts` as `UsageField` members,
field-for-field with the Rust struct.

### P4a — aggregation layer

**Extended S-1 ladder** to cache-read and cache-write via the same
`ladder_token` path as the existing token fields.

**`freshInputTokens` derivation**: checked arithmetic, fail-closed —
absent cache fields produce Unknown (not zero), overflow and
`cacheRead+cacheWrite > input` both produce `incomplete: true`.
Aggregated as a `UsageField`.

**D6 comparator**: `sort_value()` = provider total when known, else
`input+output` when both known, else `None` (unknown-last). Replaces the
prior total-only comparator for both agent-level and model-level sort.
Ships a pinned test vector that the TS render layer (P5) must match.

## Test coverage

- `buzz-agent`: 440 lib + 15 integration (golden_transcripts) — includes
13 new `cache_total_state_tests`; 14 new `turn_io_state_tests`; 3 new
`sum_usage_*` tests (exact single-field, exact two-field, overflow
signals correctly); 3 new `parse_anthropic_*` tests (overflow flag set +
value cleared, normal sum no flag, absent usage no flag); end-to-end
golden transcript drives real subprocess with Anthropic-shaped
`input_tokens: u64::MAX, cache_read: 1` response and asserts
`accumulatedInputTokens` absent from the emitted `usage_update` — no
logic duplication; 3 wire pin tests; 4 `fold_pricing_identity_*` tests;
`pricing_authority()` explicit-:443 acceptance
- `buzz-acp`: 700 tests (691 lib + 9 integration) — 4 new usage tests
(absent input → unreliable+null; absent output → unreliable+null;
goose-shaped both present unchanged; poison mid-session); 3 ACP behavior
tests; 7 pool lifecycle tests
- Desktop (Rust): 2259+ tests — 14 new P4a pinned tests; 2 M3 round-trip
tests; 1 serde key-shape test; 2 M2 partial-schema migration tests;
first-turn cache round-trip test

## Related PRs

- P1 NIP-AM spec: [#4632](https://github.com/block/buzz/pull/4632)
- P3 pricing table: [#4629](https://github.com/block/buzz/pull/4629)
- UI (P5): [#4001](https://github.com/block/buzz/pull/4001)

---------

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
2026-08-10 10:47:37 -04:00
Tyler
2b873cf208 Recover from max-token response truncation (#5223)
## Summary

- treat provider `max_tokens` as an interrupted assistant response and
continue the same turn with actionable feedback
- discard tool calls from truncated responses, including malformed
partial arguments, so they are neither executed nor replayed with
invalid tool-result pairing
- bound recovery to two retries while preserving normal finite
`max_rounds` accounting

## Verification

- `cargo fmt --all -- --check`
- `cargo test -p buzz-agent` (422 unit tests plus all package
integration/doc suites passed)
- `cargo clippy -p buzz-agent --all-targets -- -D warnings`

## Notes

The pre-push repository-wide hook also ran. Its Rust tests passed (2,270
passed, 14 ignored), but its `buzz-db` unit-test build was blocked
because local rustc 1.89 is below sqlx 0.9's rustc 1.94 requirement. The
affected package suite above is green on the exact pushed commit.

Originating Buzz channel: `c3252dd2-0142-4e01-88c7-a2183c3960a5`

Signed-off-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>
Co-authored-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>
2026-08-07 15:56:48 -04:00
Will Pfleger
742e8d1197 fix(buzz-agent): Responses reasoning summary, Anthropic display:summarized, ACP v2 messageId (#5195)
Three pre-existing gaps in the buzz-agent observer feed fixed together
per Will's ruling ("all 3 in the current PR"):

1. **OpenAI/DBv2-GPT route** — `responses_body` never requested
`reasoning.summary`; GPT-family models billed thinking tokens but
returned `summary: []`.
2. **Anthropic/DBv2-Claude route** — `anthropic_thinking_config()` never
sent `thinking.display`; newest Claude models (Opus 5, Sonnet 5, Fable
5, Mythos 5, Opus 4.7/4.8, Mythos Preview) default to
`display:"omitted"`, returning thinking blocks with an empty `thinking`
field — observer rendered nothing.
3. **ACP v2 compliance** — buzz-agent negotiates ACP v2 but emitted
`agent_thought_chunk` and `agent_message_chunk` without `messageId`,
which ACP v2's `ContentChunk` requires (`messageId` + `content` both
required at schema head `d13d1baa`).

## Changes

**`crates/buzz-agent/src/config.rs`**
- New `ThinkingSummary` enum (`Auto`/`Concise`/`Detailed`) with
`BUZZ_AGENT_THINKING_SUMMARY` env var (default `Auto`); mirrors
`BUZZ_AGENT_THINKING_EFFORT` pattern
- `anthropic_thinking_config()` now emits `"display": "summarized"` in
both the adaptive shape and the manual-budget shape whenever thinking is
enabled
- Rewrote `is_adaptive_thinking_model` and `anthropic_thinking_config`
doc comments to match Anthropic's exact three-way per-model terminology
(doc:
https://platform.claude.com/docs/en/build-with-claude/thinking-troubleshooting#supported-models):
- Opus 4.6/4.7/4.8, Sonnet 4.6: **Off** — thinking OFF by default;
`type:"adaptive"` required to enable
- Opus 5, Sonnet 5: **On** — thinking on by default, can be disabled; we
still send `type:"adaptive"` to activate `output_config.effort`
- Fable 5, Mythos 5, Mythos Preview: **Always on** — thinking cannot be
disabled; we still send `type:"adaptive"` to activate
`output_config.effort`

**`crates/buzz-agent/src/llm.rs`**
- `responses_body` emits `reasoning.summary` alongside
`reasoning.effort` when effort is set (gated — no bare
`reasoning:{summary}` without effort)
- Covers both the pure-OpenAI Responses path and the DBv2 GPT-family
Responses path

**`crates/buzz-agent/src/agent.rs`**
- `agent_thought_chunk` carries `"messageId":
format!("{run_id}-thought-{round}")`
- `agent_message_chunk` carries `"messageId":
format!("{run_id}-message-{round}")`
- The two IDs are distinct (thought and assistant are two logical
messages per the ACP v2 Message ID RFD)
- `run_id` is a fresh random token per `session/prompt` invocation so
IDs are session-unique across multiple prompts

**`crates/buzz-agent/src/lib.rs`**
- `run_id` plumbed into `RunCtx` (was already generated in `run_prompt`,
just not threaded through)

**`crates/buzz-agent/tests/golden_transcripts.rs`**
- `test_acp_v2_chunks_carry_message_id` — negotiates v2, drives two
consecutive `session/prompt` calls, asserts: both chunk types carry
non-empty `messageId`; thought and message IDs are **distinct**; IDs do
**not** recur across the two prompts in the same ACP session

**`desktop/src-tauri/src/managed_agents/env_vars.rs`**
- `BUZZ_AGENT_THINKING_SUMMARY` added to `is_safe_to_reveal` allowlist

**`desktop/src-tauri/src/commands/agent_config_tests.rs`**
- Tests for `BUZZ_AGENT_THINKING_SUMMARY` allowlist entry
(case-insensitive)

## Tests added

- `parse_thinking_summary_round_trips_all_values`
- `parse_thinking_summary_unset_and_empty_yield_auto`
- `parse_thinking_summary_is_case_insensitive`
- `parse_thinking_summary_rejects_unknown_value`
- `thinking_summary_as_str_mapping`
- `responses_body_summary_present_iff_effort_set`
- `responses_body_emits_configured_summary_mode`
- `responses_body_concise_summary_mode`
- `anthropic_thinking_config_adaptive_emits_display_summarized`
- `anthropic_thinking_config_manual_budget_emits_display_summarized`
- `test_acp_v2_chunks_carry_message_id` (integration test — two-prompt
cross-session case)

## Notes

- **DBv2 gateway parity for `display`**: unverified — the DBv2 Claude
route proxies Anthropic Messages shape, but whether the gateway passes
`thinking.display` through is not confirmed. Flagged here rather than
blocking on it.
- buzz-acp and Desktop TS are unchanged — they already parse `messageId`
as optional and will pick it up from the wire automatically.
- Chat Completions and OpenRouter paths: untouched.

---------

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
2026-08-07 13:45:43 -04:00
Will Pfleger
ed4b3e7afa fix(buzz-agent): recover from context-window 400s instead of sticking (#4946)
Provider `context_length_exceeded` 400s permanently wedged agent
sessions: the turn errored, the oversized history persisted in the
in-memory session, and the usage baseline stayed frozen at the last
successful sub-threshold reading (failed requests report no usage), so
the preflight handoff gate never fired again — every later prompt failed
identically until an agent restart. The byte-truncation fallback never
intervened because it is a request-body limiter (`estimated_bytes`), not
a context-window defence; at context-window scale it is a measured
no-op.

This adds the reactive recovery path:

- **Typed classification.** `AgentError::LlmContextExceeded` is
classified at both non-success provider terminals — the shared `post()`
(Anthropic, OpenAI, Databricks) and `openrouter_post()` — on `status ==
400` plus a context-window body match, so ordinary 400s stay terminal.
- **Forced handoff.** A context-400 forces a summarize-handoff that
bypasses `should_handoff()` and `BUZZ_AGENT_MAX_HANDOFFS`, bounded by
its own per-turn budget (`MAX_CONTEXT_RECOVERIES_PER_RUN = 3`).
- **Shrink ladder.** The summarize prompt budget halves from the
observed rejected history size — not from `max_context_tokens`, the
number the provider just contradicted — rung to rung, with a 4096-byte
floor. A summarize call rejected for the same reason takes the next rung
instead of re-sticking. At the floor (overflow dominated by unshrinkable
frame: system prompt, tool schemas, live prompt) recovery is refused and
the provider error surfaces clearly instead of self-healing.
- **Baseline reset.** The stale usage baseline is cleared when a request
fails, so the preflight gate cannot stay frozen sub-threshold on
retries.

Named behavior changes:

1. **Anthropic and OpenRouter errors now carry the `(model)` stamp.**
Provider arms return their `Result` into the central error mapper
instead of early-returning past it, making the code match its documented
single-convergence contract at that mapper.
2. **`max_rounds` now counts completions the loop acts on.** A request
rejected with a context-400 that is then successfully recovered refunds
its round before the retry, paired 1:1 with a consumed recovery rung, so
the round cap is neither weakened nor able to drop a recovered turn
unanswered.

Related: #4805 — the complementary proactive fix (per-session
handoff-cap kill switch that let sessions grow to the provider wall).
#4805 prevents reaching the wall; this PR recovers at it.

---------

Co-authored-by: npub17jjz49l9jjmhhk7cac63j8yt9z555n9cw8vk7v5jz4vzw4ppld5qgj57cc <f4a42a97e594b77bdbd8ee35191c8b28a94a4cb871d96f32921558275421fb68@buzz.block.builderlab.xyz>
Co-authored-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
2026-08-05 16:58:25 -04:00
Will Pfleger
6df7eba24d fix(buzz-agent): scope handoff cap per turn, not per session lifetime (#4805)
`BUZZ_AGENT_MAX_HANDOFFS` compared against the session-cumulative
`handoff_count` (persisted across prompts). After N handoffs a
long-lived session hit the cap permanently: `maybe_handoff()` returned
`Skipped` on every subsequent prompt, the 16 MiB byte-truncation
fallback never bound before a 1M-token provider wall, and the session
wedged on the first 400 with no recovery path. Thufir's session log
shows 8 days of cap-forced truncation before the first
`context_length_exceeded` 400.

The fix replaces the session-level cap comparison with a local
`handoff_attempts` counter constructed at the start of `run()` and
passed into `maybe_handoff()`. The counter resets on every
`session/prompt` turn so `BUZZ_AGENT_MAX_HANDOFFS` caps compaction loops
within a single turn while allowing unbounded compactions across a
session's lifetime. The session-cumulative `handoff_count` is retained
for log context only and is not reset. Steer-driven rounds share the
per-turn budget automatically since steers inject into the running
`run()` loop, not a new call.

- Move `handoff_attempts` increment to before `summarize()` so failed,
empty, and cancelled summarize calls each consume one budget slot — the
cap cannot be bypassed by a repeatedly-failing summarizer
- Upgrade cap-forced `Skipped` from `INFO` to `WARN`; add structured
fields for `session_id`, attempt count, projected tokens, and threshold
so the cap→wall pairing is attributable per session
- Document `max_handoffs` in `config.rs` as a per-`session/prompt`-turn
bound
- Three new behavioral regression tests: per-turn reset proven across
two separate turns; within-turn cap proven via multi-round tool-call
turn; failed summarize proven to burn the attempt budget

Note: this is the proactive half of the context-window fix. The reactive
`context_length_exceeded` 400 recovery path is owned by Sami's branch
(`buzz-ctxfix-sami`, Tyler's crew); this PR is intended to land after
that one.

---------

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
2026-08-05 15:30:04 -04:00
Tyler
8a7eb8d3d7 fix(agent): recover from unsupported image input instead of poisoning the turn (#4896)
## Problem

`buzz-dev-mcp` advertises `view_image` to every agent regardless of
whether the session's model accepts images. When a text-only model (e.g.
DeepSeek V4 Flash) takes the bait, the image lands in session history
and every subsequent LLM request 404s with `No endpoints found that
support image input`. The error was classified as `LlmModelNotFound` and
propagated fatally out of the turn loop — history stays poisoned,
buzz-acp retries the batch with exponential backoff, and the session
burns its entire clock doing no work. In a recent trial run, **all 57
trials that called `view_image` on a text-only model died this way; none
recovered.**

## Fix

Capability-gating the advertised tool isn't reliable — there is no
image-capability metadata at the agent layer across providers. Instead,
recover at the turn loop:

- **Typed error**: new `AgentError::UnsupportedImageInput`, classified
narrowly on the exact provider phrase `No endpoints found that support
image input` on both the generic 404 path and OpenRouter's 404 path.
Unknown-model 404s and OpenRouter parameter-routing 404s keep their
existing classifications. No deterministic retry.
- **In-turn recovery**: on this error, `RunCtx::run` strips every image
block from history — keeping the tool result (and therefore
tool-call/result pairing) intact — marks the result `is_error`, appends
actionable model-facing guidance ("The current model does not support
image input. The image was removed from conversation history so this
turn can continue. Use a text-based inspection tool…"), and continues
the same turn. Base64 never replays again.
- **Loop guard**: recovery only fires when at least one image was
removed; if the provider says "image" and history has none, the error
propagates as before.

## Tests

- Unit: phrase classification (typed, not retried; unknown-model 404
unaffected), idempotent image-to-error history mutation preserving call
IDs and text.
- End-to-end (`fake_llm.rs` + `fake_mcp.rs`): tool call → MCP image
result → 404 unsupported-image → same-turn recovery. Captured requests
prove round 2 carried the image, round 3 replays no image, carries the
guidance text, preserves pairing, and ends `end_turn`.
- Loop guard: typed unsupported-image error with **no** image in history
fails after exactly one provider request instead of spinning —
mutation-testing showed deleting the `removed == 0` guard survived the
suite, and `max_rounds` defaults to unlimited in production, so this
branch needed direct coverage.

Verified at `a210305019b33d5f56677b4c82bab79e4ac52d24`: `cargo test -p
buzz-agent` (full package, 381 unit + all integration suites) green;
`clippy --all-targets -D warnings` green; `fmt --check` green; pre-push
hooks (rust-tests, desktop-tauri-checks, branch-skew) green.

**Scope of the classification guarantee**: the classifier runs in the
shared `post()` (which Anthropic and OpenAI paths route through) and in
`openrouter_post()` — i.e., every 404 path in `llm.rs`. It only runs on
404 responses; providers that reject images with a different status
(e.g. a 400) are out of scope for this PR — see the review-comment
discussion for why broadening the phrase list alone would not cover
them.

Authored by Wren, loop-guard test by Sami, reviewed by Eva.

---------

Signed-off-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>
Signed-off-by: Sami <f4a42a97e594b77bdbd8ee35191c8b28a94a4cb871d96f32921558275421fb68@buzz.block.builderlab.xyz>
Co-authored-by: Wren <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>
Co-authored-by: Sami <f4a42a97e594b77bdbd8ee35191c8b28a94a4cb871d96f32921558275421fb68@buzz.block.builderlab.xyz>
2026-08-05 11:51:05 -04:00
Kalvin C
4a2305170e fix: reauthenticate databricks model discovery (#4008)
## Summary
- preserve Databricks catalog 401 responses as authentication failures
and retry discovery exactly once after silently refreshing the rejected
bearer
- preserve runtime OAuth recovery: when discovery has no usable OAuth
credential, `session/new` succeeds with only the trimmed configured
model so the first `session/prompt` can run the existing browser PKCE
flow
- reject a rejected configured `DATABRICKS_TOKEN` with actionable,
non-interactive guidance; static credentials cannot recover through PKCE
- use the configured-model fallback for non-auth discovery failures
without caching failed or fallback catalogs, so later sessions retry
discovery
- keep known Databricks v2 models only for authenticated empty-catalog
responses and mark their provenance
- resolve discovery before MCP spawn or session registration, preventing
failed discovery from leaking resources or consuming session capacity
- permit serialized interactive PKCE only from the explicit saved-agent
model picker; passive draft discovery never opens a browser

## Runtime flow
1. OAuth discovery attempts cached credentials and silent refresh
without opening a browser.
2. If no usable OAuth bearer exists, `session/new` advertises only the
configured model and succeeds.
3. The first `session/prompt` uses `TokenSource::bearer()`, which may
launch browser PKCE.
4. A later session retries discovery and caches only the authenticated
catalog.

## Regression coverage
- rejected-but-locally-fresh OAuth bearer performs one refresh and one
catalog retry
- OAuth mode with no cached token allows `session/new` and returns
exactly the trimmed configured model
- the OAuth fallback is not cached; a later authenticated session
retries discovery and caches the returned catalog
- rejected static tokens still reject `session/new`
- failed discovery does not consume the sole session slot or spawn the
supplied MCP process
- Desktop interactive/passive auth intent, static-token redaction, and
authenticated empty-catalog provenance

## Verification
- `cargo test -p buzz-agent`
- `cargo test --manifest-path desktop/src-tauri/Cargo.toml --lib
commands::agent_models`
- `cargo clippy --manifest-path desktop/src-tauri/Cargo.toml
--all-targets -- -D warnings`
- `cargo fmt --all -- --check`
- `git diff --check`
- full pre-push hooks

## Review
Adversarial review found and drove fixes for session/MCP resource
leakage, duplicate concurrent PKCE flows, sensitive error propagation,
incorrect 403 reauthentication, missing discovery-level coverage,
passive browser launch, and the Desktop file-size ratchet. The final
follow-up preserves the existing prompt-time OAuth flow while retaining
static-token rejection and pre-allocation discovery ordering.

---------

Signed-off-by: npub1dccv64krpcpse5cmkzfeh998cftungyatw3djt8jwdw6g43f7fyqzzmrf7 <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz>
Co-authored-by: npub1dccv64krpcpse5cmkzfeh998cftungyatw3djt8jwdw6g43f7fyqzzmrf7 <6e30cd56c30e030cd31bb0939b94a7c257c9a09d5ba2d92cf2735da45629f248@buzz.block.builderlab.xyz>
2026-08-04 15:20:49 -07:00
Atish Patel
09c86c56e5 fix: report agent usage per provider round, not once per turn (#4545)
## The bug

buzz-agent emitted its `usage_update` notification in exactly one place:
after `ctx.run()` returned. Until that moment a turn's token counters
lived only in the prompt task's stack frame. **A turn killed mid-flight
reported nothing at all** — the provider had already billed every round
it completed, and no consumer ever saw any of it.

That is not a corner case for anything that ends a turn on a clock. It
is the normal case for a long-horizon benchmark run that relaunches its
agent between phases.

## How big

Measured against a provider's own billing ledger over one run's window:

| | provider ledger | what we recorded |
|---|---|---|
| the relaunched lead seat | $485 / 348M tok | $98.99 / 90.3M tok |
| the two seats that were not relaunched | $29.90 / 856M | $25.81 / 765M
— reconciles |

97% of that run's usage rows came back all zeros, against 1–4% for
comparable runs that never relaunch. In one 450-phase trial exactly 7
phases recorded any usage — and each of those carries 177k–437k input
tokens, a whole session's worth landing in the one phase that happened
to end gracefully.

Worth being precise about what was *not* wrong, since both were
plausible and both were checked:

- **Not pricing.** The rates were verified against the provider's
endpoints API and match what we charge.
- **Not a truncation bug.** The usage files were intact and internally
consistent. The tokens were never captured in the first place.

## The fix

The run loop now emits a session-cumulative `usage_update` after every
usage-bearing provider response, so an interrupted turn has reported
everything but its single in-flight request.

- **Emitting more than once per turn is already part of the contract.**
buzz-acp's `UsageTracker` advances its committed baseline only at
publish time, and goose behaves the same way — which is why the tracker
was written to tolerate it.
- **The turn-start session baseline is snapshotted into `RunCtx`** so
the mid-turn figure stays *session*-cumulative. A turn-local number
would be discarded by a high-water-mark consumer and lose the turn
entirely; there is a test for exactly that.
- **Snapshot by value, not a session handle.** The loop reports once per
round, and taking the sessions lock on each would serialise concurrent
sessions behind one another's provider round-trips. Nothing else
advances those counters while the turn holds `busy`, so it cannot go
stale.
- **One shared `wire::usage_update_payload`** for both call sites, so
the mid-turn and end-of-turn shapes cannot drift. A drift there would
present as tokens silently vanishing, which is the failure this
reporting exists to prevent.

## Why not a SIGTERM handler

That was the obvious shape and it does not work. At signal time the
counters are not sitting anywhere a handler could reach — they are in
the turn's stack frame, and the value the handler would need has not
been folded into the session yet. Making usage durable *during* the turn
is what actually fixes it; once it is, a handler adds nothing beyond the
in-flight request, whose cost is unknown until its response lands.

## Tests

- `usage_is_reported_after_each_round_not_only_at_turn_end` — two
rounds; asserts the **first** notification carries round 1's counts
alone, proving it went out before round 2 returned.
- `mid_turn_usage_includes_earlier_turns` — a mid-turn report must be
session-cumulative, not turn-local.

buzz-agent 18/18 on the `fake_llm` suite, 382 unit. `cargo fmt` /
`clippy` / `cargo check --workspace --all-targets` clean.

## Scope

Agent-side only, against `main`. The matching harness change — settling
usage on the timeout path, which was skipped on the reasoning that an
incomplete turn has nothing to flush — is **#4553**, against the
benchmark branch, since that harness does not exist on `main`.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Signed-off-by: Atish Patel <atish@squareup.com>
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-08-03 12:42:42 -04:00
Tyler
081f805d5e feat(agent): optional reply guard reminds a silent turn to publish (#3763)
## Why

A Buzz agent's assistant text and reasoning are never shown to anyone —
only what it posts through the CLI. A turn that runs fifteen tool calls
and never publishes is a silent failure: the requester waits on a result
that was produced and thrown away.

This adds an optional reminder at the end-of-turn gate, off by default.

Tyler asked for it in buzz-mesh; plan iterated to **9.5/10 with @Wren**
(Minimalness 9.7, Elegance 9.5, Correctness 9.3).

## What

`BUZZ_AGENT_REQUIRE_REPLY=1` (default off, per-agent opt-in). A turn
about to end with no recognized attempt to post gets a reminder and is
rerolled. **At most two, then the turn ends regardless** — the guard
catches accidental omission, it does not compel speech. The reminder
text explicitly licenses silence so it cannot fight the base prompt's
"silence is usually correct."

**This is not a new MCP hook.** `RunCtx::run` *is* the turn, so the two
per-turn locals need no plumbing, and every tool call already passes
through it with arguments visible. The objection is appended at the
existing `_Stop` gate and rides `push_hook_outputs_as_tool_results`, so
the model receives it as a lower-trust tool result with `{hook, server,
text}` attribution. No new trust path, no new lifecycle event, no
dev-mcp or CLI protocol change.

Earlier revisions of this plan needed four crates (a `_UserPromptSubmit`
hook, a marker file, a `buzz-cli` change, dev-mcp state). Tyler pointed
out the agent already knows both facts; that deleted all of it. Net
runtime change is ~35 lines in `agent.rs` + ~4 in `config.rs`.

### Recognition contract

A registered non-hook tool whose qualified name ends in `__shell`, whose
`command` argument contains `messages send` or `reactions add`.

- **The `__` separator is exact, not approximate.** Given `has()` +
`!is_hook()`, `ends_with("__shell")` is *provably equivalent* to a bare
name of `shell`: registration forbids `__` in server and bare names
(`mcp.rs:227,268`) and qnames are `{server}__{bare}`, so a trailing
`__shell` could only straddle the separator if the bare name began with
`_` — which `is_hook` excludes. Without the separator, `powershell` and
`noshell` would match.
- **Reads the structured `command` field**, not serialized arguments, so
a `description` that quotes a send cannot disarm the guard, and a
non-string `command` is rejected rather than coerced.
- **Detects an attempt, not a successful publish.** A failed send
already returns non-zero exit and error JSON — louder than this
reminder. The variable is named `buzz_reply_call_seen` so the code can't
pretend otherwise.
- **Checked after the per-turn tool-call cap**, since a discarded call
never ran.
- `messages send` also covers `messages send-diff`. Reactions count
because the base prompt directs agents to react rather than post a bare
acknowledgement.

**Known limits, both deliberate and documented:** a command assembled at
runtime (`$CMD`) or hidden in a wrapper script is missed; text that
merely quotes a send (`echo "buzz messages send"`) matches. Missing a
real post is the expensive direction and substring matching is the
forgiving one there. Neither edge is pinned by a test, so the matcher
stays free to improve.

### Budget

Reminders share `BUZZ_AGENT_STOP_MAX_REJECTIONS`, the existing outer cap
on every end-turn objection. Default 3 fits both; at 1 only one fits; at
0 the guard is off with the hooks. A round carrying both a hook
objection and a reminder costs one rejection and delivers both texts. An
independent budget would either violate that bound or need a second
arbitration rule.

## Prior art

- **#3467** (closed) built the same detector one layer up in `buzz-acp`
for a different remedy. None of its symbols are on main — this borrows
its permission to be coarse, but reads structured data that ACP didn't
have.
- **#3648** (open) detects turns with *no output at all*; a turn with
fifteen tool calls and no post counts as output there, so it does not
cover this case.
- **#3741** (merged) is mesh-only.

## Testing

**14 new tests.** 4 unit tests on the matcher; 10 integration tests
through the ACP wire harness: off by default, `=0` still off, opted-in
silent → exactly 2 reminders then `end_turn`, registered `fake__shell`
send → 0 reminders, hallucinated `fake__shell` → still reminded, publish
call truncated past the 64-call cap → still reminded, budget 1 → 1
reminder, budget 0 → off, combined `_Stop` hook objection + reminder →
one round both texts and after 2 reminders the hook objection continues
alone, unparseable `=true` → startup error naming the key.

**10 mutation checks, each breaking a specific named test** — neutralize
the nag cap, stop sharing the budget, neutralize `buzz_reply_call_seen`,
drop `has`/`is_hook`, ignore the flag, drop the `__`, drop `reactions
add`, read serialized args, move detection before truncation.

`tests/bin/fake_mcp.rs` gains `FAKE_MCP_SHELL_TOOL=1`: it previously
exposed no tool with a bare name of `shell`, so the satisfied-guard path
was untestable.

Full `cargo test -p buzz-agent` green at 9e0ae1f04; clippy `-D warnings`
and `cargo fmt --check` clean.

**Unrelated flake found:**
`cancelled_turn_with_usage_emits_notification_before_response`
(`tests/fake_llm.rs`) is timing-sensitive. Under 10 loaded cores it
fails **2/20 on this branch and 1/20 at unmodified
`origin/main@02be413b8`** — pre-existing, not caused by this change
(which is inert without the env var). Flagging so it isn't misattributed
to the next PR that's open when CI hits it.

## Docs

`crates/buzz-agent/README.md` is the primary home (env var, recognition
contract, limits, budget interaction). `docs/MCP_DRIVEN_HOOKS.md` gets a
short cross-reference explaining this is *not* a hook — otherwise
readers hunt for a `_ReplyGuard` tool that doesn't exist.

---------

Signed-off-by: tlongwell-block <109685178+tlongwell-block@users.noreply.github.com>
Signed-off-by: npub1mprnacetjua2xx3p5eddmhxyk6wv929ymm5py8kd2xfxurxahspqqlgyta <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@buzz.block.builderlab.xyz>
Co-authored-by: Dawn (sprout agent) <c6237ef84fa537c78dcee78efd2d4e59f728859c7f194da42ac51ededfa0be05@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: npub1mprnacetjua2xx3p5eddmhxyk6wv929ymm5py8kd2xfxurxahspqqlgyta <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@buzz.block.builderlab.xyz>
2026-07-31 07:02:48 -04:00
Will Pfleger
f95fdc1a10 feat(agent,acp): wire provider total_tokens through NIP-AM publish chain (#3593)
## What

Wires genuine provider-reported `total_tokens` through the full
buzz-agent → buzz-acp publish chain so kind-44200 events carry real
per-turn and cumulative totals for OpenAI-backed models, while
preserving all existing behaviour for Anthropic and external harnesses
(goose, claude-code).

## Why

Live prod data showed 0 of 1,934 archived reports carry `totalTokens`.
Both hardcoded `total_tokens: None` in `pool.rs` and the absent field in
`buzz-agent`'s parser are root causes. This is the backend half of a
two-track fix; the display-fallback half lands in
[#2035](https://github.com/block/buzz/pull/2035).

## Changes

**`crates/buzz-agent/src/types.rs`**
- Added `total_tokens: Option<u64>` to `LlmResponse` with an explicit
doc comment that NIP-AM forbids deriving it.
- Added `TurnTotalState` enum (`Unseen | Exact(u64) | Unknown`) with
`fold()` and `exact_value()` — the tri-state accumulator that
distinguishes not-yet-observed from permanently poisoned.

**`crates/buzz-agent/src/llm.rs`**
- `parse_responses` and `parse_openai`: read `usage.total_tokens` from
OpenAI Chat Completions (including Databricks routes) and the Responses
API via `sum_usage`.
- Anthropic: explicit `total_tokens: None` — no genuine total available;
NIP-AM forbids summing categories.

**`crates/buzz-agent/src/agent.rs`**
- Added `turn_total_state: &'a mut TurnTotalState` to `RunCtx`.
- Fold `response.total_tokens` into the accumulator after each
usage-bearing response; non-usage-bearing responses (keepalive/stream
frames) do not poison.

**`crates/buzz-agent/src/lib.rs`**
- Added `accumulated_total_state: TurnTotalState` to `Session` (default
`Unseen`).
- Per-turn state passed to `RunCtx`, folded into session cumulative
after each turn.
- Emits `accumulatedTotalTokens` in `usage_update` only when cumulative
is `Exact(n)`.

**`crates/buzz-acp/src/usage.rs`**
- Added `accumulated_total_tokens: Option<u64>` (serde default) to
`UsageUpdatePayload` — optional for goose compat.
- Added `last_total: Option<u64>` to `SessionState`.
- Added `turn_total_tokens` and `cumulative_total_tokens` to `TurnUsage`
(field-local — never affect `delta_reliable`).
- Derive turn-total delta only when prev and current are both `Some` and
monotonic; absence, decrease, or no baseline leaves only the total delta
null without touching input/output reliability.

**`crates/buzz-acp/src/pool.rs`**
- Replaced both hardcoded `total_tokens: None` in
`publish_agent_turn_metric` with `usage.turn_total_tokens` and
`usage.cumulative_total_tokens`.

## Tests

20 new tests across the four touched files:

| File | Tests |
|------|-------|
| `types.rs` | `TurnTotalState` fold, accumulation, exact_value, default
(7 tests) |
| `llm.rs` | Chat present/absent, Responses present/absent, Anthropic
always-None (5 tests) |
| `usage.rs` | First turn no baseline, second-turn delta, cumulative
decrease (field-local), current absent, goose-shaped deserialization,
baseline absent (6 tests) |
| `pool.rs` | Exact turn+cumulative mapping, null totals never derived
(2 tests) |

`cargo test -p buzz-acp -p buzz-agent` — all passing, 0 failures.

## Scope

Boundary: `crates/buzz-agent/**` + `crates/buzz-acp/**` only. Desktop
unchanged.
`costUsd` explicitly out of scope.

Related: [#2035](https://github.com/block/buzz/pull/2035)

---------

Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1g8493u0xfsjrvflg4n08ezd7vec99mnwzlv0qgwpr9d7gvjwhuzqx59rhw <41ea58f1e64c243627e8acde7c89be667052ee6e17d8f021c1195be4324ebf04@buzz.block.builderlab.xyz>
2026-07-29 18:15:30 -04:00
thomaspblock
7f769b2439 fix(desktop): align inbox layout and draft details (#2143)
Approved
2026-07-20 00:53:53 +02:00
Tyler
ca8324d2cf Log handoff token counts (#1954)
Signed-off-by: npub12gtutshhh76rx0jx697f32f9tffd4hhp3hx58fp4x6u4uemkm7sqf8f757 <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: npub12gtutshhh76rx0jx697f32f9tffd4hhp3hx58fp4x6u4uemkm7sqf8f757 <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@sprout-oss.stage.blox.sqprod.co>
2026-07-16 11:59:14 -04:00
Will Pfleger
71265ca361 feat(nips,relay,acp): NIP-AM durable encrypted agent turn metrics (kind 44200) (#1441)
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: npub1fgdl5qqnh3k3f2xkqrvt7cujalhm623x4s7fdjdj5yrtp5fzjl9qrjpucw <4a1bfa0013bc6d14a8d600d8bf6392efefbd2a26ac3c96c9b2a106b0d12297ca@sprout-oss.stage.blox.sqprod.co>
2026-07-06 16:35:10 +00:00
Wes
4a09510429 fix(agent): make stop-hook rejection budget per-prompt, fix stale hook docs (#1503)
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Brain <21994759fc7a6fa6b965551d35cfd7897d262f2495467f2d78694ddcfa6a5c7e@sprout-oss.stage.blox.sqprod.co>
2026-07-03 09:21:18 -07:00
Wes
1c297d2f26 fix(agent): honor stop hook retry budget (#1501)
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Pinky <44b8e82baa6e0e254e0208d68f335c283c94e7b78dd1fa10d5a49d3f13dd0435@sprout-oss.stage.blox.sqprod.co>
2026-07-03 15:50:22 +00:00
Will Pfleger
3e282a2418 feat(buzz-agent): config parity — thinking effort, model switching, normalized token limits (#1470)
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@sprout-oss.stage.blox.sqprod.co>
2026-07-02 22:52:38 +00:00
Will Pfleger
9f2a11b338 feat(buzz-agent): emit agent_thought_chunk for reasoning content (#1436)
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@sprout-oss.stage.blox.sqprod.co>
2026-07-01 14:57:05 -04:00
Kalvin C
15a73aa27f feat(buzz-agent): add databricks_v2 provider for AI Gateway v2 (#1311) 2026-06-29 15:46:02 -07:00
Tyler
e567491a19 feat(buzz-acp): steering as the default mid-turn mention delivery (#1160)
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
Co-authored-by: npub17jjz49l9jjmhhk7cac63j8yt9z555n9cw8vk7v5jz4vzw4ppld5qgj57cc <f4a42a97e594b77bdbd8ee35191c8b28a94a4cb871d96f32921558275421fb68@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1fgdl5qqnh3k3f2xkqrvt7cujalhm623x4s7fdjdj5yrtp5fzjl9qrjpucw <4a1bfa0013bc6d14a8d600d8bf6392efefbd2a26ac3c96c9b2a106b0d12297ca@sprout-oss.stage.blox.sqprod.co>
2026-06-29 13:18:56 -04:00
Will Pfleger
8717ddf2eb fix(buzz-agent): follow symlinks when discovering skill directories (#1295)
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1fgdl5qqnh3k3f2xkqrvt7cujalhm623x4s7fdjdj5yrtp5fzjl9qrjpucw <4a1bfa0013bc6d14a8d600d8bf6392efefbd2a26ac3c96c9b2a106b0d12297ca@sprout-oss.stage.blox.sqprod.co>
2026-06-25 16:44:10 -04:00
Will Pfleger
25396c06d6 feat(buzz-agent): lazy skill loading via load_skill tool (#1283)
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@sprout-oss.stage.blox.sqprod.co>
2026-06-25 12:55:04 -04:00
Tyler
73cc31cc52 chore: remove LLM-slop comments across the codebase (#1277)
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
Co-authored-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: Max <d8473ee32b973aa31a21a65adddcc4b69cc2a8a4dee8121ecd51926e0cddbc02@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: Mari <95cae996907d7cab9f5dbf43c0f53edeac6ab0b032a6feae4abfd784e467b3f5@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: Quinn <96f056ad5f2305c8ddf637dc65d048aa4c12d7daeb8867690e34fca46b0ef64c@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: Sami <f4a42a97e594b77bdbd8ee35191c8b28a94a4cb871d96f32921558275421fb68@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: Perci <5a968df9a7494b4e019b9ecf739e088ba61097b4312124e9a88ae5b42e3f5f3e@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
2026-06-25 11:03:05 -04:00
tlongwell-block
fa1cade3ba handoff: pass full session history to summarizer (#1033)
Signed-off-by: npub1jh9wn95s0472h86ahapupaf7m6kx4v9sx2n0atj2hltcfer8k06s5n3pyf <95cae996907d7cab9f5dbf43c0f53edeac6ab0b032a6feae4abfd784e467b3f5@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: npub1jh9wn95s0472h86ahapupaf7m6kx4v9sx2n0atj2hltcfer8k06s5n3pyf <95cae996907d7cab9f5dbf43c0f53edeac6ab0b032a6feae4abfd784e467b3f5@sprout-oss.stage.blox.sqprod.co>
2026-06-13 15:49:05 -04:00
Will Pfleger
5a8cc79c6a feat(auth): force token refresh on rejected token (401/403), never the browser (#1015)
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@sprout-oss.stage.blox.sqprod.co>
2026-06-12 17:19:37 -04:00
Will Pfleger
f085882454 feat(acp): implement systemPrompt with protocol version gating (#981)
Signed-off-by: Will Pfleger <wpfleger@block.xyz>
Signed-off-by: Will Pfleger <wpfleger@squareup.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: npub16v54tttfqacx9ycvc3k0ut0npj564ahcuajzy6qjvh57ntmsf4uq4806j2 <d32955ad69077062930cc46cfe2df30ca9aaf6f8e76422681265e9e9af704d78@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: npub1fgdl5qqnh3k3f2xkqrvt7cujalhm623x4s7fdjdj5yrtp5fzjl9qrjpucw <4a1bfa0013bc6d14a8d600d8bf6392efefbd2a26ac3c96c9b2a106b0d12297ca@sprout-oss.stage.blox.sqprod.co>
2026-06-12 15:07:34 -04:00
Will Pfleger
d99ad131f1 refactor: rename sprout backend to buzz (#958)
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <wpfleger@block.xyz>
Signed-off-by: Will Pfleger <wpfleger@squareup.com>
Signed-off-by: Will Pfleger <wpfleger96@gmail.com>
Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: npub1fgdl5qqnh3k3f2xkqrvt7cujalhm623x4s7fdjdj5yrtp5fzjl9qrjpucw <4a1bfa0013bc6d14a8d600d8bf6392efefbd2a26ac3c96c9b2a106b0d12297ca@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: npub16v54tttfqacx9ycvc3k0ut0npj564ahcuajzy6qjvh57ntmsf4uq4806j2 <d32955ad69077062930cc46cfe2df30ca9aaf6f8e76422681265e9e9af704d78@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: Will Pfleger <wpfleger96@gmail.com>
2026-06-10 19:29:51 -04:00