Skip to content

CoSTEER RAG trace cursor can skip fresh repair feedback #1398

Description

@deepkilo

Summary

CoSTEERRAGStrategyV2 keeps a persistent current_generated_trace_count cursor, but each CoSTEER.develop() run can pass a fresh evolving_trace object. When the stale cursor happens to equal the length of a new trace, generate_knowledge() returns early and skips ingesting the latest repair feedback.

Verified on microsoft/RD-Agent main at:

706e907727d3f0721f6707675722155615294195

Relevant upstream code:

  • rdagent/components/coder/CoSTEER/knowledge_management.py
  • rdagent/components/coder/CoSTEER/evolving_strategy.py

Why this matters

In multi-task CoSTEER repair, this can cause already-successful candidates from the previous repair step to be scheduled again because their successful implementations were not written into success_task_to_knowledge_dict.

A typical failure mode:

  1. An evolve step implements several tasks.
  2. Most tasks pass, one task fails.
  3. CoSTEERRAGStrategyV2.current_generated_trace_count is stale from a previous trace.
  4. A new trace with the same length is passed to generate_knowledge().
  5. generate_knowledge() returns None before recording the passing tasks.
  6. The next repair round does not know which tasks already passed and may reimplement the whole group instead of only repairing the failed task.

This wastes LLM calls and can regress previously successful code.

Root cause

current_generated_trace_count is scoped to the RAG strategy instance, but it is interpreted as a cursor for whichever evolving_trace object is currently passed in.

Current logic:

if len(evolving_trace) == self.current_generated_trace_count:
    return None

This is only safe if the cursor is tied to the same trace identity. It is unsafe when a fresh trace has the same length as a previously processed trace.

Suggested fix

Bind the cursor to the identity of the trace object and reset it when a new trace is observed. Also reset if the cursor is larger than the current trace length, which can happen after trace truncation or fresh trace reuse.

Patch sketch:

 class CoSTEERRAGStrategyV2(CoSTEERRAGStrategy):
     def __init__(self, settings: CoSTEERSettings, *args, **kwargs) -> None:
         super().__init__(*args, **kwargs)
         self.current_generated_trace_count = 0
+        self._generated_trace_identity: int | None = None
         self.settings = settings
 
     def generate_knowledge(
         self,
         evolving_trace: list[EvoStep],
         *,
         return_knowledge: bool = False,
     ) -> Knowledge | None:
+        trace_identity = id(evolving_trace)
+        if (
+            getattr(self, "_generated_trace_identity", None) != trace_identity
+            or self.current_generated_trace_count > len(evolving_trace)
+        ):
+            self._generated_trace_identity = trace_identity
+            self.current_generated_trace_count = 0
+
         if len(evolving_trace) == self.current_generated_trace_count:
             return None

Optional additional hardening: in MultiProcessEvolvingStrategy.evolve_iter(), if last_feedback[index].final_decision is True and evo.sub_workspace_list[index] already exists, keep that workspace and do not schedule the task again. That protects against future knowledge-ingestion gaps.

Suggested regression tests

  • CoSTEERRAGStrategyV2.generate_knowledge() should process a fresh trace even when current_generated_trace_count == len(fresh_trace).
  • The cursor should reset when current_generated_trace_count > len(evolving_trace).
  • Partial repair should not reschedule tasks whose previous feedback has final_decision is True and whose workspace already exists.

Notes

This bug is most visible in factor coding workflows with multi-candidate repair, where a round can have several successful candidates and one failed candidate. The expected behavior is to carry forward successful workspaces and only repair the failing candidates.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions