Skip to content

feat(acp): honour _meta.freshSession — !rotate opens a new channel conversation; repin buzz-acp to fountain.3 - #788

Merged
jhgaylor merged 2 commits into
mainfrom
feat/acp-fresh-session-on-rotate
Aug 17, 2026
Merged

feat(acp): honour _meta.freshSession — !rotate opens a new channel conversation; repin buzz-acp to fountain.3#788
jhgaylor merged 2 commits into
mainfrom
feat/acp-fresh-session-on-rotate

Conversation

@jhgaylor

Copy link
Copy Markdown
Collaborator

Summary

Two prod findings after #787 (!rotate/!shutdown now reach the harness from Desktop):

  1. !rotate did nothing. The harness rotated (log: !rotate received — invalidated idle channel session(s) invalidated=1), then the next turn's session/new carried the same _meta.channelId, and the channel-bound resume (buzz: a hosted harness loses every channel's conversation and sandbox on each deploy (buzz-acp session/new after restart) #774) handed the same conversation back. Rotation and channel-binding cancelled out.
  2. !shutdown restart-looped. Fountain.Buzz.Harness restarts on any exit (status=0 included); the fresh process replayed the same !shutdown from its 5s subscription backlog and exited again — 5 cycles per command, ending online:
    07:07:27.824 [warning] buzz-acp exited: label=a37cb149-… status=0 — restarting
    07:07:28.992 [warning] buzz-acp exited: label=a37cb149-… status=0 — restarting
    … ×5
    

Change

Rotate → new conversation (pairs with block/buzz#6103, which sends _meta.freshSession: true on the one session/new after !rotate):

  • fountain acp (cli/internal/acp/session.go, cmd/acp.go): reads _meta.freshSession, forwards it as fresh: true on POST /api/conversations — only alongside a channel key.
  • Server (Conversations.start_or_resume_conversation/2): with fresh, the currently-bound conversation is unbound (channel_id cleared; it keeps running and the sandbox reaper retires it as usual) and a new one is opened as the binding. Unbinding rather than "newest wins" keeps the outcome independent of inserted_at's one-second precision — the first cut relied on ordering and a same-second rotate resumed the old one in the test.
  • OpenAPI: fresh on ConversationCreateRequest.

Shutdown loop (block/buzz#6104): the harness now ignores owner control commands created before its process started, so a replayed !shutdown no longer re-fires. This PR does not change Harness's restart-on-any-exit policy — that's a separate I5 question (should an owner !shutdown keep the identity down until re-enabled?) worth its own decision; with #6104 the loop is gone either way.

Pin: buzz-acp.sourcejhgaylor/buzz@f98c2a7f (fountain-pin = #6088 + #6101 + #6103 + #6104), release buzz-acp-v0.5.14-fountain.3, Dockerfile default ARG, CHANGELOG. #776's repin condition now includes #6103 and #6104.

Test plan

  • Go: TestNewSessionForwardsFreshSessionAfterARotate, TestCreateConversationSendsFreshWithTheChannelKey (new); go test ./internal/acp/ ./internal/cmd/ green; gofmt/vet clean
  • Elixir: "fresh: true opens a new conversation despite the binding, and it takes over" (new) — 69/69 in conversation_controller_test.exs; mix format --check-formatted; credo --strict clean on the changed module
  • Rollout: Publish buzz-acpbuzz-acp-v0.5.14-fountain.3 (both arches) → CI → build → Flux
  • Prod: @Fountain Maintainer !rotate, then a mention → new conversation; !shutdown → single clean exit in pod logs

🤖 Generated with Claude Code

…nversation; repin buzz-acp to fountain.3

The channel-bound resume (#774) hands the harness the same conversation on
every session/new, which made buzz-acp's !rotate a no-op behind Fountain:
the harness dropped its session, asked again with the same _meta.channelId,
and got the old conversation back (observed on the hosted Fountain
Maintainer). The harness now marks that one session/new with
_meta.freshSession: true. fountain acp forwards it as fresh: true on
POST /api/conversations; the server unbinds the current conversation from
the channel (channel_id cleared — it keeps running) and opens a new one as
the binding. Unbinding rather than 'newest wins' keeps the outcome
independent of inserted_at's one-second precision.

buzz-acp.source -> jhgaylor/buzz@f98c2a7f (fountain-pin), release
buzz-acp-v0.5.14-fountain.3: adds the freshSession signal and ignores owner
control commands created before the process started, which ends the
!shutdown restart loop (exit -> supervisor restart -> backlog replay ->
exit, x5) seen on prod.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Jake Gaylor <jhgaylor@gmail.com>
…(warnings-as-errors)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Jake Gaylor <jhgaylor@gmail.com>
@jhgaylor
jhgaylor merged commit 0583c1c into main Aug 17, 2026
8 checks passed
@jhgaylor
jhgaylor deleted the feat/acp-fresh-session-on-rotate branch August 17, 2026 07:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant