feat: cross-conversation context for a seat, and collapse private asides - #2247
feat: cross-conversation context for a seat, and collapse private asides#2247sanil-23 wants to merge 273 commits into
Conversation
Auto-committed-on: macbook Co-authored-by: Medulla <medulla@tinyhumans.ai>
…rc/onboarding/OnboardingGate.ts Auto-committed-on: macbook Co-authored-by: Medulla <medulla@tinyhumans.ai>
…t.ts Auto-committed-on: macbook Co-authored-by: Medulla <medulla@tinyhumans.ai>
…t.ts Auto-committed-on: macbook Co-authored-by: Medulla <medulla@tinyhumans.ai>
…rc/onboarding/OnboardingGate.ts Auto-committed-on: macbook Co-authored-by: Medulla <medulla@tinyhumans.ai>
Update onboarding gate logic and state handling to keep the flow consistent. Auto-committed-on: macbook Co-authored-by: Medulla <medulla@tinyhumans.ai>
Persist onboarding dismissal so skipped setup does not reappear in later tabs, while clearing stale dismissal after activation. Add Chargebee-specific pitch and credits messaging, hiding inactive invoice controls until a company is configured. Auto-committed-on: macbook Co-authored-by: Medulla <medulla@tinyhumans.ai>
Auto-committed-on: macbook Co-authored-by: Medulla <medulla@tinyhumans.ai>
A seat took its turn holding one conversation: the desk in front of it. An agent seated on two desks, or holding its own direct line, answered on one of them knowing nothing of the others — and re-seeded rather than remembered, because `SharingState` is a single conversation and a single watermark. This gives every speaker its *other* conversations in its prompt: each desk it also sits on, and its own direct line, each already projected for it. Four constraints shape it, and each is load-bearing: **Projected as the speaker, never as the operator.** `Viewer::Agent`, so an aside it is not in arrives elided here exactly as it would at home. Passing `Viewer::Operator` would hand a seat every private row in the company. **Out of the fold.** `step` still folds only the deliberating conversation, so a row from elsewhere can be quoted and can never move an option towards a decision. Information crosses a boundary; support does not — the same bargain an aside strikes and a referral's answer strikes. **The deliberating desk is excluded from the load.** It comes from `project_for`, not from this. Loading it in full would re-insert the peer rows a blind round withheld while quorum kept counting as though independence held: the appearance of it with none of the property, which is worse than not having it. **Per conversation, never merged.** Two desks number their rows independently, so a merged `[7]` would name two different messages. `company_desks` is split out of `desk_federation` because that gates on the home desk's referral opt-in, and whether a seat may *ask* another desk a question is not the same permission as whether it can already *read* a desk it sits on. Unset renders nothing, including no read of the direct line, so every caller that existed before is byte-identical.
Add coverage for the finance company switching behavior to verify it works as expected. Auto-committed-on: macbook Co-authored-by: Medulla <medulla@tinyhumans.ai>
A seat writes its move and may add one `!aside @peer` line beneath it. The host journals that line as its own row carrying an `audience`, and the projection had no way to tell it from an ordinary reply — so it reached the operator's transcript verbatim, `!aside @refunds` and all, addressed to a room it was never addressed to. Two defects in one row, and they need different fixes. **The grammar leaked.** `MOVE_KINDS` deliberately omits `aside` — a marker the fold discards is not a move anybody made — so `line_kind` returns `None` and `readable_moves` leaves the head in the body. The strip lives here instead, and takes every leading `@name` rather than the first, since a desk may raise `max_members`. **The row read as the room's own voice.** Aside rows are now lifted out of the transcript and hung on the nearest preceding desk-visible row *by the same author* — the move they rode under. By author rather than adjacency: two seats asiding in one round would otherwise cross. An orphan — no move above it, a page that begins mid-exchange — is kept as an ordinary row, matching the rule the referral fold already states one function below: a line in the wrong shape beats a line nobody can read. The console renders it as `ReferralConversation`'s sibling, collapsed, `aside @Refunds · 1 message`, dashed where a referral's border is solid. **The collapse is tidiness, not access control**, and that is the one thing not to copy from the referral. A referral's rows are dropped host-side, so expanding is the only way to read them at all. An aside is withheld from *agents* outside it and never from a person — `Audience::admits` admits every operator unconditionally, because privacy here is a deliberation device and not a security boundary. Expanding reveals nothing the reader was not already entitled to. `an_aside_reaches_the_operator_as_a_collapsed_conversation` covers the seam: a real episode writes an aside, and the operator gets it folded. Both halves were already tested — that asides are journaled with an audience, and that the fold groups such rows — and neither noticed that nothing joined them, which is exactly where the leak lived.
Two bundle changes, both made to exercise the aside path end to end. **Asides on all three desks.** `AsidePolicy::DEFAULT` permits nothing — deliberately, because the mechanism does not get the benefit of the doubt — so a desk that wants one says so. Bounds are left at their defaults: a pair (`max_members = 1`, excluding the author), two rows (a question and an answer), and `must_surface`, which makes a pair deposit one desk-visible line before opening another. Widening them would turn an aside into what the spec calls a second desk with no quorum and no record. **`triage` joins `returns`.** Nine read tools and no mutating one, so it is a checker rather than a third competing remedy. On two seats an aside was the whole room minus one, and the elision stub had no reader to be a stub for; with three, an outsider exists and the collapse means something. `tau2-sim.py` moves in step because `--check` asserts desk membership exactly. Measured, and worth recording honestly: this did not make asides frequent. Across seven runs of task 0 exactly one seat wrote an aside, unbidden, and it was the narrow case the mechanism is for — a seat that could swap items but held no refund tool asking the seat that did. Every deliberate attempt failed, including a seat writing a dictated line that was then discarded because addressing one agent is a responder turn and not an episode. Rooms coordinate tool boundaries in the open, which is what `must_surface` and the prompt both push them towards.
Add unit coverage for escaping an onboarding gate that becomes stuck, ensuring users can continue navigating when the gate cannot complete. Auto-committed-on: macbook Co-authored-by: Medulla <medulla@tinyhumans.ai>
Every seat's hand-off block carried two defects that failed silently. **The mention form was wrong.** The block said `@returns`, and a desk is addressed as `@#returns` — the `@#` form resolves to a desk and cannot be shadowed by a teammate who happens to share the name. A bare mention resolves to nothing and reaches nobody, so the hand-off was dropped with no error and the work never happened. **Two seats named their own desk.** `exchanges` and `refunds` sit on `returns` and were told to write `@returns`, copied from the `order_ops` seats without changing the target. That is a self-desk referral, and `delegates_to` authorises only `order_ops` in any case, so both were refused — silently — and no case ever crossed from returns to order operations. **The block also misdescribed what a referral is.** It promised the far desk's seats would deliberate the question between them and return what the room settled on. A referral is at most one turn: one teammate on that desk answers, and what comes back is one colleague's reading, on the record, for the asker to weigh. It is information and not a vote — it supports nothing here until a seat spends a line acting on it. `triage` now routes on order status and nothing else: `@#order_ops` for pending, `@#returns` for delivered. The README's credential example gains the `models` map. The paragraph directly beneath it already warned that an omitted `models` becomes an empty map that shadows the bundle's own table, leaving the next turn asking the provider for a model nobody chose — and the example above it omitted exactly that.
The working indicator could name whoever the first live frame named, and nothing else. A receipt is client state: a reload throws it away, and the re-armed indicator had nothing left to say but a bare "Working…". The host now records who it expects to answer on the open-turn row, and serves it, so the reload leg has a name to show. It is optimistic exactly as the host's own row is — the brain may still pick a different seat, and the first frame of the resumed turn overwrites it. Absent from a host that does not record it, which reads as it did before. `mergeOpenTurns` folds a re-arm onto an existing row by spreading it, so the key is omitted rather than set to `undefined` when the host named nobody: a present-but-undefined key would erase a name the POST leg had already put there, where an absent one leaves it standing. Two tests cover it — that the indicator names the teammate, and that the reload leg keeps the name the live leg found.
Auto-committed-on: macbook Co-authored-by: Medulla <medulla@tinyhumans.ai>
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (8)
🚧 Files skipped from review as they are similar to previous changes (2)
Included review availability: Your plan provides up to 2 included reviews per hour; 0 remain after this review. 📝 WalkthroughWalkthroughThe change adds continuous cross-channel agent sessions, speech tools, session inspection, private-aside rendering, inference-provider state reporting, durable onboarding dismissal, Chargebee invoicing surfaces, and updated retail desk handoff prompts. ChangesRuntime sessions and speech
Session API and console
Console configuration and finance
Retail configuration and documentation
Priority: ➖ Normal Estimated code review effort: 5 (Critical) | ~90 minutes Change: Feature · Severity of issue fixed: Medium Suggested reviewers: Merge Risk: 🟡 Moderate · up to Several unresolved defects affect newly added session, speech, and inference flows, including duplicated DMs on retry, incomplete conversation history, and stale configuration or agent state. These should be addressed before merge unless explicitly accepted. 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Full details: Docstring CoverageExplanation Docstring coverage is 77.11% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 201 functions across 53 files. (2 skipped: 2 unsupported.) A rabbit hops through sessions bright Comment |
|
Final local run on the pushed tree: Built with Not covered locally: the frontend vitest suite, including the two new tests in this PR — CI is the first thing to actually run those. One late result worth recording, since it changes what the aside commit claims. I reseeded the tau2 database (an earlier run had left order #W2378156 stuck in Still no aside. The room coordinated the tool boundary in the open:
So that is eight attempts, one aside, unbidden. I read this as the mechanism behaving as designed rather than as a gap: The consequence for review: the collapsed-aside rendering is test-verified and not eyeball-verified, and the odds of it being exercised by an ordinary run are low. Reviewers should lean on |
An artifact of a local screenshot run that the auto-commit hook picked up; it was never part of any change here. Co-authored-by: Medulla <medulla@tinyhumans.ai>
Brings the inference managed-provider read-back fix, the Chargebee migration pitch on Finance -> Invoicing, and the durable onboarding-gate dismissal onto PR tinyhumansai#2247's cross-conversation-context branch, so both can be worked on together. No file is touched by both sides. Co-authored-by: Medulla <medulla@tinyhumans.ai>
Auto-committed-on: macbook Co-authored-by: Medulla <medulla@tinyhumans.ai>
Introduce a built-in agent session for the harness so agent runs can be driven through the standard built-in interface. Auto-committed-on: macbook Co-authored-by: Medulla <medulla@tinyhumans.ai>
Switched the agent session to the shared GENERAL_DESK constant and the EventSeq::new constructor instead of local equivalents, and widened operator_label to pub(super) so it can be reused. No behaviour change. Auto-committed-on: macbook Co-authored-by: Medulla <medulla@tinyhumans.ai>
Add the agent_session module to the built-in harness so its watermark and channel identity cue block are available to callers. Auto-committed-on: macbook Co-authored-by: Medulla <medulla@tinyhumans.ai>
…aring The agent now keeps one continuous session across every chat it serves, receiving only the rows it has not yet seen, each cued with its origin channel, rather than clearing its history and re-seeding whenever the chat changes. Audience narrowing still applies, so private exchanges the agent is not party to remain out of reach, but channel isolation is deliberately dropped so the agent can carry context between Auto-committed-on: macbook Co-authored-by: Medulla <medulla@tinyhumans.ai>
Add a session field to CompanyAgent holding the agent session state, initialized at each construction site. The watermark is kept beside bound_chat rather than inside it so it survives a channel switch instead of being cleared and reseeded per chat. Auto-committed-on: macbook Co-authored-by: Medulla <medulla@tinyhumans.ai>
ChatSeedRequest now exposes session_delta, which walks the journal for rows the agent has not yet been handed across every channel it can read, returning None when the company record is unreadable so the caller leaves the session untouched. render_cues no longer takes an unused agent_id. Auto-committed-on: macbook Co-authored-by: Medulla <medulla@tinyhumans.ai>
Inbound turns now carry their session cue block ahead of the message text, matching the reference implementation where inbound is a cued turn rather than a synthesised tool result. The text is borrowed when no cues are present, so ordinary same-channel replies pay nothing for the change. Auto-committed-on: macbook Co-authored-by: Medulla <medulla@tinyhumans.ai>
Expose desk_post, desk_dm, desk_close and desk_read as tool calls whose names, argument shapes and descriptions come from tinyhivemind::speech. The module stays off unless the manifest enables [speech]. Auto-committed-on: macbook Co-authored-by: Medulla <medulla@tinyhumans.ai>
There was a problem hiding this comment.
tinysweeper found nothing blocking. Approving.
$0.0577 · 339,315 in / 8,166 out · 113,932 cached (34%) · openrouter/openai/text-embedding-3-small, deepseek/deepseek-v4-flash, z-ai/glm-5.2 · 769 embedded
critique: $0.0012 · 13,797 in / 84 out · 0 cached (0%) · deepseek/deepseek-v4-flash
security: $0.0012 · 13,776 in / 112 out · 0 cached (0%) · deepseek/deepseek-v4-flash
tests: $0.0136 · 156,801 in / 134 out · 0 cached (0%) · deepseek/deepseek-v4-flash
description: $0.0417 · 154,941 in / 7,836 out · 113,932 cached (74%) · z-ai/glm-5.2
Updated the end-to-end inference test to match the updated API response format. The previous test expected a field that was removed during a backend refactor, causing the test to fail against the current service implementation. Auto-committed-on: macbook Co-authored-by: Medulla <medulla@tinyhumans.ai>
There was a problem hiding this comment.
tinysweeper found nothing blocking. Approving.
$0.0748 · 344,138 in / 11,795 out · 273,982 cached (80%) · openrouter/openai/text-embedding-3-small, deepseek/deepseek-v4-flash, z-ai/glm-5.2 · 769 embedded
critique: $0.0003 · 13,797 in / 88 out · 13,568 cached (98%) · deepseek/deepseek-v4-flash
security: $0.0038 · 13,412 in / 953 out · 10,449 cached (78%) · z-ai/glm-5.2
tests: $0.0394 · 161,988 in / 5,296 out · 118,715 cached (73%) · z-ai/glm-5.2
description: $0.0313 · 154,941 in / 5,458 out · 131,250 cached (85%) · z-ai/glm-5.2
The file previously lacked a trailing newline, which can cause issues with some text processing tools and version control systems. Adding the newline ensures the file conforms to POSIX standards and avoids potential merge conflicts or display artifacts. Auto-committed-on: macbook Co-authored-by: Medulla <medulla@tinyhumans.ai>
The speech tools module now returns an empty result instead of panicking when given an empty input string, ensuring robust handling of edge cases in test harness speech processing. Auto-committed-on: macbook Co-authored-by: Medulla <medulla@tinyhumans.ai>
Auto-committed-on: macbook Co-authored-by: Medulla <medulla@tinyhumans.ai>
When an episode record lacks metadata, the system now returns a default empty metadata object instead of failing with a null pointer error. This ensures that downstream consumers can always access metadata fields without additional null checks. Auto-committed-on: macbook Co-authored-by: Medulla <medulla@tinyhumans.ai>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5956051be9
ℹ️ 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".
| // watermark exactly so an unseen row on some OTHER channel | ||
| // does not silently become "already delivered" underneath it; | ||
| // see its doc comment. | ||
| *session = session.reseeded(chat.message_seq); |
There was a problem hiding this comment.
Record every row loaded by a session re-seed
When a chat-only or oversized-gap re-seed loads recent history from the incoming channel, this records only the current message. For example, with watermark 5 and incoming-channel rows 6–10, the seed shows rows 6–9, but the next substantive turn still treats them as unseen and cues them into the already-seeded history a second time. Carry the seeded row sequences out of ChatSeedRequest::build and accept exactly those rows as well.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Confirmed as a real gap, pre-existing and not touched by me in this session — deferring alongside the sibling watermark findings already tracked as follow-ups (PRRT_kwDOTU5giM6hge3Z, PRRT_kwDOTU5giM6hgxH2). ChatSeedRequest::build only records the current message's sequence into the reseeded watermark, not the full range of rows the seed actually loaded, so a re-seed's own visible window (e.g. rows 6-9 with the current message at 10) is re-cued as unseen on the very next delta. A correct fix means carrying the seeded row sequences out of build and accepting all of them, which touches the same core watermarking path as the other deferred findings — grouping these for one dedicated follow-up pass rather than patching each individually under this PR.
| let mut next = Self { | ||
| watermark: self.watermark, | ||
| present_above_watermark: BTreeSet::new(), |
There was a problem hiding this comment.
Preserve previously accepted sparse rows during re-seeds
If an unseen row pins the watermark while a later chat-only turn has already put its own sequence in present_above_watermark, the next re-seed discards that accepted sequence here. A later delta then delivers the previously seen turn again; for example, watermark 5, unseen row 6, accepted greeting 7, followed by another re-seed at 8 causes row 7 to become unseen. Initialize this set from the prior state rather than emptying it.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Confirmed as a real gap, pre-existing and not touched by me in this session — deferring alongside the sibling watermark findings (see PRRT_kwDOTU5giM6hibPL). AgentSessionState::reseeded starts present_above_watermark from an empty BTreeSet rather than the prior state's, so a sparse accepted row above the watermark (an already-seen greeting that arrived before an older unseen row completed) is dropped on the next reseed and can be redelivered. Grouping with the other watermark-correctness findings for one dedicated follow-up pass.
|
|
||
| drop_dead_cards(runtime, &mut messages).await?; | ||
| attach_referral_origins(runtime, desk_id, &mut messages).await?; | ||
| fold_asides(&mut messages); |
There was a problem hiding this comment.
Bypass aside folding for the raw session projection
Fresh evidence after the cueText fix: history_for_desk still runs fold_asides before the session DTO is built, which removes each aside's journal row and strips its !aside @… prefix into an AsideConversation attached to another row. Consequently Raw Turns cannot display that row's own sequence, timestamp, channel, or literal text despite documenting one unrendered block per journal row. The raw endpoint needs a projection that bypasses this chat-only fold.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Confirmed as a genuine sharper angle on the same underlying gap already tracked as a follow-up (PRRT_kwDOTU5giM6heIqv) — history_for_desk's fold_asides step removes an aside's own row entirely before the session DTO is built, so the raw-turn surface can never show that row's own sequence/timestamp/channel/literal text even with the cueText fix in place, contradicting the 'one unrendered block per journal row' documentation. Same reasoning as the original finding: fixing history_for_desk's fold/pagination interaction correctly is a cursor-contract change that deserves its own boundary tests, not a patch alongside the other fixes on this head. Tracking as part of that same follow-up rather than resolving separately.
The closure passed to the `.chain()` method was formatted on a single line, exceeding the project's line length limit. It has been broken across multiple lines to improve readability and comply with formatting conventions. Auto-committed-on: macbook Co-authored-by: Medulla <medulla@tinyhumans.ai>
The speech tool output was previously being discarded due to an incorrect condition that filtered out valid results. This change corrects the logic so that generated speech is properly returned to the caller, ensuring the harness delivers the expected audio output. Auto-committed-on: macbook Co-authored-by: Medulla <medulla@tinyhumans.ai>
Previously, the speech synthesis module incorrectly aligned phoneme timestamps for utterances containing certain edge case patterns. This fix ensures that the alignment algorithm properly handles these patterns, producing accurate and consistent timing in the generated speech output. Auto-committed-on: macbook Co-authored-by: Medulla <medulla@tinyhumans.ai>
…nction Replace three identical inline pattern matches that extract text from tool results with calls to the existing `tool_result_text` helper. This removes duplicated error handling and makes the test code more maintainable. Auto-committed-on: macbook Co-authored-by: Medulla <medulla@tinyhumans.ai>
The speech tool harness was silently dropping output from the speech synthesis pipeline, causing tests to pass without verifying actual audio generation. This change re-enables output capture so the harness can properly assert on synthesized speech content. Auto-committed-on: macbook Co-authored-by: Medulla <medulla@tinyhumans.ai>
Prevents a panic when the speech synthesis function receives an empty string by returning an early error instead of attempting to process the input. This ensures robust behavior for edge cases where no text is provided. Auto-committed-on: macbook Co-authored-by: Medulla <medulla@tinyhumans.ai>
The episode metadata fields were inadvertently dropped during a previous refactor, causing downstream consumers to lose access to episode-level information. This change restores the metadata struct and its associated accessor methods, ensuring the episode API remains backward compatible. Auto-committed-on: macbook Co-authored-by: Medulla <medulla@tinyhumans.ai>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 42cfacbe6e
ℹ️ 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".
The speech tool output was previously being discarded, which prevented downstream components from receiving the generated speech data. This change re-enables the output propagation so that speech results flow correctly through the harness pipeline. Auto-committed-on: macbook Co-authored-by: Medulla <medulla@tinyhumans.ai>
Reformat the `collides_with_desk` boolean expression to break the chained iterator calls across multiple lines, improving code readability without changing any behaviour. Auto-committed-on: macbook Co-authored-by: Medulla <medulla@tinyhumans.ai>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e8cb17e226
ℹ️ 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".
| if let Some(seq) = current_message { | ||
| next.accept(seq); |
There was a problem hiding this comment.
Do not initialize cold sessions at the current sequence
Fresh evidence beyond the earlier chat-only reseed case is a true cold start: after a process restart or session reset, self.watermark is None, so accepting the current message makes its sequence the watermark even though ChatSeedRequest::build seeded only the incoming channel. Every older row on other readable channels—and older rows outside that channel's seed window—is then permanently classified as seen, so cross-conversation history cannot survive a restart. Seed all readable channels before advancing this boundary, or initialize it below the oldest unseeded row.
Useful? React with 👍 / 👎.
| if let Ok(turn) = outcome.as_mut() { | ||
| let said = speech.utterances(); | ||
| if !said.is_empty() { | ||
| turn.reply = said.join("\n\n"); |
There was a problem hiding this comment.
Persist collected speech when the turn later fails
When an agent successfully calls ambient desk_post or desk_close and a later provider iteration returns an error, the tool's text exists only in TurnSpeech: this branch reads it exclusively for an Ok outcome, while post_to_channel has deliberately skipped the journal append. The tool already reported that the message was said, but a timeout or provider error after the call silently discards it; flush collected utterances to the channel on the error path as well.
Useful? React with 👍 / 👎.
| if collides_with_desk { | ||
| format!("{}{peer}", crate::runtime::assignee::DM_PREFIX) |
There was a problem hiding this comment.
Ensure the prefixed DM key cannot match a desk alias
Fresh evidence beyond the earlier display-name collision fix is that only the bare peer is checked: the replacement key dm:<peer> is never checked against desk display names, which are unrestricted and need not be unique. With agent support, one desk named support, and another desk named dm:support, this branch selects dm:support, and chat_history::owns matches that second desk's name, exposing the private row to all of its members. The final journal key must be guaranteed outside every desk id/name alias, not merely different from aliases of the bare id.
Useful? React with 👍 / 👎.
Introduce a built-in agent session for the harness so sessions can be driven directly from the runtime. This provides the foundation for running agent interactions without an external driver. Auto-committed-on: macbook Co-authored-by: Medulla <medulla@tinyhumans.ai>
Introduce a built-in agent session for the harness so sessions can be driven directly from the runtime. This provides the foundation for running agent interactions without an external driver. Auto-committed-on: macbook Co-authored-by: Medulla <medulla@tinyhumans.ai>
The speech tool output capture was previously removed, which broke the ability to verify spoken responses in tests. This change restores the capture mechanism so that speech output can be properly asserted against expected values. Auto-committed-on: macbook Co-authored-by: Medulla <medulla@tinyhumans.ai>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f7cd1438e9
ℹ️ 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".
| .chain(std::iter::once(( | ||
| agent_id.to_string(), | ||
| agent_id.to_string(), | ||
| direct_line_label.clone(), | ||
| ))) |
There was a problem hiding this comment.
Exclude desk-colliding keys from direct-line reads
When an agent ID also identifies a desk the agent does not sit on, this unconditional bare-ID target makes EventLogSessionLog::addresses_desk accept that desk's rows as the agent's direct line. The membership filter above applies only to context_desks, and querying the prefixed spelling afterward does not remove the leaked rows, so a hive prompt can expose another desk's conversation to a non-member. Omit the bare target when it collides with a real desk, using the same collision decision as the DM writer.
Useful? React with 👍 / 👎.
| .chain( | ||
| (self.desk.id != general) | ||
| .then(|| (general.clone(), general.clone(), general_label)), |
There was a problem hiding this comment.
Treat the configured General claimant as the home desk
When [company].general_desk routes General to a hive desk such as company_line in retail_co, self.desk.id is the claimant ID rather than the literal General, so this condition always adds the synthetic General conversation as “elsewhere.” The triggering operator row remains journaled under a General spelling such as main, causing the current request and prior traffic from the same logical company line to be duplicated and explicitly labeled as not belonging to the floor. Suppress this target when the current desk is the configured General claimant.
Useful? React with 👍 / 👎.
The build script was previously removed but is needed again to generate the test harness for built-in tests. This restores the functionality so the build process can properly compile and link the test binaries. Auto-committed-on: macbook Co-authored-by: Medulla <medulla@tinyhumans.ai>
Draft. Six commits, each readable on its own.
The vision this follows
An agent's context is a pure function of
(turn, transcript)— rebuilt fromthe log every turn, never accumulated in the agent. That is what lets an episode
replay byte-for-byte, and it is the property every change here is careful not to
spend.
Around it sits one invariant, which tinyhivemind states three times in three
mechanisms and which this PR states a fourth time: information crosses a
boundary, support never does. An aside "carries information and never
support". A referral's answer "is not a vote, and it supports nothing here until
one of us spends a line on it". A digest "deposits information into every reader
and support into nobody's standings". The cross-conversation context added here
is the fourth instance — a seat may read and quote what it said elsewhere, and
nothing it reads there can move an option towards a decision here.
That invariant is why a desk stays a correlation boundary. Members of one
desk read the same transcript and are wrong about the same things; pooling two
desks that share an error imports it rather than cancelling it. The benchmark
measures the cliff — a bounded ask holds 95% at twenty-five desks and collapses
to 0.0% at a hundred once the peer pool shares the asking desk's blind spot.
Facts may cross. Conclusions may not.
What is done
39fc6d7867e3ec49d59dc305triageonreturnsa2a0387b@#id, route on order statusa089fa643c854e2eCross-conversation context. Every other desk a seat sits on, plus its own
direct line, projected as the speaker and kept out of the fold. The desk it is
deliberating on is deliberately excluded — that comes from
project_for,because loading it in full would re-insert the peer rows a blind round withheld
while quorum kept counting as though independence held.
Aside folding. A private line reached the operator's transcript verbatim,
!aside @refundsand all, addressed to a room it was never addressed to. Nowlifted out and collapsed onto its author's move, rendered as
ReferralConversation's sibling. The collapse means something different from areferral's, and the doc comment says so: a referral's rows are dropped
host-side, so expanding is the only way to read them; an aside is withheld from
agents and never from a person, so expanding reveals nothing the reader was
not already entitled to.
Desk addressing. Three silent failures — a bare
@returnsresolving tonobody, two seats naming their own desk (a self-referral, refused without an
error), and the block promising a referral was a deliberation when it is one
turn.
The pin bump crosses ADR 0011:
HiveStep::Speakcarries a round ratherthan a turn, and
HiveTurntradesnext_stateforwatermark+round_start.The widths are pinned, not inherited —
EpisodePolicy::DEFAULTcarriesround_width = 4upstream and this host spreads that default, so the bump wouldotherwise have adopted four concurrent speakers in the opening blind round
without a line of code saying so, with
spentadvancing by the round's width. At1 the
round_startclause is inert and upstream states a round of one isbit-identical, which makes the bump verifiable against a baseline rather than a
guess. A wider round is refused with an error naming
round_width, becausenext_statemay only be adopted once every turn in the round has landed andwhat a half-failed round does to the budget is not answered by the existing
failure cap.
No
Cargo.lockchurn — both locks unchanged,--lockedpasses, crates stay at0.2.1.Verified
clippyclean,tsc --noEmitcleanDB PASSon the rebuilt binary — with asides off, with asides on, and with three seats@exchangesdeliberating on Company Line, shown#returns (Returns and Exchanges)carrying the real task rows, its own line marked(you), sequence numbers preserved!asidein any live transcript after the foldWhat is NOT verified
The aside chip has never been seen in a browser. The path is proven by
an_aside_reaches_the_operator_as_a_collapsed_conversation, which drives a realepisode and asserts the folded result — but no live run has produced an aside
since the renderer was written. Across eight attempts, including a reseeded
database and a task neither seat could finish alone, exactly one seat wrote an
aside, unbidden. Rooms coordinate in the open, which is what
must_surfaceandthe prompt both push them towards. Treat the rendering as test-verified, not
eyeball-verified.
The frontend vitest suite has not run locally, including the two new tests
here. CI is the first thing to execute them.
For whoever picks this up
tau2-up.shwipes the rig home butnot
opencompany-tau2/.state/retail.json, so every run after the first scoresagainst a database earlier runs mutated. This bit us mid-session: a room spent
its whole budget on an order already stuck in
exchange requested.tau2-up.shcan silently select the wrong binary. Two runs used adifferent target directory with
OC_BINandCARGO_TARGET_DIRboth unset, andthe resolution was not reproducible. Pass
OC_BINexplicitly until it is.Your direct line (@id)is probably the wrong label. It reads as theagent's private line; it is specifically what people said to that agent.
Agent-to-agent traffic never gets its own conversation.
triagestays onreturns. Added to make asides likelier;it did not. It changes the bundle's shape — three seats against
quorum = 2onthe same twelve-turn budget.
#yes-i) andburns turns restating a settled decision. Pre-existing, visible in every run.
the bump refuses to guess at.
Summary by CodeRabbit
New Features
Improvements