Skip to content

Hand a confided answer to a seat once - #78

Merged
sanil-23 merged 3 commits into
tinyhumansai:mainfrom
sanil-23:feat/one-copy-of-a-confided-answer
Sep 24, 2026
Merged

sanil-23 merged 3 commits into
tinyhumansai:mainfrom
sanil-23:feat/one-copy-of-a-confided-answer

Conversation

@sanil-23

@sanil-23 sanil-23 commented Sep 24, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

A seat that asks a peer reads the answer three times. 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.

Three deliveries, each added when the others did not exist:

  1. the reply under the ask, promoted to channel level for a party since Let a seat read the whole of a thread confided to it #75;
  2. the conclusion row, which restated the askee's last line verbatim;
  3. the ConversationView in EpisodeBrief::conversations.

This keeps (1) — chronological, in the desk's own order, and the only one a
HostedRunner still holds next turn — and removes the other two for a
conversation 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::outcome returns text only for a forced close. The conclusion row
    is still written, because that row is the mechanism — apply_committed
    releases the asker's hold on seeing a Dm — but it no longer carries the
    answer. Behaviour change for a host that renders that row.
  • Child::last_by_askee removed: after the above it was written on every
    committed row in a conversation and read by nothing, and it travelled in
    every snapshot. It is not in the fields conduct::test::wire pins.
  • A row narrower than its conversation renders @author (privately): … on a
    desk read, in both the brief and the hosted seed. Unmarked inside a
    conversation, where every row is private and the heading says so.
  • A concluded conversation whose rows are already in this turn's delta is no
    longer handed over again as a ConversationView. One still running always
    is: 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 -- --check
  • cargo clippy --all-targets --all-features -- -D warnings
  • cargo build --all-targets --all-features
  • cargo test --all-features — 36 binaries, 1326 tests
  • .github/scripts/assert-pure.sh — clean
  • each of the three commits builds and passes on its own

Live, examples/openhuman --bin conducted, TINYHIVEMIND_RUNNER=raw, four
runs per side against the same task and model:

turns rows refusals
before 8.50 (range 6–11) 9 every run 3.00
after 8.75 (range 8–9) 9 every run 2.50

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 conversation
    ended and does not carry the answer again.
  • episode::test::flow — the answer reaches the asker's desk turn exactly
    once, says it was confided, and carries no second copy under a heading.
  • hosted::test — a note addressed to one seat seeds as private, the same
    spelling the brief gives it.

Untested deliberately: the marking is not asserted for elsewhere rows.
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 in open_turn — each saying what was
measured and why the narrow form was chosen. No docs/ or wiki/ page
describes the three deliveries, so none went stale.

Checklist

  • The change is focused on one logical change
  • No new #[allow(...)], #[ignore], or relaxed lints
  • No secrets, tokens, or .env contents in the diff or the description

Summary by CodeRabbit

  • Improvements
    • Conversation conclusion notifications now identify the thread without repeating its answer; the answer remains available in the conversation transcript.
    • Private notes shown in desk updates are now clearly marked as private.
    • Answers already shown in desk updates are no longer repeated in the recent-conversations section.
    • Conversations that do not conclude in time now receive a clear notice to proceed with the discussion so far.

`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.
@coderabbitai

coderabbitai Bot commented Sep 24, 2026

Copy link
Copy Markdown

Review in Change Stack →

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 configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 7a3c427a-f424-4c45-b968-1b942f674478

📥 Commits

Reviewing files that changed from the base of the PR and between f3c3af9 and 8a67276.

📒 Files selected for processing (7)
  • crates/tinyhivemind-driver/src/conduct/child.rs
  • crates/tinyhivemind-driver/src/conduct/test/conversations.rs
  • crates/tinyhivemind-driver/src/conduct/wave.rs
  • crates/tinyhivemind-openhuman/src/episode/mod.rs
  • crates/tinyhivemind-openhuman/src/episode/test/flow.rs
  • crates/tinyhivemind-openhuman/src/hosted/seed.rs
  • crates/tinyhivemind-openhuman/src/hosted/test.rs
 ______________________________________________________________________________________________________
< What one programmer can do in one month, two programmers can do in two months. - Frederick P. Brooks >
 ------------------------------------------------------------------------------------------------------
  \
   \   \
        \ /\
        ( )
      .( o ).

Comment @coderabbitai help to get the list of available commands.

@sanil-23
sanil-23 merged commit 071e676 into tinyhumansai:main Sep 24, 2026
4 of 5 checks passed
@tinysweeper

tinysweeper Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

Tiny Sweeper review

Tiny 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
Priority: medium
Reviewed head: 8a6727641d86
Updated: 1790210659 (Unix time)

Review snapshot

Change surface Files Review signal Count
Production 4 Active findings 2
Tests 3 Noted findings 0
Documentation 0 Resolved findings 0
Configuration 0 Pending checks/questions 1

Completeness: Incomplete
Test assessment: No supported feature-to-test mapping was available; this does not mean tests are absent or passed.

What changed

The review could not produce a supported behavioral summary; inspect the cited changed surface and lane details below.

Features

None identified with supported citations.

Tests

No supported feature-to-test mapping was produced. Test execution is not inferred.

Findings

  • medium · critique · 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 wit (crates/tinyhivemind\-openhuman/src/episode/mod\.rs:462)
  • medium · security · Compare conversation rows by identity, not rendered text — 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 unrelate (crates/tinyhivemind\-openhuman/src/episode/mod\.rs:463)

Could not review: tinysweeper/description

Before merge

  • Complete the description review for tinysweeper/description.

How this fits together

flowchart 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
Loading
Agent review details

critique

  • Conclusion: Success
  • Scope reviewed: all assigned evidence
  • Lane summary: Reviewed 6 files; 1 finding. _The code index is behind this pull request (indexed at `ddaf106e68c8`), so retrieved context may be out of date._ _2 memory call(s) failed (model: cortex: v1/answer: timed out after 20s), so this review saw part of what the engine holds._
  • Evidence: crates/tinyhivemind\-openhuman/src/episode/mod\.rs — Require a non-empty fresh read before suppressing the conversation

security

  • Conclusion: Success
  • Scope reviewed: all assigned evidence
  • Lane summary: Reviewed 6 files; 1 finding. (1 observation(s) grouped into shared inline comments) _The code index is behind this pull request (indexed at `ddaf106e68c8`), so retrieved context may be out of date._ _2 memory call(s) failed (model: cortex: v1/answer: timed out after 20s), so this review saw part of what the engine holds._
  • Evidence: crates/tinyhivemind\-openhuman/src/episode/mod\.rs — Compare conversation rows by identity, not rendered text

tests

  • Conclusion: Success
  • Scope reviewed: all assigned evidence
  • Lane summary: Removes duplicate conversation blocks when the desk read already carries the private rows, marks confided rows as private on the desk, and simplifies conversation conclusion messages. Tests are updated to verify the deduplication. The change is safe to merge. _The code index is behind this pull request (indexed at `ddaf106e68c8`), so retrieved context may be out of date._ _2 memory call(s) failed (model: cortex: v1/answer: timed out after 20s), so this review saw part of what the engine holds._

commits

  • Conclusion: Neutral
  • Scope reviewed: all assigned evidence
  • Lane summary: Nothing sensitive found in what this pull request commits.

description

  • Conclusion: Neutral
  • Scope reviewed: incomplete; unanswered: tinysweeper/description
  • Lane summary: No reviewer could be consulted.

e2e

  • Conclusion: Neutral
  • Scope reviewed: all assigned evidence
  • Lane summary: No end-to-end harness in this repository: no e2e test files and no e2e workflow.
Evidence and run details
  • Models: ladder/vectors, gpt-5.6-luna, deepseek/deepseek-v4-flash, deepseek-v4-flash
  • Spend: $0.011514
  • Tokens: 280342 input · 16112 output · 11997 cached · 799 embedding
Head State Pass summary
8a6727641d86 incomplete 2 active finding(s), 0 resolved finding(s) (at 1790210659)

tinysweeper 0.1.0

@tinysweeper tinysweeper Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment on lines +462 to +464
let fresh = rows_above(log, &thread, &turn.seat, turn.since, latest, true).await?;
if fresh.iter().all(|row| rows.contains(row)) {
carried.insert(root);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

priority medium critique confident

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

priority medium confident

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

Suggested change
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 ·

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant