Skip to content

fix(gateway): a steer/interrupt-triggered queued terminal turn gets its own ledger id - #106358

Open
nftpoetrist wants to merge 1 commit into
NousResearch:mainfrom
nftpoetrist:fix/queued-followup-ledger-id
Open

nftpoetrist wants to merge 1 commit into
NousResearch:mainfrom
nftpoetrist:fix/queued-followup-ledger-id

Conversation

@nftpoetrist

Copy link
Copy Markdown
Contributor

What does this PR do?

Closes the one branch the queued-lane ledger saga (#106312/#106316, merged earlier today) didn't reach: a queued follow-up triggered by a leftover /steer or a bare control interrupt_message (no originating queued event) still collides on the same delivery-ledger obligation id as an earlier reply in the chain.

Problem

gateway/run_turn.py::_run_agent_queued_followup derives next_inbound_id — the id that keys the follow-up's own delivery-ledger obligation — only inside the if pending_event is not None: branch. When the follow-up instead comes from result["pending_steer"] or a bare interrupt_message (pending_event is None), next_inbound_id stays at its None default.

That None flows into queued_terminal_inbound_id, which the caller in gateway/run.py reads back with a truthiness check:

_terminal_inbound = agent_result.get("queued_terminal_inbound_id")
if _terminal_inbound:
    event.ledger_message_id = str(_terminal_inbound)

None is indistinguishable from "absent" here, so event.ledger_message_id is never overridden and the outer final send falls back to the id of the event that opened the chain. If this steer/interrupt-triggered terminal reply happens to carry the same text as an earlier reply in the same chain, both compute the identical obligation id — the earlier (possibly flood-refused) row is silently overwritten and marked delivered, and is never redelivered. This is exactly the bug the saga just fixed, reached from the one branch it didn't cover.

Fix

When pending_event is None, mint a synthetic id (uuid4, prefixed steer:) distinct from any real transport id. There's no natural identity to derive one from for a synthesized continuation — this mirrors the existing keyless-turn-owner fallback already used elsewhere in the same file (_run_agent_prepare_turn's owner id, which falls back to uuid.uuid4() when event.message_id is absent).

Testing

  • Added test_a_steer_triggered_terminal_turn_gets_its_own_inbound_id, mirroring the saga's own test_a_deeper_chain_keeps_the_innermost_inbound_id pattern: asserts the inbound id is truthy, that it feeds into queued_terminal_inbound_id, and that two separate occurrences get different ids (not a deterministic/reusable value).
  • Mutation-verified: reverting gateway/run_turn.py drops exactly the new test (9 pass, 1 fail — assert None, reproducing the bug directly); reapplying restores green (10 passed).
  • uv run --frozen python -m pytest tests/gateway/test_queued_final_ledger.py tests/gateway/test_queued_native_image_session_key.py tests/gateway/test_multiplex_busy_input_mode.py -q → 32 passed (checked sibling queued-lane suites for interaction regressions — none).
  • ruff check clean on both changed files.

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)

Checklist

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)

…ts own ledger id

The queued-lane ledger saga (NousResearch#106312/NousResearch#106316, merged earlier today) fixed
_run_agent_queued_followup's next_inbound_id so a chained queued turn no
longer collides on the SAME delivery-ledger obligation id as an earlier
reply in the chain -- but only for the pending_event branch. The sibling
branch, reached when the follow-up has no originating event (a leftover
/steer drained via result["pending_steer"], or a bare control
interrupt_message), left next_inbound_id at its None default.

That None flows into queued_terminal_inbound_id, and the call site that
reads it back (gateway/run.py's `if _terminal_inbound:`) is a truthiness
check, so a None there is treated exactly like "absent": event.ledger_message_id
is never overridden, and the outer final send falls back to the id of the
event that OPENED the chain. If this steer/interrupt-triggered terminal
reply carries the same text as an earlier reply in the chain, both compute
the same obligation id -- the earlier (possibly flood-refused) row is
silently overwritten and marked delivered, and is never redelivered. This
is the exact bug the saga just fixed, reached from the one branch it didn't
cover.

Fix: when pending_event is None, mint a synthetic id (uuid4, prefixed
"steer:") distinct from any real transport id -- there is no natural
identity to derive one from for a synthesized continuation, matching the
existing keyless-turn-owner fallback shape already used elsewhere in this
file (`_run_agent_prepare_turn`'s `owner` id).

Verified empirically: read the consumer (`if _terminal_inbound:`) to
confirm None is treated as absent, not as "no override needed" -- it's a
real collision, not a defensive no-op. Added a regression test mirroring
the saga's own `test_a_deeper_chain_keeps_the_innermost_inbound_id`
pattern, asserting the inbound id is truthy, feeds into
queued_terminal_inbound_id, and differs across two separate occurrences
(not deterministic/reused). Mutation-verified: reverting run_turn.py drops
exactly the new test (9 pass, 1 fail: `assert None` reproducing the bug
exactly); reapplying restores green (10 passed). Also ran the sibling
queued-native-image and multiplex-busy-input suites (32 passed) for
interaction regressions -- none.
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/gateway Gateway runner, session dispatch, delivery sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages labels Sep 9, 2026
@Enough1122

Copy link
Copy Markdown
Contributor

AI code review — automated review for reference; please use your judgment.

Summary

In gateway/run_turn.py::_run_agent_queued_followup, a steer/interrupt-triggered terminal turn with no originating queued event now mints a unique steer:<uuid> inbound id instead of leaving next_inbound_id=None (which silently skipped the ledger override and fell back to the chain-opening event, risking same-text obligation-id collision — the branch the #106312/#106316 saga missed). Test asserts truthy, distinct-across-occurrences ids.

Findings

No blocking issues.

  • Root-cause chain is right: None failed the truthiness check at the call site, so the override was skipped silently. Minting a fresh uuid per occurrence guarantees distinct ledger identity; steer: prefix keeps it greppable and disjoint from real transport ids.
  • Import placement: import uuid is function-local in the else branch — stdlib import is cheap/cached; fine, though module top-level would be more conventional. Cosmetic.
  • Test covers the exact regression (second steer drain gets a different id). Good.

Verdict

Looks good. No concerns.

This branch has not been deployed

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

Labels

comp/gateway Gateway runner, session dispatch, delivery P2 Medium — degraded but workaround exists sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants