feat(status): 待ち時間中の表示を、実イベントに裏づけられた状態表示にする - #14
Merged
Merged
Conversation
A streaming turn opens by posting a message containing "…" and edits it as
output arrives. On Discord that path is the normal one -- use_streaming()
is `!other_bot_present` (discord.rs:194), not a config flag -- so almost
every turn starts this way.
Against a hosted model the placeholder is gone in under a second. Against
a local one it is not: measured today, three bodies share one inference
server and a two-token reply took 14.6s wall for 1.2s of compute, with
turns of 53s, 86s and 198s in the logs. For all of that time the channel
shows a message whose entire content is "…", which is what a broken bot
looks like. Reported from a real thread where the answer did arrive, and
only the wait looked wrong.
The reaction machine already signals liveness, but a single emoji cannot
say which tool is running, how long it has been, or whether anything has
been heard at all -- and with tool_display = "none" (every local body)
the streaming display stays empty until the first text, so the emoji is
the only signal there is.
So the placeholder now carries a status line instead of an ellipsis. No
new message: it writes into the same watch channel the edit loop already
reads, and stops on the first text chunk so the answer owns the message
from then on. The handover flag is set synchronously in send() rather
than through the channel, or a tick could overwrite real output.
⏳ 受け取りました · 4秒
🤔 考えています · 6秒
🔧 実行中: bash: uv run python -m unittest · 21秒
… 反応がありません(直前: 考えています) · 48秒 ツール2件(失敗1)
The constraint the module is built around is that none of this may be
decoration pretending to be information:
- a phase is entered only by the event that proves it, so there is no
"thinking" before anything has been heard -- the opening line claims
only that the request was accepted, which is a fact about us
- tick() can demote to Silent and nothing else; time is not evidence of
work
- Silent names the absence of signal and recalls what was last actually
heard, instead of continuing to display stale activity as current.
Waiting and wedged look identical from outside, and the elapsed time
is the only thing that separates them
- a running tool is not silence: we were told it started and not told
it ended
- tool titles are shown verbatim; failures are counted, not dropped
Tests assert those properties directly, including that no label appears
without its event and that ticking never advances the phase.
Off by default. It changes what every message looks like, which is not
something a deployment should get without asking.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LXqeYy8DAp3BdEHxsjHJL8
|
All PRs must reference a prior Discord discussion to ensure community alignment before implementation. Please edit the PR description to include a link like: This PR will be automatically closed in 3 days if the link is not added. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
結論
streaming 中の placeholder が
…だけで、ローカルLLMの待ち時間(実測 53〜198秒)ずっとそれが表示され、故障に見えていた。演出は残したまま、表示する状態を全て実イベント由来にした。Owner報告のスクショで、HANAが
…のまま数十秒。回答自体は後から正常に届いており、壊れていたのは表示だけ。事実確認
streamingは config ではなくDiscordAdapter::use_streaming()=!other_bot_present(discord.rs:194)で決まる。Discord では既定でON。加えてadapter.rs:511-514に既知のレースがあり、スレッド初回はbotが同席していても streaming になる。スクショはこれ。実測(2026-08-02、3体が1つの推論サーバを共有):
qwen3-coder-30b-chatqwen3.6-35b-a3bhana-coder-ff711リアクション(🤔)は生存signalにはなるが、どのツールが動いているか・何秒経ったか・そもそも何か届いたかは表せない。さらにローカル3体は
tool_display = "none"のため、最初の本文が来るまで streaming 表示は空のまま。変更
placeholder の中身を状態表示にする。メッセージは増やさない(編集ループが既に読んでいる watch チャンネルへ書くだけ)。最初の本文が来た時点で書き込みをやめ、以降は回答がメッセージを占有する。
「嘘をつかない」の担保
装飾は字面とレイアウトだけ。主張部分は次の規律で縛り、テストで固定した。
it_does_not_claim_thinking_before_anything_is_heard/every_rendered_phase_was_caused_by_its_eventtick()はSilentへの降格しかしないtime_alone_never_promotes_a_phaseSilentは無signalを無signalとして言い、直前に実際に聞いた状態を添えるsilence_is_reported_as_silence/recovering_from_silence_requires_an_eventa_running_tool_is_not_silencethe_tool_name_is_shown_verbatim/tool_failures_are_not_hiddenelapsed_time_never_goes_backwardsハンドオーバーは
send()内で同期的に立てる。チャンネル経由だと tick が本文を上書きする窓が残る(text_hands_the_message_over_immediately)。状態行には zero-width space の marker を付け、
is_placeholder_display()が「回答ではなく足場」と判別できるようにした。steer 時に状態行を残さないため。文言一致だと表現を変えた瞬間に壊れる。設計上の削除
当初は「別メッセージを遅延投稿して完了時に削除」で作り、placeholder 自体を置き換える形へ変えた際に不要になった
delay_ms・本文文字数カウント・Phase::Writingを削除。本文が届いた時点で本文そのものが表示されるので、文字数を出す機会が無い。動かないコードは残さない。既定
enabled = false。全メッセージの見た目が変わるので、デプロイ側が明示的に選ぶべき変更。有効化は[live_status] enabled = true(tick_ms既定2000、silence_after_ms既定10000)。検証
cargo testcargo clippy --all-targetscargo build --releaseうち新規18件が
live_status。未対応(別issue相当)
adapter.rs:511-514の multibot レース自体は直していない。bot同席スレッドの初回が streaming になる件で、本PRの範囲外。