Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
c9e1d5f
docs(fork): design visible and controllable agent sessions
Oct 10, 2026
24acf37
docs(fork): keep all session-continuity work in one PR
Oct 10, 2026
a9eb3ff
docs(fork): move session-continuity specs out of the public repo
Oct 16, 2026
f94925d
Merge remote-tracking branch 'fork/main' into fork/session-continuity
Oct 16, 2026
1bdce4b
fix(buzz-acp): decide which prompt results count as idle activity
Oct 16, 2026
f21c250
fix(buzz-acp): refresh idle clocks inside prompt result handling
Oct 16, 2026
c04e321
docs(fork): register the idle clock refresh seam
Oct 16, 2026
6cd82b6
fix(fork): treat MCP launcher chains as resident helpers
Oct 16, 2026
f1f524d
feat(fork): add per-scope session store with leases
Oct 16, 2026
2ebf33e
feat(fork): advertise-aware ACP session/resume
Oct 16, 2026
92fd9a0
feat(fork): resume ACP sessions from the session store
Oct 16, 2026
a0537d5
fix(fork): register the session store instance on first write
Oct 16, 2026
d76f37b
feat(fork): mark and discard durable sessions from pool control paths
Oct 16, 2026
d5ea6ae
feat(fork): pin the agent's first mention into cold-start context
Oct 16, 2026
4492233
feat(fork): tell the agent why its session is new or resumed
Oct 16, 2026
a7e9ee3
style(fork): satisfy fmt and clippy for session continuity
Oct 16, 2026
8409553
test(fork): drive rotate-during-acquisition tests by adapter calls
Oct 16, 2026
b93a67a
fix(fork): fence instance registration and preserve cold-start assign…
Oct 16, 2026
cdb193d
fix(fork): resume provider sessions after idle worker transport failure
Oct 16, 2026
7eba21e
fix(acp): isolate resumed sessions and fence failed channel invalidation
Oct 17, 2026
1c212db
feat(core): add shared session status contract
Oct 17, 2026
ac26aaf
fix(core): encode incomplete history in session info tag
Oct 17, 2026
45da1a7
feat(acp): publish per-scope session status snapshots
Oct 17, 2026
0e2d8b6
fix(acp): report only allowlisted resume failure codes in status
Oct 17, 2026
5c3e7ad
feat(cli): attach session status to new agent messages
Oct 17, 2026
31726a3
feat(desktop): show agent session status beneath agent messages
Oct 17, 2026
5d93adf
fix(session): preserve compatible state and trusted display scopes
Oct 17, 2026
f0bce6c
feat(session): define controls and durable restart intent
Oct 17, 2026
6364aa0
fix(session): retain restart policy after failed attempts
Oct 17, 2026
9f32df9
feat(session): authorize restart controls and bound cold history
Oct 17, 2026
0107137
fix(session): preserve retired worker health and DM pagination
Oct 17, 2026
50e1ddb
fix(session): verify worker retirement before restart success
Oct 17, 2026
87213c3
fix(acp): preserve restart ownership and orphan reconciliation
Oct 17, 2026
936b0f7
test(acp): add hosted retirement containment gates
Oct 18, 2026
38754f4
Merge current main to enable hosted retirement validation
Oct 18, 2026
2d306bb
fix(ci): expose hosted ACP discovery diagnostics
Oct 18, 2026
b1001f0
fix(acp): gate Unix permission fixtures on native platforms
Oct 18, 2026
e871ad5
test(acp): await checked termination fixture readiness
Oct 18, 2026
89289ea
feat(acp): add bounded typed compaction controls
Oct 18, 2026
4540eab
fix(acp): preserve compaction capability and terminal receipts
Oct 18, 2026
d574a7d
feat(acp): checkpoint Windows worker job containment
Oct 18, 2026
29e9acc
test(acp): verify Windows retirement proof and retry bounds
Oct 18, 2026
bda88d5
feat(desktop): add scoped session controls
Oct 18, 2026
bbf1a85
Project typed compaction lifecycle into observed activity
Oct 18, 2026
02d2451
test(fork): prepare integrated session controls acceptance
Oct 18, 2026
89e476a
docs(testing): require process isolation for native desktop QA
Oct 18, 2026
b43c745
fix: close session-control lifecycle gaps
Oct 18, 2026
a14faca
fix(desktop): keep reaction picker within available viewport
Oct 18, 2026
62a73df
fix(acp): classify Codex snapshot races conservatively
Oct 18, 2026
ab605a4
fix(acp): avoid disk compaction wake when resume is disabled
Oct 18, 2026
bc1f91a
fix(desktop): clarify session restart choices
Oct 19, 2026
34e8ea9
fix(desktop): preserve contrast in session menu descriptions
Oct 19, 2026
e461a03
Merge current main into session continuity
Oct 19, 2026
c6170ac
test: synchronize snapshot fixtures and history fetch prerequisite
Oct 19, 2026
558f83a
test: isolate history request counts and seed expired controls before…
Oct 19, 2026
49e03e2
fix: correct Claude context and queued compaction status
Oct 19, 2026
98e5d3b
test: expect Claude result metadata in wrapped sessions
Oct 19, 2026
d717e39
feat(agents): manage thread context and report restart status
Oct 20, 2026
85646db
fix(agents): spell out inferred restart-status language
Oct 20, 2026
0b31365
fix(agents): ground restart status in delivered history
Oct 20, 2026
7d9f216
fix(fork): add agent avatars and first restart reply metadata
Oct 20, 2026
94d805f
fix(fork): close session control CI regressions
Oct 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion .github/workflows/_ci-rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@ jobs:
for bin in buzz-acp buzz-agent buzz-dev-mcp git-credential-nostr buzz; do
touch "desktop/src-tauri/binaries/${bin}-${TARGET}.exe"
done
- name: Test (ACP checked retirement on Windows)
run: python scripts/check-acp-retirement.py
- name: Clippy (workspace)
run: cargo clippy --workspace --all-targets --target $env:TARGET -- -D warnings
- name: Check (workspace)
Expand Down Expand Up @@ -206,11 +208,27 @@ jobs:
env:
CMAKE_POLICY_VERSION_MINIMUM: "3.5"

acp-retirement-macos:
name: ACP checked retirement (macOS)
runs-on: macos-latest
timeout-minutes: 30
if: inputs.lane == 'required' && (github.event_name == 'push' || inputs.rust)
permissions:
contents: read
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32
with:
key: acp-retirement-macos
save-if: ${{ github.event_name != 'pull_request' }}
- name: Test (ACP checked retirement on macOS)
run: python3 scripts/check-acp-retirement.py

results:
name: Results
if: ${{ always() }}
needs: [rust-lint, unit-tests, server-cross-compile, windows-rust]
needs: [rust-lint, unit-tests, server-cross-compile, windows-rust, acp-retirement-macos]
runs-on: ubuntu-latest
timeout-minutes: 5
permissions: {}
Expand Down
4 changes: 4 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,10 @@ See [TESTING.md](TESTING.md) for the full multi-agent E2E guide.
For mobile simulator screenshots, save the PNGs in a local directory and run
`./scripts/post-screenshots.sh <PR-number> <png-dir>` or use the third argument
with a markdown template containing `{{filename}}` placeholders.
For a fork, select both destinations explicitly:
`./scripts/post-screenshots.sh --repo owner/repo --remote fork <PR-number> <png-dir>`.
The script preserves screenshot history with a normal push and signs off its
asset commit using the effective Git identity. A rejected push stops publication.

The desktop app requires the E2E mock bridge to render — it cannot run in a plain
browser. Use `just desktop-screenshot` to capture screenshots (builds frontend,
Expand Down
13 changes: 13 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2,256 changes: 2,044 additions & 212 deletions FORK_PATCHES.md

Large diffs are not rendered by default.

49 changes: 49 additions & 0 deletions TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -389,3 +389,52 @@ CLI-side, only two matter for testing:
| ACP logs `discovered 0 channel(s)` / `no channel subscriptions resolved` | Agent identity isn't a member of any channel | `buzz channels add-member --channel "$CHANNEL" --pubkey "$AGENT_PUBKEY" --role member` from another identity |
| `GOOSE_MODE` warning, agent hangs | Not set | `export GOOSE_MODE=auto` |
| Tests pass locally but CI fails | Forgot to run `just ci` | `just ci` runs the gate (fmt, clippy, unit tests, desktop/web builds) |

### Desktop scoped session controls

Native Desktop QA must run in a verified private PID namespace, or behind an
explicit process-reaper fence reviewed before launch. A demo app identifier,
separate XDG directories and a private DBus session do not isolate process
reaping: startup and periodic cleanup can inspect and stop agents from other
Desktop instances. Do not mount the host `/proc` into a native QA container;
verify its visible process set and mount list before exercising the app.

The registered mock-bridge smoke workflow drives the thread composer's
Manage context → agent list → selected agent actions flow,
verified directory refresh, native send boundary, canonical thread routing and
compact request row. It also checks keyboard submenu/Escape focus return,
permission revocation and draft preservation. Run it with a fresh E2E build and
refuse an existing server:

```sh
CI=1 pnpm --dir desktop test:e2e:smoke session-controls.spec.ts session-status.spec.ts
```

For jsdom absence assertions, compare a boolean with a descriptive message
(for example, `assert.ok(!view.queryByRole("menuitem"), "No actions after revocation")`).
Do not pass a live DOM element to `assert.equal(element, null)`: Node's failure
diff can traverse the jsdom/React graph and exhaust memory. Await asynchronous
query-cache notifications before asserting their rendered effect. Run test and
build process trees under an external hard memory limit, zero swap and a runtime
deadline; fail closed if containment cannot be established. A V8 heap flag or
in-process test timeout alone cannot bound native allocation or a stalled event
loop.

Raw result and actor projection regressions live beside the production formatter
in `desktop/src/features/messages/lib/sessionControl.test.mjs`. The shared
`crates/buzz-core/tests/fixtures/session_controls.json` command corpus runs against
both actual parsers, including the Rust/JavaScript Unicode whitespace boundary.
Mock-bridge coverage does not attest real-provider or native-platform behavior.
The final browser matrix also checks independent target agents, denied access,
twenty-minute-old request expiry and validated manual actor copy. Stored raw
tags distinguish canonical native requests from generic seeded reply helpers.

The same registered workflow also opens an owned agent's production activity
panel and ingests raw observer frames through `syncAgentObserverEvents`. It
checks in-place typed compaction outcomes, replay, two operations in one turn,
legacy hook deduplication, visible failures and the original raw rail. The
focused `agentSessionCompaction` and `observerCompaction` tests cover exact wire
validation, UTF-8 ID bounds, observed worker/resume and successful new-session ACK boundaries, missing-history
unknown provenance, summary limits, terminal monotonicity and store rebuild/reset.
Observer access remains owner-scoped; permission to send a remote session control
does not authorize reading that agent's observer feed.
6 changes: 6 additions & 0 deletions crates/buzz-acp/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ buzz-persona = { path = "../buzz-persona" }
git-credential-nostr = { path = "../git-credential-nostr" }
git-sign-nostr = { path = "../git-sign-nostr" }
tempfile = "3"
# Advisory locks for the durable session store.
fs2 = "0.4"
zeroize = { workspace = true }

# Nostr
Expand Down Expand Up @@ -82,6 +84,10 @@ evalexpr = { workspace = true }
[target.'cfg(unix)'.dependencies]
nix = { version = "0.31", default-features = false, features = ["signal", "user"] }

[target.'cfg(windows)'.dependencies]
process-wrap = { version = "=10.0.1", default-features = false, features = ["tokio1", "job-object", "creation-flags", "kill-on-drop"] }
win32job = "=2.0.3"

[dev-dependencies]
axum = { workspace = true }
tokio = { workspace = true, features = ["test-util"] }
Expand Down
73 changes: 73 additions & 0 deletions crates/buzz-acp/SESSION_CONTROLS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Restart status turns

An authorized Restart first durably resets the selected scope and waits for
checked termination of its old workers. Only successful retirement schedules a
short task-status turn. The Restart acknowledgment confirms that reset; it does
not claim that a provider started or that a status reply reached the relay.

The status is the replacement session's first foreground turn. It uses the
selected None, Recent or All history policy, the normal standing instructions
and the original assignment where available. None does not reconstruct omitted
progress from files, logs or another provider session. The automatic bootstrap
initial message is suppressed. Later human messages keep their original queue
entries and instructions; they cannot steer or merge into this status turn.
Other scopes remain available. Status acquisition and execution have at most
120 seconds, subject to shorter configured task limits. Failure feedback has a
separate five-second grace and does not extend queue priority.

The provider returns one short text answer, without tools, subagents or task
execution. The controller captures answer chunks for that exact session, signs
one ordinary kind-9 reply as the selected agent, and posts it to the original
canonical thread or DM. It adds no mentions or broadcast tags. The controller
requires an exact relay acknowledgment before consuming the captured restart
intent. Refusal, limits, empty output, partial failed output, observed tools,
startup errors and unconfirmed delivery cannot become successful task status.
They leave an independent scoped failure notice when it can be delivered.
Restart success remains unchanged.

The turn has a durable Started marker before provider acquisition or model
execution. Captured text is limited to 12 KiB, and each saved signed event to
20 KiB, within the existing 256 KiB record cap. Signed status bytes are saved
before sending; at most three same-owner submissions reuse those exact bytes.
Failure-notice bytes are saved separately and never replace an uncertain actual
reply. New Restart, cancellation, shutdown and channel removal fence late
results. Publication authorization is the last controller check before HTTP
submission: a request already sent to the relay cannot be retracted.

After the owning controller dies, recovery never starts another model attempt
or automatically resubmits prepared bytes. It preserves those bytes and marks
the follow-up unconfirmed. This also prevents a failed cancellation write plus
a crash from authorizing stale output. A crash before dispatch or acknowledgment
can therefore lose the automatic status; a fresh Restart explicitly requests a
new attempt, and ordinary human input can still continue using the retained
history policy. Cancellation persistence failure is reported as unconfirmed,
while the live controller immediately fences further effects. If cancellation
cannot be saved, the owner/observer control result is unconfirmed and no chat
notice is sent: a failed write cannot establish publication ownership. Status
and failure-feedback jobs retain the exact scope fence captured before
scheduling, including expiry and known-local-panic feedback. A temporarily
missing or unreadable record cannot retain local status authority: cancellation
fences the accepted local token before reading the store. This also covers
cancellation
while retirement is still pending; later checked retirement can confirm the
reset and retain its history policy without recreating the cancelled follow-up.
A previously confirmed terminal status is preserved.

Language inference deliberately supports Russian, English and unknown. The
bounded selected-history reader keeps only a language code from evidence that
None otherwise omits. It checks signed channel/thread routing and ignores
controls, code, quotes and timestamp/severity log prefixes. Numbered progress
prose remains evidence. Newer substantive unsupported prose replaces an older
recognized hint with unknown; excluded non-prose does not replace it. The
configured owner and the exact authenticated requester are trusted conversation
authors by policy; this does
not prove human identity. Verified signed agent attestations exclude an author.
Absent profiles do not establish human identity. Unsupported or ambiguous
conversation evidence keeps the generic same-conversation-language instruction,
without inventing an English hint. No additional model call or unbounded
history lookup is used.

ACP permission requests for the status session are denied, and observed tool
calls fail the turn. Some providers can execute native tools before reporting
them, so this is not a universal tool sandbox. Real provider validation must
confirm the adapter's actual behavior.
40 changes: 40 additions & 0 deletions crates/buzz-acp/TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,3 +197,43 @@ report/event ID, in the original thread, with its callback mention. A second
background group must produce a second report/key. Explicit cancel/rotate and
membership removal still invalidate collection according to the lifecycle
runbook above. Agent fixtures cannot mark this human check complete.

## Restart status

The `restart_status_tests` filter covers authorized admission, checked retirement,
the typed scheduler, fake ACP processes, selected history and a signature-checking
HTTP relay. It covers context depths, conversation-language hints, reply routing,
queued input, duplicates, bounded failures, cancellation and crash uncertainty.
See [SESSION_CONTROLS.md](SESSION_CONTROLS.md) for the contract and its limits.
Use the full package suite before accepting the change; a focused filter is an
iteration aid, not a substitute for package validation.

For each supported real adapter, use a disposable session and the actual build
under review:

1. Start an English assignment, then discuss progress in Russian. Restart with
None, Recent and All. Each successful reset should produce one useful Russian
status without another mention. With None, omitted progress must be reported
as unknown. Repeat an English conversation and expect English.
2. Repeat while the old worker is busy. Wait for checked reset completion; no
status may run before that boundary. Queue a real new instruction during the
status turn: it must run afterward in its original form. A sibling thread
must continue independently.
3. Cancel, remove the agent, or issue a newer Restart during status generation.
Late output must not begin a newly authorized send. A send already submitted
may still become visible; lost acknowledgment remains unconfirmed. Inspect
adapter activity and verify that no task tool or subagent executed.
4. Check that the reply is signed by the selected agent, in the canonical thread
or DM, with no mention/broadcast tags. Repeat the same signed Restart and
confirm it does not create another model turn or message.
5. Send an ordinary follow-up after a confirmed status. It should reuse the new
session while ordinary provider-resume and rotation limits still apply.
Simulate provider failure and loss of acknowledgment separately: the reset
acknowledgment stays successful, and failure/uncertainty is reported honestly.
6. Restart the disposable harness with a pending or prepared follow-up. Recovery
must preserve uncertainty and prepared bytes without invoking the model or
automatically posting that old reply. Later ordinary input remains available.

Record build identity, adapter, selected depth, actual language, signed reply
provenance and observed tool activity. Browser fixtures alone cannot certify
provider behavior. Human acceptance remains separate from agent validation.
Loading
Loading