Skip to content

fix: make background result retriggers deterministic#231

Merged
jamiepine merged 3 commits into
mainfrom
worker-retrigger-fix
Feb 26, 2026
Merged

fix: make background result retriggers deterministic#231
jamiepine merged 3 commits into
mainfrom
worker-retrigger-fix

Conversation

@jamiepine

@jamiepine jamiepine commented Feb 26, 2026

Copy link
Copy Markdown
Member

Summary

  • make background result retrigger handling deterministic in the channel worker flow so equivalent retrigger events resolve consistently.
  • update retrigger prompt fragments and prompt engine wiring so retrigger metadata and guidance stay aligned with the runtime behavior.
  • adjust ChannelDetail UI handling to reflect the retrigger flow changes exposed by the backend.

Note

Implementation Details

This PR refactors background process result handling to eliminate non-determinism in retrigger behavior. Instead of directly injecting results into conversation history as fake user messages (which could be confused with prior results), the channel now accumulates results in a PendingResult queue and embeds them directly into the retrigger message. This ensures the LLM sees exactly which process completed and what it returned, with unambiguous process type and ID tags.

Key changes: (1) Introduced PendingResult struct to queue branch/worker results with metadata, (2) Modified flush_pending_retrigger() to render results into the retrigger template for consistent message formatting, (3) Updated apply_history_after_turn() to handle retrigger cancellations separately and preserve user prompts while discarding dangling assistant tool-calls, (4) Enhanced prompt templates to embed structured result data instead of plain text, and (5) Updated UI to make worker result display collapsible with an "Open" button instead of embedding task details inline.

Written by Tembo for commit 141fce5. This will update automatically on new commits.

Queue completed branch and worker outputs as structured retrigger payloads and preserve real user prompts on PromptCancelled turns so channels relay fresh results without losing context.
@coderabbitai

coderabbitai Bot commented Feb 26, 2026

Copy link
Copy Markdown
Contributor

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a7346ba and 141fce5.

📒 Files selected for processing (5)
  • interface/src/routes/ChannelDetail.tsx
  • prompts/en/channel.md.j2
  • prompts/en/fragments/system/retrigger.md.j2
  • src/agent/channel.rs
  • src/prompts/engine.rs

Walkthrough

The changes introduce a new PendingResult model to queue background process outcomes during retriggers, refactor the retrigger flow to embed structured result summaries in history metadata, update UI components to display expandable worker and branch item details, and modify prompt templates to handle bulk result reporting with updated instructions for internal process handling.

Changes

Cohort / File(s) Summary
UI Expansion & Collapsibility
interface/src/routes/ChannelDetail.tsx
Added optional className parameter to CancelButton and introduced expanded state management for LiveWorkerRunItem, WorkerRunItem, and BranchRunItem. Replaced static descriptions with collapsible UI elements that toggle visibility of metadata, task details, and action buttons. Updated class names to support expanded/contracted layout shifts.
Prompt Templates
prompts/en/channel.md.j2, prompts/en/fragments/system/retrigger.md.j2
Unified handling of branch and worker results into a generalized retrigger message format. Replaced per-item static instructions with dynamic template that reports result counts, iterates over results with type/id/content details, and emphasizes silent incorporation of unsolicited background work without mentioning internal identifiers.
Retrigger Mechanism & History Management
src/agent/channel.rs
Introduced PendingResult model to accumulate background process outcomes during debounce windows. Reworked retrigger generation to embed pending results into retrigger payloads as metadata. Modified history rollback logic in apply_history_after_turn to preserve real user prompts while discarding intermediate tool messages on PromptCancelled, with special retrigger handling. Updated call sites to thread is_retrigger flag through execution path.
Result Structure & Serialization
src/prompts/engine.rs
Added public RetriggerResult struct with fields for process type, ID, success status, and result text. Updated render_system_retrigger method signature to accept a slice of RetriggerResult and render results into the retrigger fragment template context.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • feat(web): ui/ux cleanup #143: Implements related UI expansion/truncation behavior for branch and worker items in ChannelDetail.tsx alongside load-more and throttling mechanisms.
✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch worker-retrigger-fix

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 and usage tips.

@jamiepine jamiepine merged commit 456f16f into main Feb 26, 2026
3 of 4 checks passed
Comment thread src/agent/channel.rs
Comment on lines +1442 to +1446
let status = if r.success { "completed" } else { "failed" };
let summary = format!(
"[Background {} {} {}]: {}",
r.process_type, r.process_id, status, r.result
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If retrigger cap is hit, this path injects raw results into history. Might be worth truncating here (similar to retrigger_result_summary) to avoid blowing up context when a worker returns something huge.

Suggested change
let status = if r.success { "completed" } else { "failed" };
let summary = format!(
"[Background {} {} {}]: {}",
r.process_type, r.process_id, status, r.result
);
let status = if r.success { "completed" } else { "failed" };
let truncated = if r.result.len() > 500 {
let boundary = r.result.floor_char_boundary(500);
format!("{}... [truncated]", &r.result[..boundary])
} else {
r.result.clone()
};
let summary = format!(
"[Background {} {} {}]: {}",
r.process_type, r.process_id, status, truncated
);

Comment thread src/agent/channel.rs
Comment on lines +1512 to +1524
let result_count = self.pending_results.len();

// Build per-result summaries for the template.
let result_items: Vec<_> = self
.pending_results
.iter()
.map(|r| crate::prompts::engine::RetriggerResult {
process_type: r.process_type.to_string(),
process_id: r.process_id.clone(),
success: r.success,
result: r.result.clone(),
})
.collect();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Since the PR goal is deterministic handling, consider sorting the pending results before templating so equivalent result sets render in a stable order (even if completion events arrive in a different sequence).

Suggested change
let result_count = self.pending_results.len();
// Build per-result summaries for the template.
let result_items: Vec<_> = self
.pending_results
.iter()
.map(|r| crate::prompts::engine::RetriggerResult {
process_type: r.process_type.to_string(),
process_id: r.process_id.clone(),
success: r.success,
result: r.result.clone(),
})
.collect();
let result_count = self.pending_results.len();
// Keep ordering deterministic so equivalent retrigger batches render consistently.
self.pending_results.sort_by(|a, b| {
(a.process_type, a.process_id.as_str()).cmp(&(b.process_type, b.process_id.as_str()))
});
// Build per-result summaries for the template.
let result_items: Vec<_> = self
.pending_results
.iter()
.map(|r| crate::prompts::engine::RetriggerResult {
process_type: r.process_type.to_string(),
process_id: r.process_id.clone(),
success: r.success,
result: r.result.clone(),
})
.collect();

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant