Hand a confided answer to a seat once - #78
Conversation
`Child::outcome` returned the askee's last line verbatim, so the row that concludes a conversation was a byte-for-byte copy of the answer. The asker already reads that answer: it is the first reply under the ask, which a channel-level read promotes, and it is in the transcript the asker is handed once in `EpisodeBrief::conversations`. Measured against the `conducted` example over a live endpoint: one seat's turn was 11,246 characters carrying each answer three times, and one seat read its own answer five times in a single prompt. The row itself stays, because the row is the mechanism -- `apply_committed` releases the asker's hold on seeing a `Dm`, so a conclusion that wrote nothing would strand the asker. Only a forced close now adds text, because running out of turns is the one ending the rows do not show. `Child::last_by_askee` went with it: after this it was written on every committed row in a conversation and read by nothing, and it travelled in every snapshot. Validation: cargo fmt --all -- --check; cargo clippy --all-targets --all-features -- -D warnings; cargo build --all-targets --all-features; cargo test --all-features (36 binaries, 1326 tests).
A row narrower than its conversation renders `@author: content`, exactly as a row the whole desk saw. On the desk that is ambiguous in the one direction that matters: a seat cannot tell what it may repeat in the open from what was said to it alone, and answers as though the room heard it. Marked only for a desk read. Inside a conversation every row is private, the brief's own heading says so, and marking each line repeats it. A seat's own rows are its turns, and a model needs no telling that it spoke in confidence. This commit does the hosted seed; the brief's own renderer follows in the next. Both, because a hosted seat clears its session and rebuilds from the log every turn, so the same row reached it marked while it was new and bare once it was remembered -- and the bare one is the copy that lasts. A desk note addressed to one seat is confided too, and now says so: a seat that cannot tell a nudge meant for it from one the room also read will answer as though everyone was nudged.
The brief's renderer marks a confided row, for the reason the previous commit gives; and because a seat the thread was confided to now reads it at channel level, the `ConversationView` that carried it is a second copy of the same lines. The two are one change. The check is whether the rows are already in this turn's delta, and comparing them means rendering both sides the same way -- so `rows_above` takes the marking as a parameter rather than deriving it, and the thread is re-read in the desk's own spelling to ask the question. Separated, the check compares a row against itself in two spellings and never matches. Only a concluded conversation's view is dropped. One still running carries something its rows cannot: that it is still running. Suppressing that too was measured -- a live run refused five calls against another's one, a seat reading the exchange inline, taking it for finished, and trying to complete twice. Decided per conversation against what the turn is actually shown, not by a policy flag. A host whose rows carry no audience promotes only the first reply, its later lines are missing from the delta, and it keeps the view it has always had. No host regresses. Four live runs each side, same task and model: turns 8.75 against 8.50 (baseline range 6-11), refusals 2.50 against 3.00, and nine rows produced in all eight runs. One answer now reaches a seat once rather than three times.
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. Note Currently processing new changes in this PR. This may take a few minutes, please wait... ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (7)
Comment |
Tiny Sweeper reviewTiny Sweeper reviewed this change across 6 lane(s) and found 2 active actionable finding(s). Detailed lane evidence and any incomplete work are listed below. State: Incomplete Review snapshot
Completeness: Incomplete What changedThe review could not produce a supported behavioral summary; inspect the cited changed surface and lane details below. FeaturesNone identified with supported citations. TestsNo supported feature-to-test mapping was produced. Test execution is not inferred. Findings
Could not review: tinysweeper/description Before merge
How this fits togetherflowchart LR
n0["Journal<br/>changed<br/>2 findings"]:::flagged
n1["rows_above<br/>changed<br/>2 findings"]:::flagged
n2["the_journal_saw_each_turn<br/>changed"]:::changed
n3["...at_it_was_shown_its_own_rows_as_its_turns<br/>changed"]:::changed
n4["Sequence"]:::impacted
n5["history"]:::impacted
n6["iter"]:::impacted
n7["drive"]:::impacted
n8["...the_seat_was_not_addressed_on_is_withheld"]:::impacted
n9["desk"]:::impacted
n0 -->|uses| n4
n1 -->|uses| n4
n1 -->|calls| n6
n2 -->|calls| n6
n2 -->|tests| n6
n3 -->|calls| n4
n3 -->|tests| n4
n3 -->|calls| n5
n3 -->|tests| n5
n3 -->|calls| n9
n3 -->|tests| n9
n5 -->|calls| n4
n5 -->|uses| n4
n5 -->|calls| n6
n7 -->|uses| n0
n7 -->|calls| n6
n8 -->|calls| n4
n8 -->|tests| n4
n8 -->|calls| n5
n8 -->|tests| n5
n8 -->|calls| n6
n8 -->|tests| n6
n8 -->|calls| n9
n8 -->|tests| n9
classDef changed fill:#0d4429,stroke:#238636,color:#e6edf3
classDef impacted fill:#161b22,stroke:#6e7681,color:#c9d1d9
classDef flagged fill:#5a1e02,stroke:#d93f0b,color:#ffffff
classDef blocking fill:#67060c,stroke:#f85149,color:#ffffff
Agent review detailscritique
security
tests
commits
description
e2e
Evidence and run details
|
There was a problem hiding this comment.
tinysweeper found nothing blocking, but could not review everything, so this is not an approval: tinysweeper/description.
$0.0115 · 280,342 in / 16,112 out · 11,997 cached (4%) · ladder/vectors, gpt-5.6-luna, deepseek/deepseek-v4-flash, deepseek-v4-flash · 799 embedded
critique: $0.0051 · 128,051 in / 5,619 out · 6,448 cached (5%) · gpt-5.6-luna, deepseek/deepseek-v4-flash
security: $0.0046 · 125,038 in / 4,284 out · 5,549 cached (4%) · gpt-5.6-luna
tests: $0.0005 · 20,395 in / 2,259 out · 0 cached (0%) · deepseek-v4-flash
| let fresh = rows_above(log, &thread, &turn.seat, turn.since, latest, true).await?; | ||
| if fresh.iter().all(|row| rows.contains(row)) { | ||
| carried.insert(root); |
There was a problem hiding this comment.
Require a non-empty fresh read before suppressing the conversation
When fresh is empty, iter().all(...) returns true, so this marks the conversation as carried even though no rows were found in the desk read. For a concluded conversation with no rows newer than turn.since, the later retain removes its ConversationView, while the desk rows contain no transcript either; the seat therefore loses the conversation entirely. Require fresh to be non-empty before treating it as carried, or compare the complete conversation projection rather than an empty incremental slice.
Additional security observation
Compare conversation rows by identity, not rendered text
[RULE] ambiguous-duplicate-detection
This treats a conversation as already carried whenever every rendered line from fresh appears anywhere in the desk's rendered rows. Identical author/content pairs from unrelated rows can satisfy this condition, and contains does not account for multiplicity, so a live conversation's transcript can be omitted even though the desk rows do not represent that conversation. Track the underlying sequence/root identity (or otherwise perform a multiplicity-aware, conversation-scoped comparison) before suppressing the conversation view.
Suggested change for the opening observation
| let fresh = rows_above(log, &thread, &turn.seat, turn.since, latest, true).await?; | |
| if fresh.iter().all(|row| rows.contains(row)) { | |
| carried.insert(root); | |
| if !fresh.is_empty() && fresh.iter().all(|row| rows.contains(row)) { | |
| carried.insert(root); |
[RULE] empty-collection-vacuous-truth ·
Summary
A seat that asks a peer reads the answer three times. Measured against the
conductedexample over a live endpoint: one seat's turn was 11,246characters carrying each answer three times, and one seat read its own
answer five times in a single prompt.
Three deliveries, each added when the others did not exist:
ConversationViewinEpisodeBrief::conversations.This keeps (1) — chronological, in the desk's own order, and the only one a
HostedRunnerstill holds next turn — and removes the other two for aconversation that has concluded. A confided row now says it was confided,
because reaching a seat inline it is otherwise indistinguishable from
something the whole desk saw.
Related issue
None.
API or behavior changes
Child::outcomereturns text only for a forced close. The conclusion rowis still written, because that row is the mechanism —
apply_committedreleases the asker's hold on seeing a
Dm— but it no longer carries theanswer. Behaviour change for a host that renders that row.
Child::last_by_askeeremoved: after the above it was written on everycommitted row in a conversation and read by nothing, and it travelled in
every snapshot. It is not in the fields
conduct::test::wirepins.@author (privately): …on adesk read, in both the brief and the hosted seed. Unmarked inside a
conversation, where every row is private and the heading says so.
longer handed over again as a
ConversationView. One still running alwaysis: it carries something its rows cannot, that it is still running.
Decided per conversation against what the turn is actually shown, not by a
policy flag — a host whose rows carry no audience promotes only the first
reply, its later lines are missing from the delta, and it keeps the view it
has always had. No host regresses.
Validation
cargo fmt --all -- --checkcargo clippy --all-targets --all-features -- -D warningscargo build --all-targets --all-featurescargo test --all-features— 36 binaries, 1326 tests.github/scripts/assert-pure.sh— cleanLive,
examples/openhuman --bin conducted,TINYHIVEMIND_RUNNER=raw, fourruns per side against the same task and model:
No cost in turns, and one answer reaches a seat once rather than three
times. An earlier revision suppressed in-progress views too and cost real
turns — 13 against a 6–11 baseline, five refusals to one, a seat reading
the exchange inline and trying to complete twice. That is why only
concluded views are dropped, and the A/B above is the check that the
regression is gone.
Tests
conduct::test::conversations— the private row says the conversationended and does not carry the answer again.
episode::test::flow— the answer reaches the asker's desk turn exactlyonce, says it was confided, and carries no second copy under a heading.
hosted::test— a note addressed to one seat seeds as private, the samespelling the brief gives it.
Untested deliberately: the marking is not asserted for
elsewhererows.Those are another desk's, read under that desk's own heading, and are
rendered unmarked on purpose.
Documentation
Behaviour is documented at each seam it changes —
Child::outcome,render,seed::turn, and the suppression inopen_turn— each saying what wasmeasured and why the narrow form was chosen. No
docs/orwiki/pagedescribes the three deliveries, so none went stale.
Checklist
#[allow(...)],#[ignore], or relaxed lints.envcontents in the diff or the descriptionSummary by CodeRabbit