Skip to content

feat(sdk): Claude Desktop Band agent integration - #501

Open
AlexanderZ-Band wants to merge 40 commits into
mainfrom
feat/intel-claude-desktop-claudeai-band-integration-ban-INT-1116
Open

feat(sdk): Claude Desktop Band agent integration#501
AlexanderZ-Band wants to merge 40 commits into
mainfrom
feat/intel-claude-desktop-claudeai-band-integration-ban-INT-1116

Conversation

@AlexanderZ-Band

@AlexanderZ-Band AlexanderZ-Band commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds Claude Desktop as a Band agent.

  • band-mcp --scope agent performs Band operations.
  • band-room-view keeps the configured agent online, shows one agent-visible room transcript, and synchronizes it with Claude.
  • The room view resolves identity from /api/v1/agent/me, uses the configured REST/WebSocket endpoints, and supports join, refresh, and waiting for room events.
  • Includes the desktop extra, band-room-view command, setup documentation, and tests.

Boundary

Claude Desktop cannot start a new model turn from an unsolicited Band message. The room view can update while Desktop is open; replies require an active Desktop turn/monitor loop.

Verification

  • Real agent identity and WebSocket connection verified against the development platform.
  • Agent-scoped band-mcp send and a delegated reply flow verified live.
  • 4,098 unit tests passed; 82 skipped.
  • Ruff, formatting, Pyrefly, diff checks, JavaScript syntax, and skill validation passed.

@linear-code

linear-code Bot commented Jul 27, 2026

Copy link
Copy Markdown

INT-1116

@AlexanderZ-Band
AlexanderZ-Band force-pushed the feat/intel-claude-desktop-claudeai-band-integration-ban-INT-1116 branch from 8315300 to 1ffd3ec Compare July 27, 2026 18:06
@AlexanderZ-Band AlexanderZ-Band changed the title feat(managed-agents): Anthropic Managed Agents integration feat(integration): Anthropic Managed Agents integration Jul 27, 2026
@AlexanderZ-Band AlexanderZ-Band changed the title feat(integration): Anthropic Managed Agents integration feat(sdk): Anthropic Managed Agents integration Jul 27, 2026
@AlexanderZ-Band
AlexanderZ-Band marked this pull request as ready for review July 27, 2026 18:20
@AlexanderZ-Band AlexanderZ-Band changed the title feat(sdk): Anthropic Managed Agents integration feat(sdk): Claude Desktop Band agent integration Jul 28, 2026
@AlexanderZ-Band
AlexanderZ-Band force-pushed the feat/intel-claude-desktop-claudeai-band-integration-ban-INT-1116 branch 2 times, most recently from 27fea58 to 3feac29 Compare July 28, 2026 11:20
@AlexanderZ-Band
AlexanderZ-Band requested a review from a team July 29, 2026 08:49
@AlexanderZ-Band

Copy link
Copy Markdown
Collaborator Author

Desktop constraints behind this design

  • Anthropic documents local Desktop integration through MCP servers/extensions; it does not document a public API for an external process to create or append to a Desktop conversation. MCP notifications and MCP Apps can update the client/UI, but do not define a Desktop model-turn trigger. The room view therefore owns the Band WebSocket and synchronizes the view while an active Desktop turn monitors it. Desktop MCP setup · MCP notifications · MCP Apps
  • Cowork does not provide that external event hook: scheduled tasks run on a cadence and Dispatch starts work from a Claude mobile message. Anthropic does not document an API or webhook that lets a Band WebSocket/filesystem event target a specific Desktop/Cowork conversation. Scheduled tasks · Dispatch

@AlexanderZ-Band
AlexanderZ-Band force-pushed the feat/intel-claude-desktop-claudeai-band-integration-ban-INT-1116 branch from 80b79b2 to b422071 Compare July 29, 2026 08:56
AlexanderZ-Band and others added 17 commits July 29, 2026 15:10
Fanout runs on the leader's WebSocket event path, and drained each
follower's socket with no bound. A follower that stopped reading — a
frozen Desktop, a stopped process still holding its end — stalled the
send queue it shares with every other follower, starving all of them of
room events rather than only itself.

Delivery is now bounded by band_relay_fanout_timeout_s and a follower
that misses it is dropped like one that raised.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUzAucWVaLChjBgzRA27eW
Desktop answers an unactivated ui/message with a JSON-RPC error, not an
isError result. The view classified every thrown error as a transport
failure, so the deterministic refusal came back through retry_wakes and
was re-asked on every tick — measured live at two failed round trips per
tick, with the same stale mention ids riding along in wake_requests for
the rest of the conversation.

A rejection now carries whether the host answered it, so only a call that
never arrived is retried. The ledger also gives up on a mention after
MAX_REOFFERS, so a future misclassification cannot loop forever; the
monitor loop remains the guarantee that the mention is answered.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUzAucWVaLChjBgzRA27eW
Observed live: the agent answered a message typed mid-wait and never
resumed monitoring. The room went unwatched for minutes while the view's
display loop kept ticking, so both the widget and the transcript looked
healthy — and the agent had no way to tell, since nothing it can see
distinguishes a watched room from one it stopped watching.

Monitor calls now name their caller, so the view's display ticks no
longer stand in for the agent's. A read whose last model tick is older
than STALE_AFTER_TICKS quanta carries a server-authored monitoring
notice, which the view relays into model context beside the briefing.
The notice rides every tick and empties itself once the loop is running
again — folding it into the cached briefing would have nagged forever.

Repair still lands on the agent's next turn: Desktop declares
sampling=false and gates ui/message behind user activation, so nothing
here can start a turn on its own.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUzAucWVaLChjBgzRA27eW
A stalled loop leaves the room unwatched while the view keeps ticking,
so it looks healthy from every surface. The notice the agent gets is now
also on the record, which is how anyone diagnoses this after the fact.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUzAucWVaLChjBgzRA27eW
Caught live: the agent's loop was stopped for minutes and nothing said
so. On a live socket a quiet tick answers from the last read's snapshot
without reading, and that snapshot was taken while the loop was still
running — so the one state that changes precisely when nothing else does
was the one being reported stale.

The quiet path now takes the monitoring status fresh, as it already did
for the transport and the host. The regression guard runs on a live
transport, which is what makes the tick skip its read; the earlier tests
all read every tick and so could not see this.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUzAucWVaLChjBgzRA27eW
Adding one enum field put a $ref/$defs pair in the monitor tool's input
schema, and Claude Desktop validates arguments against that schema
itself. It mis-parsed the whole document: chat_id arrived undefined and
timeout_seconds arrived as the string "25", so every monitor call failed
validation and the agent stopped watching the room entirely.

Tool schemas are now emitted self-contained, with internal refs inlined
and the field's own words kept over the type's. Pydantic emits a $ref for
any enum or nested model, so this belongs at the boundary rather than as
a rule about how the input models may be written.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUzAucWVaLChjBgzRA27eW
The staleness limit came off the install default, but the quantum is the
model's to pick per call — and the briefing tells it to wait up to 30s
once the room is quiet. On a default install that read a healthy loop as
stopped after a single wait, and injected "you are NOT monitoring" into
its context on every tick while it was monitoring: worse than the silence
it replaced, because it teaches the agent to disregard the notice.

Each model tick now records the quantum it asked for, and the limit
follows it. The matching warning is claimed once per outage rather than
logged per tick, since the view keeps ticking for as long as the
conversation is open and would otherwise bury the log in repeats.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUzAucWVaLChjBgzRA27eW
Claude Desktop sends numbers as JSON strings. Arguments were passing
through two gates — the framework's JSON Schema pass and the input models
— and the strict one rejected "30" against an integer before the model
that reads it as 30 ever saw it. Every monitor call naming a timeout
failed, which is every call the briefing asks for, so the agent lost its
loop and said so: "validation snag on every value I tried".

The input models are now the only gate, and a call they cannot read is
refused in the caller's own terms rather than by schema path. Tests
missed this because they invoke handlers with Python types; the guard
added here calls the tool the way a host does, with the numbers spelled
as strings.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUzAucWVaLChjBgzRA27eW
The limit was a multiple of the agent's quantum, which is the wrong
shape: what happens between two monitor calls is one wait plus whatever
the agent does with what the wait returned, and that work does not scale
with how long it chose to wait. So the multiple ran 90s on a 30s quantum
— measured live, a room unwatched that long before anything said so —
while giving only 15s on the 5s quantum the briefing asks for during a
conversation, less than answering a single mention takes.

Adding a fixed grace to the agent's own wait tracks the real iteration:
60s at a 30s quantum, 35s at 5s.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUzAucWVaLChjBgzRA27eW
Live, with the stopped-loop notice reaching it, the agent reported its
state correctly for the first time — "idle now" rather than the "watching
room as tom" it used to claim while its loop was dead — and then asked
whether it should resume. Both the notice and the monitoring contract
said to call the tool; neither said the call was not the user's to
approve. A turn that ends on that question leaves the room unwatched
until they answer, which is the outage it was meant to end.

Joining the room is the standing instruction to watch it, and both texts
now say so.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUzAucWVaLChjBgzRA27eW
Asked why it had stopped watching, the agent recited the contract back
correctly — "each turn should end with resume monitor" — and resumed. So
the instruction was reaching it; the instruction was wrong. It framed
turns as ending normally, and every ending leaves the room unwatched
until the user happens to type, since nothing here can start a turn.

The flood measured what makes that unnecessary: the host delivers a
message typed mid-turn at the next tool-call boundary, so the agent can
answer its user between two monitoring calls without pausing the loop.
The contract now says the turn is the loop and stopping is the only thing
that unwatches the room.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUzAucWVaLChjBgzRA27eW
Verified against the MCP Apps protocol: ui/update-model-context does not
trigger a follow-up — the host defers the latest update until the next
user message, including ui/message. So the stale notice cannot restart a
stopped loop by itself; until the user acts it is a diagnostic, and the
one actor who can act was being shown a green "Live · synced" badge
computed from transport health alone.

The room has two liveness axes — event delivery and agent attention —
and the display now reports the weaker: "Agent stopped watching" with
the idle gap when the model's loop is stale, and a Wake agent button
whose click carries the user activation ui/message requires. The click
relays the server-authored notice as the message, starting the turn that
the deferred context update rides in with; the view still writes no
model-facing text. On recovery the next payload clears notice, badge,
and button together.

Also: a widget instance the host mounted but never fed (Desktop renders
one instance while the call runs and hands the result to another) no
longer reports "WebSocket down · polling" — it has no transport to
report, and says it is waiting for a room.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUzAucWVaLChjBgzRA27eW
A room event preempts the wait instantly and a quiet tick on a live
socket costs no REST, so a longer quantum buys only fewer tool round
trips — and its entire price is the human: a message they type lands at
the next tool boundary, making the ceiling their worst-case wait to be
heard. The briefing was steering the agent to 30 seconds on a quiet
room, which is far past what a person accepts from a chat surface with
them sitting at it.

Ceiling lowered to 15 seconds, and the briefing now tells the agent to
stay at the default once the user goes quiet instead of reaching for the
ceiling. Worst case as measured — quantum plus model latency — drops
from ~35s to ~13s at the default, ~8s while conversing. The cost is ~4
quiet ticks a minute instead of 2 on an idle room, on a surface whose
sessions already last hours, not days.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUzAucWVaLChjBgzRA27eW
Measured live: the platform does not echo an agent's own messages back
to its own WebSocket. A message the agent posts through band-mcp (a
separate process on the same identity) produces no event in the room
view's relay, so the skip-read proof — broker version unchanged on a
live socket means the room is provably unchanged — was built on a false
premise. Its failure compounded: reads were skipped while the quiet-tick
cursor advanced past the unseen posts, so the agent's own messages never
rendered at all. The widget froze on green "Live · synced" and only a
manual Refresh showed what the agent had been saying.

A tick now ends in a page-1 REST read regardless; the event's only job
is ending the wait early, and a caller behind the newest message ever
read is answered before waiting at all. ReadPulse keeps the watermark
and snapshot but no longer claims proof authority — its docstring
records why, so the optimization can return if the platform ever echoes
own messages. The regression test posts as the agent with no event and
requires the next quiet tick to deliver it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUzAucWVaLChjBgzRA27eW
Room-first (the default, unchanged) parks the turn on the monitor loop:
the room is answered in seconds and the user's typing waits one quantum.
User-first inverts the trade for users who want to come first: no turn
is held, the user is answered instantly, and the room is swept once at
the start of each turn — so a mention waits until the user next speaks
or clicks the view's Check room button, whose click carries the user
activation ui/message needs and whose text is the server-authored check
prompt.

The mode is chosen in chat (attention on the room-opening tools, from
phrasing like "answer me first"), switched in chat (attention on a
monitor call — honored only from caller=model, so the view's display
loop cannot flip it), and held server-side. The model only ever reads
the active mode's contract: attention_contract() emits one behaviour,
the other appearing solely as the line naming the way into it, because
a briefing describing both gets blended.

Everything the loop's health machinery assumes is gated to room-first —
staleness, the notice, the wake accelerator — since in user-first
not-watching is the chosen state and each would nag or fire refused
ui/message calls per mention. Pending mentions stay in pending_requests
for the next sweep; the widget becomes the inbox: "On demand · N
waiting" with a calm standing Check room button. Switching back re-arms
staleness freshly — the switching call is its own first tick — so an
idle gap accrued by choice never fires the notice.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUzAucWVaLChjBgzRA27eW
…way in

Tried live, the standing Check room button earned its removal twice
over: Desktop treats even an activated ui/message as a composer prefill
rather than a send, so the click still ended in the user pressing Enter
— a worse way to say something — and every user message already sweeps
the room by contract, so the button added chrome without adding a
capability. The widget now only counts what is waiting; the red Wake
agent button remains for its real job, the stopped-loop repair in
room-first, which appears only during a fault and carries text the user
should not have to compose.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUzAucWVaLChjBgzRA27eW
AlexanderZ-Band and others added 22 commits July 30, 2026 12:08
…ontrol

The behaviour, stated once: the user talks to the agent in chat, and by
default the user leads — the room is swept at the start of every turn,
and at join the agent asks once whether to watch continuously. Saying
"watch the room" (at join or any time) parks the turn on the monitor
loop with a 5-second beat back to the user; both directions switch in
chat and take effect in the same reply.

The widget is a window into the room: resizable by its native handle,
collapsible, counting what waits — and buttonless, because everything
the user wants done is said in chat. That deleted the ui/message wake
path whole: without activation the host refuses it, with activation it
only prefills the composer, and a buttonless window has no activation
source. Gone with it: the wake ledger, retry_wakes, wake_requests and
wake_prompt, the refresh tool nothing called anymore, and the view code
and tests that served them. Staleness detection stays as the diagnostic
for a stopped watch — repair is the user's next message, which carries
the notice in with it.

The whole tuning surface for attention now lives in one declarative
table: CONTRACTS in prompts.py maps each mode to its briefing bullets,
its after-join instruction, and its per-tick resume line. Editing the
strings is editing the behaviour; nothing else moves. The user guide
gains transcripts of both modes and the switch; the architecture doc
and setup skill say what the code now does.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUzAucWVaLChjBgzRA27eW
Observed live: "join ... and answer me first" was answered with "want
the room watched continuously?" — the ask-once was written for the
default join, where the user expressed no preference, but the contract
could not tell a chosen user_first from a defaulted one. The model can:
it parsed the phrasing. The after-join instruction now says to ask only
when the user has not already said how the room should get attention.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUzAucWVaLChjBgzRA27eW
Desktop sometimes mounts two widget instances for one join — one while
the call runs, one for the result — and the orphan sat as a full-height
empty "Loading transcript…" box. The view now boots collapsed to its
one-line bar and expands only when the first payload names a room, so
the fed instance opens the moment the join lands and an unfed orphan
stays a quiet "waiting for a room" strip instead of a broken-looking
twin. Also better loading behaviour for the real instance: a slim bar
during the join read instead of an empty box.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUzAucWVaLChjBgzRA27eW
After a long conversation the widget lives far up in the scroll history
— hosts offer no way to pin or move a mounted iframe, but a tool result
naming the view resource mounts a fresh widget at the bottom. "Show the
room" now does exactly that: band_show_room remounts the live view at
the current point without touching the attention contract, each view
instance names itself on its watch ticks, and the server awards the
room's display to the most recently first-seen instance — every older
widget is told it is superseded and retires itself to a one-line bar.
An already-known instance can never steal ownership back, so the two
loops cannot flap. This also tidies Desktop's occasional double-mount:
whichever twin ticks second simply collapses.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUzAucWVaLChjBgzRA27eW
…irection

RoomTranscriptService had quietly absorbed four jobs: transcript reads,
attention mode, monitor health, and view-instance ownership. The last
three are one responsibility — this conversation's relationship to a
room, not the room's content — and now live together in RoomSession
(attention.py), which the service composes and the server addresses
directly. The untracked-room fallback follows the system default
(user_first), so a widget remounted after a process restart is not
greeted by an armed staleness alarm for a loop nobody promised.

Also deletes the workflow-operation registry: an enum, a type alias, a
dict, and a dispatch loop that served exactly one operation consumed by
two callers, which now simply call the function.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUzAucWVaLChjBgzRA27eW
Colors, radius, and danger red come from the tokens Band's site ships
(assets.band.ai entry stylesheet): ink #001319 on white/#f5f5f5
surfaces, #e4e4e7 borders, 6px radius, #ff6b6b danger, and the brand
green #92ed92 — used as a fill behind ink for the mark and badge, with
the deeper green-700 #54a554 carrying text-adjacent accents, since the
bright brand green does not read at small sizes. Dark scheme derives
from the same ink. Fonts stay the system stack: the widget's sandbox
blocks external origins, so webfonts cannot load anyway.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUzAucWVaLChjBgzRA27eW
The staleness heartbeat now runs both ways. The widget's display ticks
are the server's only proof a window is on screen, so RoomSession tracks
them (a mounting tool's grant counts as the first tick) and the monitor
summary tells the model to call band_show_room when they stop — after a
Desktop restart, which kills widgets silently, resuming monitoring used
to leave the user staring at a dead relic.

Ownership is claimed before the wait, so a fresh mount takes the room
over the moment it calls, and re-checked after it, so a takeover landing
while the old widget is parked mid-wait reaches the result it is already
owed instead of leaving both widgets on screen for a tick.

Also corrects the architecture doc's stale default-mode marker and its
reference to the renamed CONTRACTS table.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…back

The relay coordinates Desktop's MCP processes through fcntl file locks
and Unix sockets, so on Windows merely importing the server raises a
bare ModuleNotFoundError naming neither the product nor the platform.
The console script now enters through a thin cli module whose platform
check runs before the server is imported.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two independent relay defects on the leader's event path:

The accept handlers parked on the relay-wide stop event, which a
WebSocket failover never sets, and server.wait_closed() waits for the
handlers on Python 3.12+ — so a leader losing its socket while any
follower was connected hung in its own cleanup holding the leader lock,
its followers wired to a dead socket indefinitely. Handlers now park on
a per-leadership event that every exit sets, and close their own
writers when released.

Fanout also delivered serially, so several wedged followers stalled the
WebSocket callback for the sum of their timeouts. Deliveries now run
concurrently: however many are frozen, the event path is parked at most
one fanout timeout, and healthy followers still get the line.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A quiet read advanced next_since to this machine's clock. A local clock
ahead of the platform — or a message still committing as the read ran —
then left backend timestamps at or before the cursor, and the resume
filter dropped those messages from every later read: an addressed
message silently never displayed or answered.

Only timestamps observed from Band become the cursor now; a quiet tick
repeats the caller's own instead of inventing a newer one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- Install guide says macOS-only before any command runs, with the reason
  (fcntl locks, Unix sockets) and the behaviour on Windows.
- The quantum is the configured default (5 s, ceiling 15), not an
  adaptive 5/10 split; the typed-input beat is one quantum.
- Room creation is the one mutation band-mcp does not perform:
  band_create_and_open_room creates the room itself.
- The tool table lists band_show_room; band_refresh_room_view and its
  Refresh button no longer exist, and the OPEN_ROOM operation name is
  gone with the registry it belonged to.
- There is no no-REST tick to prove: every tick reads, and the cursor
  only ever carries platform-observed timestamps.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- Prerequisites up front: macOS-only with the reason, Claude Desktop and
  uv install links, and the agent key as its own section.
- Agent key sourcing: issued at registration in the Band app, agent key
  vs user key, one consumer per key, production vs self-hosted
  BAND_BASE_URL.
- Released install path first; the editable checkout is the developer
  variant.
- Verification split into quick check and full validation; remount via
  'show the room' added.
- Day-to-day section gains restart recovery and in-chat mode switching;
  upgrade/uninstall section added.
- Troubleshooting: restart-killed widget row; the dedup row no longer
  claims the cursor advances on quiet ticks (it no longer does); the
  on-demand contract distinguished from a stopped watch loop.
- Legacy band-peer plugin check scoped to users who also run Claude
  Code.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…nment

Observed live: the room_first contract only mapped 'answered first / on
demand' phrasing to the mode switch, so 'stop monitoring' — the natural
words — read as a third mode. The model stopped calling without telling
the server: the room went silently unwatched, staleness nagged against
the user's own choice, and turn-start sweeps never began, so a waiting
mention sat unseen through a full user turn.

The room_first exit line now names the stop phrasings and says there is
no abandoned mode; the staleness notice carries the one exception to
its resume order — a user-requested stop is recorded by switching to
user_first, which also ends the notice. Docs and setup skill state that
stopping still sweeps every turn start.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Quiet ticks shed the briefing while still carrying the mode, so after a
switch the widget's cache holds the old mode's contract and re-syncs it
into model context on every display tick — after 'stop monitoring' the
model keeps reading 'resume before anything else', on the exact path the
stop fix prescribes, and user_first disarms the staleness notice that
could have corrected it. A payload that switches the mode now
invalidates the cached briefing instead of letting the old orders speak
on.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…modes

The stop-to-user_first mapping lived only in the room_first briefing and
the staleness notice; the always-visible schema vocabulary
(ATTENTION_CHOICE) still named only the entry triggers, so a
conversation running without a briefing in context could rediscover the
third-mode misreading. The notice's stop exception also verified itself
— nothing said the user's words are the only key, leaving a stalled
agent free to rationalize the switch. ATTENTION_CHOICE now names the
stop triggers, the notice states that a stall is not a stop order, and
the AttentionContract docstring names the other surfaces the mode
vocabulary speaks from instead of claiming CONTRACTS is the whole
tuning surface.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The turn-start sweep is prompted behaviour with no server-side
enforcement, and health detection is deliberately disarmed in
user_first — so 'mentions wait at most until you next say anything'
promised a bound nothing can hold. Say what is instructed, and point at
the widget's waiting counter as the honest signal.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Join and create refreshed the profile before briefing; show reused the
cached viewer, so a remount after the profile changed under a
long-running process misnamed the agent and mis-matched its mentions.
The refresh now belongs to the mounts_view property in the workflow
executor: every tool that re-briefs the room re-reads the identity it
briefs, and the monitor loop keeps its cheap cached reads.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…test

band_room_stale_grace_s is a float field, so
(STALE_GRACE_S + quantum) // quantum stayed a float and range() rejected it
with TypeError: 'float' object cannot be interpreted as an integer.
# Conflicts:
#	.github/workflows/ci.yml
@AlexanderZ-Band
AlexanderZ-Band force-pushed the feat/intel-claude-desktop-claudeai-band-integration-ban-INT-1116 branch from 0266c4f to e1bd785 Compare July 31, 2026 16:09
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.

2 participants