Skip to content

fix: preserve agent stream timeline position#1424

Draft
agent-p1p wants to merge 2 commits into
masterfrom
pip/whitenoise-android-1372
Draft

fix: preserve agent stream timeline position#1424
agent-p1p wants to merge 2 commits into
masterfrom
pip/whitenoise-android-1372

Conversation

@agent-p1p

@agent-p1p agent-p1p commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Summary

  • preserve an actively displayed agent stream row when its final record replaces it
  • derive cold/reload ordering from the durable final stream-start -> start parent -> prompt chain
  • pre-index authoritative pages and live batches so raw timestamp/page iteration order cannot place the answer before its prompt
  • resolve start/final tie orders against the prompt's effective local position, including reconciled optimistic prompts
  • cover page permutations, absent durable links, and reversed start/final clock skew

Dependency

The Marmot stream protocol supports the parent tag, but the current MDK agent-control path does not emit it. marmot-protocol/mdk#876 tracks propagating the triggering prompt ID into kind-1200 stream-start events. This PR intentionally does not infer a prompt from nearest-message or receive-time heuristics because those corrupt historical sync ordering.

Test plan

  • ./gradlew ktlintCheck
  • ./gradlew testDevPlayDebugUnitTest
  • focused AgentStreamPreviewTest
  • GitHub Android CI

Fixes #1372

@stage-review

stage-review Bot commented Jul 15, 2026

Copy link
Copy Markdown

Ready to review this PR? Stage has broken it down into 3 individual chapters for you:

Title
1 Define stream position calculation logic
2 Integrate stream position preservation in controller
3 Verify stream position logic with tests
Open in Stage

Chapters generated by Stage for commit 91da46b on Jul 15, 2026 5:56pm UTC.

Keep a streamed agent reply at the timestamp and ordering of its displayed preview when the durable final record replaces it.

Fixes #1372
@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@agent-p1p, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 31 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ea4fc35f-4ceb-4780-9814-be3a6a1cf75f

📥 Commits

Reviewing files that changed from the base of the PR and between ef5cfc9 and 91da46b.

📒 Files selected for processing (2)
  • app/src/main/java/dev/ipf/whitenoise/android/state/Controllers.kt
  • app/src/test/java/dev/ipf/whitenoise/android/state/AgentStreamPreviewTest.kt
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch pip/whitenoise-android-1372

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@agent-p1p
agent-p1p force-pushed the pip/whitenoise-android-1372 branch from 55dbda2 to 2994a75 Compare July 15, 2026 16:27
@github-actions

github-actions Bot commented Jul 15, 2026

Copy link
Copy Markdown

Preview APK for PR #1424

Home-screen label: 1424 White Noise with the blueprint launcher icon — installs side-by-side with the production and staging apps.

Install directly — one tap, no GitHub sign-in (public, content-addressed .apk). ARM64 only.

Every push to this PR rebuilds and updates this comment.

@agent-p1p

Copy link
Copy Markdown
Contributor Author

Pip adversarial review

BLOCKING:

  • The preserved position is only available while a preview/start row is still in memory; the original inversion returns on a cold/replaced timeline. Controllers.kt:6460-6461, 6839, 6879-6888replaceWindow drops timestamp/order overrides for every non-optimistic projected record. The final can then recover an override only from the currently displayed stream:$streamId row. On controller recreation there is no phone-clock preview, and on an authoritative page the final may be processed before its start row (the failure case already permits the final's timelineAt to be earlier), or the start's own bridge timestamp may share the same clock skew. In either case the final keeps its raw earlier recordedAt and sorts above the triggering user message again. This fixes the live swap, not the durable timeline ordering required by Agent response bubble can sort above the user's own message (recordedAt reconciliation mismatch between streaming placeholder and final projected record) #1372. Make the final's order derivable on every projection from durable data (ideally the linked triggering message), or fix the event timestamp at the source; do not rely solely on a transient preview override.

SUGGESTION:

  • Add a reload/replace-window regression that asserts the actual sorted outcome. AgentStreamPreviewTest.kt:11-38 — the new tests only prove that a pure helper copies an existing preview's fields. They do not exercise override pruning, an absent preview, authoritative page ordering, or compareTimelineMessages. A regression should start with user-before-preview, reconcile the final, then rebuild from authoritative records with no optimistic preview and assert the answer still sorts after the question.

NITPICK:

  • none

SUMMARY: The common live reconciliation path is small and correctly captures the preview before optimisticMessages.remove, and the targeted tests pass. It does not establish a durable ordering invariant, so the same answer-before-question bug can return after refresh/controller recreation. Targeted verification: 14/14 tests passed across Dev Zapstore and Dev Play (AgentStreamPreviewTest plus CompareTimelineMessagesTest). Android CI was still running at review time.

VERDICT: REQUEST_CHANGES
SENSITIVE_PATHS: none

Rebuild stream start and final sort positions from the persisted final-to-start-to-prompt relationship on page replacement and live batches. Preserve rows that were already displayed while keeping new batch projection independent of record order.\n\nFixes #1372
@agent-p1p

Copy link
Copy Markdown
Contributor Author

Addressed the blocking review in 91da46b.

  • authoritative pages and live batches are indexed before projection, so final-before-start iteration order is irrelevant
  • skewed stream starts anchor after their durable parent prompt; skewed finals anchor after the linked start
  • effective local timestamp/order overrides are respected, so reconciled optimistic prompts still stay before the stream
  • only rows present before a live batch count as already displayed; same-batch records use the durable chain
  • reload tests sort actual TimelineMessage values across page permutations and mixed start/final clock skew

Local: ktlintCheck and full testDevPlayDebugUnitTest pass.

Source prerequisite: current MDK does not emit the protocol's optional parent tag. Filed marmot-protocol/mdk#876. The PR remains draft until that dependency and this PR's CI are resolved; inferring a prompt from receive time or nearest-message order would reintroduce historical-sync bugs.

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

Labels

None yet

Projects

None yet

1 participant