A crowd that does not agree with whoever spoke last
Three fixes to how a round behaves, found by reading one real round on the live instance rather than by testing it. A member asked "what would you have done differently" answered the person's original question again instead of critiquing what was already there. Fine on a question with one answer; on a request to *make* something it is an invitation. `crowd.turn` now says to respond to what is above and not to re-answer. The model that opened the round, told to write the final answer and take what the others got right, abandoned its own good answer and adopted the newcomer's position with no argument anywhere for why. Both closing fragments now say that an answer is not the worse one for having been written first, and that agreement with no argument behind it is not a reason to change. That second one is not cosmetic: all three answers from the observed round were compiled. The original and the critic's alternative both build; the merged answer that was actually delivered does not. A crowd's failure mode is not looping -- the caps handle that -- it is converging on the last thing said. Third, the reply that opens a round now carries a chip like every other one. It is the single contribution the crowd does not start, so there was nothing to stamp it with until the round began, and a two-model round rendered as an unmarked reply followed by one saying "2 of 2". The stamp is display state and never scheduling state: `crowd.scheduling_state` hides it from everything that decides what happens next, because fed to the scheduler it would inherit the round's clock -- regenerating the opening an hour later would end the round with "out of time" before anybody spoke -- and would hand that reply a member's tools and a member's instruction. And the chip was never translated. It is now, with the count as placeholders rather than three t() calls around one sentence. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -661,7 +661,10 @@ async def _run(generation: Generation) -> None:
|
||||
# once, here, and used for three decisions: which tools it may have,
|
||||
# which instruction closes its request, and whether it may ask for
|
||||
# another round.
|
||||
crowd_state = crowd_service.state_of(message)
|
||||
# `scheduling_state` for the reason `build_request` gives: the
|
||||
# opening reply's stamp is for the transcript, and regenerating it
|
||||
# must not hand it a member's tools or a member's instruction.
|
||||
crowd_state = crowd_service.scheduling_state(message)
|
||||
crowd_settings = settings_store.crowd(db)
|
||||
may_ask_again = bool(
|
||||
crowd_state is not None
|
||||
@@ -2232,7 +2235,11 @@ def _advance_crowd(generation: Generation) -> bool:
|
||||
speakers = crowd_service.member_speakers(db, chat, owner_user)
|
||||
speakers = speakers[: int(settings["max_models"]) + 1]
|
||||
|
||||
state = crowd_service.state_of(message)
|
||||
# `scheduling_state` and not `state_of`: the opening reply carries a
|
||||
# stamp for the transcript's sake (so it can say `1 of 3`), and that
|
||||
# stamp must not read as "a round is already running" -- it would
|
||||
# inherit the old clock on a regenerate. See `crowd.is_opening`.
|
||||
state = crowd_service.scheduling_state(message)
|
||||
# The turn a round belongs to: the user message this all answers.
|
||||
turn_id = state.turn if state is not None else _turn_anchor(db, message)
|
||||
following = crowd_service.next_turn(
|
||||
@@ -2254,6 +2261,22 @@ def _advance_crowd(generation: Generation) -> bool:
|
||||
db.commit()
|
||||
return False
|
||||
|
||||
if state is None:
|
||||
# The round begins here, so stamp the reply that opened it. It is
|
||||
# the only contribution that is not started by the crowd, and
|
||||
# before this it was the only one with no chip -- which made a
|
||||
# two-model round read as an ordinary reply followed by a crowd,
|
||||
# and left the reader counting "2 of 2" with no 1 in sight. Same
|
||||
# turn and same `started_at`, so the bubbles group.
|
||||
message.crowd_json = crowd_service.Turn(
|
||||
turn=following.turn,
|
||||
round=following.round,
|
||||
phase=crowd_service.PHASE_OUT,
|
||||
index=0,
|
||||
of=following.of,
|
||||
started_at=following.started_at,
|
||||
).as_json()
|
||||
|
||||
speaker = speakers[following.index]
|
||||
placeholder = chat_service.create_message(
|
||||
db,
|
||||
@@ -2281,10 +2304,14 @@ def _opens_the_turn(message: Message) -> bool:
|
||||
"""Whether this reply is the first one answering a question.
|
||||
|
||||
True for every ordinary reply, and for a crowd only for the main model's
|
||||
opening turn -- which is the one with no crowd state on it at all, because a
|
||||
round begins when that reply *finishes*.
|
||||
opening turn. That reply has no crowd state while it is being written -- a
|
||||
round begins when it *finishes* -- and once the round has begun it carries the
|
||||
opening stamp, which `is_opening` reads as "still the one that opens the
|
||||
turn". Both are the same answer to this question, and missing the second means
|
||||
a reply that has already been compacted-for and titled gets it again on the
|
||||
next look.
|
||||
"""
|
||||
return crowd_service.state_of(message) is None
|
||||
return crowd_service.scheduling_state(message) is None
|
||||
|
||||
|
||||
def _opens_the_turn_id(generation: Generation) -> bool:
|
||||
|
||||
Reference in New Issue
Block a user