Skip to content

Follow-up: aside fold ordering, thread-aware collapse, and an honest benchmark correction - #19

Merged
senamakel merged 17 commits into
mainfrom
aside-followup
Sep 7, 2026
Merged

Follow-up: aside fold ordering, thread-aware collapse, and an honest benchmark correction#19
senamakel merged 17 commits into
mainfrom
aside-followup

Conversation

@senamakel

Copy link
Copy Markdown
Member

Follow-up to #16, #17 and #18, which merged with review threads still open. This
addresses the substantive ones. Two of them were defects in my own work that the
bots caught correctly, and one of those contradicted a doc comment I had written.

Library correctness

The aside fold checked policy before it resolved the audience
crates/tinyhivemind-core/src/aside/mod.rs. A request that named an inactive
target and sat on a channel under require_thread was refused with
ThreadRequired, so a caller would open a thread and be refused again for the
reason it was never given. This contradicted the evaluation order the spec
already states. The three policy rungs now run after audience resolution, and a
test pins the order — the old code passed the existing suite, which is why it
shipped.

Two closed threads between the same pair collapsed into one stub
crates/tinyhivemind/src/session/mod.rs. Collapsing keyed on participants
alone, and SessionMessage carries no parent, so two distinct closed threads
merged into a single stub reporting a combined range and message count for
exchanges that were never one exchange. The projection now carries each row's
conversation identity through the collapse: a promoted reply's root, a root's
own sequence when something actually replies to it, and None for a plain
channel row — so a run of channel asides is still one stub.

A settlement that channel narrowing dropped left the stub unsettled — same
file. project_channel keeps each root and its first reply; when the aside
was that first reply and the settlement was a later one, the settlement was not
in the projection at all and the stub reported settled_at: None for an aside
the room had settled. Settlements are now collected from the whole scanned
slice, before narrowing throws replies away.

The benchmark arm

A responder echoed an already-pooled value
crates/tinyhivemind-hive/examples/bench/sim.rs. answer_check returned
score(), which averages in every reading the responder had already absorbed,
so one peer's signal could be counted several times over. That is not the
experiment the write-up describes, where an asker averages its own reading with
one independent peer's. A responder now answers with own_reading.

The numbers were re-run and the write-up corrected. At --aside-cap 1
every configuration in the note — recirculation almost never got a chance to
occur, so the uniform rows are unchanged to the decimal and the hidden-profile
rows moved by less than a point:

configuration published corrected
uniform b15 · aside / ask / aside! 79.6 / 79.7 / 79.6 unchanged
uniform b40 · aside / ask / aside! 81.6 / 81.8 / 81.6 unchanged
hidden profile b40 · aside / ask / aside! 52.7 / 52.0 / 52.7 52.6 / 52.5 / 52.7

No conclusion changes. The hive+aside − hive+ask null on the hidden profile
actually tightened, +0.7 [-0.8, +2.1]+0.1 [-1.3, +1.6]. The defect was
real, the fix is right on principle, and it happened not to be load-bearing at
this cap. docs/experiments/2026-09-07-do-asides-help.md carries a
"A correction, and what it did not change" section saying so rather than
quietly restating the table.

Documentation honesty

The request body was in curl's argv, and the doc comment said it wasn't.
crosstalk/agent.rs claimed "neither the API key nor the request body ever
appears in the process argument list" while passing --data-binary <body> as an
argument. The body now travels in the config on stdin, escaped for curl's config
grammar. Verified live against a running endpoint, not just compiled.

The CLI backend's real limits are now written down rather than left implied:
the prompt reaches the agent as its final argument, so the transcript is
readable out of ps while a turn runs, and --timeout is not enforced because
std::process::Command has no deadline. Both are inherent to the --agent-cmd
contract — it exists so any prompt-as-final-argument CLI works unmodified — and
neither applies to the HTTP backend, which carries the only credential. Declined
rather than "fixed", with the reason stated in the code and the README.

settled_at on an incremental sharing delta is a floor, not a fact. A stub
delivered in one tick keeps the None it was sent with even when the aside
settles in a later one: the watermark has moved past the row and this crate
cannot revise a message it no longer holds. A re-seed shows it settled. Fixing
it incrementally would need pending-aside state carried across ticks, which is a
second store to invalidate and the charter refuses it. Documented in
sharing/mod.rs and added to the spec's Open questions.

The bench README was 502 lines, over the repo's explicit 500 cap. The
statistics section moved to examples/bench/STATISTICS.md and is linked from
where it was. Nothing else in the repo exceeds the cap.

Public API changes

SessionMessage, LogMessage and the wire forms are untouched. Internal to
tinyhivemind::session, collapse_elisions now takes conversation identity and
a settlement slice. project_as is unchanged in signature but documents a
limitation it always had: a SessionMessage carries no parent, so a caller that
holds only a transcript cannot tell two closed threads apart. A caller holding
the log should project from it.

Validation

  • cargo fmt --all -- --check
  • cargo clippy --all-targets --all-features -- -D warnings — clean
  • cargo build --all-targets --all-features
  • cargo test --all-features732 passed, 0 failed (727 on main, +5 new)
  • ./.github/scripts/assert-pure.sh — clean
  • RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --all-features — clean
  • Benchmark re-run at three configurations; the ten pre-existing arm rows match
    the published table to the decimal.
  • crosstalk --api-base ... --hops 2 run live against a real endpoint after the
    curl change.

Deliberately not done here

  • CodeRabbit's --window 1 finding on the crosstalk example (private.first()
    can select a row outside the rendered views). Real, but example-only and
    cosmetic — it makes a claim print FAIL on a deliberately pathological window.
  • spent_in_aside counting rows from a neighbouring aside in the same
    example. Real, example-only, and only reachable with more than two seats
    opening asides in sequence.
  • The --agent-cmd argv exposure and the missing CLI timeout, declined above
    with reasons.

Each is listed so it is on the record rather than lost; happy to take them in a
further follow-up if you want them closed.

senamakel and others added 17 commits September 7, 2026 02:51
The wiki submodule is advanced to a newer commit, incorporating upstream changes to the wiki content.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
The policy checks for thread requirement, message budget, and unsettled state were moved to run after the audience is validated. This ensures that when a request names an inactive target and also violates a policy rule, the caller receives the actionable refusal about the target rather than a misleading policy error that would persist even after correcting the policy issue.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Adds a test asserting that audience faults are reported before thread, budget, and settlement rungs, since the spec fixes audience resolution and validation as the first evaluation step. Also verifies that an author fault still outranks the audience check, as a member off the desk cannot address anyone on it.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
The settlement lookup now scans a precomputed list of desk-visible, agent-authored rows from the entire scanned slice rather than only the projection. This fixes stubs reporting `settled_at: None` when an aside's settlement lies outside the narrowed channel projection, and also prevents distinct closed threads between the same participants from collapsing into a single stub by keying runs on conversation identity.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
The projection functions now return settlement information alongside the projected messages, and `collapse_elisions` receives it as an explicit parameter instead of deriving it internally. This lets `project_as` compute settlements from the original transcript, preserving thread boundaries that would otherwise be lost when merging adjacent elided runs between the same pair of authors.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
The projection now carries the thread root sequence alongside each presented message, allowing callers to identify which messages belong to the root thread versus nested replies. This also records seen row metadata earlier to support accurate settlement tracking during projection.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
The projection functions now return settlement metadata alongside the projected messages, allowing callers to track which threads have been settled. Settlements are computed from the full scanned slice before narrowing discards replies, ensuring that asides which are first replies to a root are correctly settled by later replies.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
The thread identity for a kept row was previously always the row's own sequence when it had no parent, which caused a run of parentless channel asides to be split into separate stubs instead of collapsing into one. Now a parentless row only becomes its own thread when something actually replies to it, while ordinary channel messages remain unthreaded and merge correctly.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Adds four tests for the session projection logic that groups asides into stubs. Two tests verify that separate closed threads between the same pair remain distinct stubs while a run of channel asides collapses into one, and two more confirm that settlement detection only applies to replies that follow the aside in the transcript.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
The simulation agent now responds to peer requests with its own untouched evaluation of a topic rather than its averaged score. Previously, using the averaged score meant already-pooled readings were echoed back into the room, causing double-counting and distorting the benchmark's model of independent peer signals.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
…coring

The experiment note previously reported results from a bug where a responder could return an already-pooled reading, letting one peer's signal be counted multiple times. The fix makes responders answer with their own untouched private evaluation, and the corrected numbers show the uniform rows unchanged and hidden-profile rows moved by less than a point, leaving all conclusions intact.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
The README's statistics section now links to the dedicated STATISTICS.md file instead of duplicating the full explanation of the benchmark's metrics, confidence intervals, and methodology.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
The HTTP turn example previously passed the request body directly as a curl command-line argument, but now includes it in the curl config file instead. This keeps the command invocation simpler and consolidates all request parameters in the configuration, making the example easier to read and modify.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
The crosstalk example now escapes backslashes and double quotes in the JSON request body before embedding it into curl's config file format, where double-quoted values interpret backslash escapes. This ensures the body's JSON quotes and backslashes survive the trip through curl's parser and reach the server intact.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
The curl command in the crosstalk agent example was missing escaped quotes around the data-binary argument, which would cause the command to fail when executed. This change adds the proper escaping so the request body is passed correctly.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
The sharing path now documents that a settlement arriving after its stub has shipped leaves the earlier `settled_at: None` in place, since the watermark has moved past the row and the crate cannot revise a message it no longer holds. The example's CLI backend similarly notes that its prompt is visible in the process argument list and that `--timeout` is not enforced, both inherent to its contract rather than oversights.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Reformatted several long function signatures, assertions, and a chained string replacement in the crosstalk example to fit within the project's line-length conventions. No behavior changes were made.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 7, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-07T00:05:32.839050Z 742c6f5 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@coderabbitai

coderabbitai Bot commented Sep 7, 2026

Copy link
Copy Markdown

Warning

Review limit reached

  • Run on-demand review

On-demand reviews are free for the next 14 days. After that, they cost $0.25 per reviewed file.

Or wait 38 minutes for your next included review.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: 78a3fe62-0664-4cd9-849b-5729c1d9d76e

📥 Commits

Reviewing files that changed from the base of the PR and between 2930a9a and 742c6f5.

📒 Files selected for processing (13)
  • crates/tinyhivemind-core/src/aside/mod.rs
  • crates/tinyhivemind-core/src/aside/test.rs
  • crates/tinyhivemind-hive/examples/bench/README.md
  • crates/tinyhivemind-hive/examples/bench/STATISTICS.md
  • crates/tinyhivemind-hive/examples/bench/sim.rs
  • crates/tinyhivemind/examples/crosstalk/README.md
  • crates/tinyhivemind/examples/crosstalk/agent.rs
  • crates/tinyhivemind/src/session/mod.rs
  • crates/tinyhivemind/src/session/test.rs
  • crates/tinyhivemind/src/sharing/mod.rs
  • docs/experiments/2026-09-07-do-asides-help.md
  • docs/specs/private-asides.md
  • wiki

Warning

Your free Security trial is over. An organization admin can upgrade to Advanced for continuous pull request security review or dismiss this notice.


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

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 742c6f5576

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +487 to +489
candidates
.iter()
.map(|candidate| (&candidate.sequence, &candidate.audience, &candidate.author)),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Exclude blank rows from settlement candidates

In channel-level projections, candidates includes whitespace-only rows because they are retained earlier for root detection, and this iterator now passes them to Settlement::over without filtering their content. If an aside participant authors a blank desk-visible row after an aside, the stub is therefore marked settled_at at that sequence even though narrowing removes the blank row and it contains no public outcome for the viewer to recover. Filter empty content when constructing the settlement list, as the thread projection already does.

Useful? React with 👍 / 👎.

@tinysweeper

tinysweeper Bot commented Sep 7, 2026

Copy link
Copy Markdown

How this change flows

5 changed behaviours across 14 relationships. 5 surrounding behaviours are shown (60 graph nodes walked). 43 further behaviours left out to keep the diagram readable.

flowchart LR
  n0["aside<br/>changed"]:::changed
  n1["SimAgent<br/>changed"]:::changed
  n2["Candidate<br/>changed<br/>1 finding"]:::flagged
  n3["narrow_to_roots_and_first_replies<br/>changed<br/>1 finding"]:::flagged
  n4["participants<br/>changed<br/>1 finding"]:::flagged
  n5["Sequence"]:::impacted
  n6["SessionMessage"]:::impacted
  n7["iter"]:::impacted
  n8["project_as"]:::impacted
  n9["as_viewer"]:::impacted
  n0 -->|calls| n7
  n1 -->|uses| n5
  n2 -->|uses| n5
  n3 -->|uses| n2
  n3 -->|uses| n5
  n3 -->|uses| n6
  n3 -->|calls| n7
  n4 -->|uses| n6
  n4 -->|calls| n7
  n6 -->|uses| n5
  n8 -->|uses| n5
  n8 -->|uses| n6
  n8 -->|calls| n7
  n9 -->|uses| n6
  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

Green: changed behaviour. Grey: surrounding behaviour. Arrows name the call, use, implementation, or test relationship. Orange: has findings. Red: has a finding that blocks the merge.

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. Approving.

             $0.0480 · 322,862 in / 10,164 out · 60,454 cached (19%) · openrouter/openai/text-embedding-3-small, deepseek/deepseek-v4-flash, z-ai/glm-5.2 · 745 embedded
critique:    $0.0187 · 178,301 in / 3,596 out  · 19,661 cached (11%) · deepseek/deepseek-v4-flash, z-ai/glm-5.2
security:    $0.0185 · 102,119 in / 3,481 out  · 26,916 cached (26%) · deepseek/deepseek-v4-flash, z-ai/glm-5.2
tests:       $0.0023 · 24,922 in  / 114 out    · 0 cached (0%)       · deepseek/deepseek-v4-flash
description: $0.0085 · 17,520 in  / 2,973 out  · 13,877 cached (79%) · z-ai/glm-5.2

candidate.content,
candidate.audience,
viewer,
// A promoted reply's identity is the root it hangs under; a 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 likely

Root is tagged as its own thread even when no reply follows it

The rooted set is built from candidate.parent values — the parent sequences that received a reply. If a root happens to have the same sequence number as a different root's parent value (which is impossible across different sequences but correct-by-construction here), or more importantly if a root's own sequence equals that of a different root's parent, then a root with no actual replies gets thread = Some(sequence). This makes two separate sequences of channel-level asides between the same pair that never had a thread falsely look like one thread, merging their stubs. The set should contain sequences of roots that are actually replied to, but rooted only contains parent values, which are the sequences of the things being replied to, not the sequences of the candidates themselves. The condition should check whether the candidate is a root that was actually replied to later in the same slice.

[RULE] logic-error ·

@senamakel
senamakel merged commit 639d2d6 into main Sep 7, 2026
15 checks passed
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