Skip to content
Back to skills

Truncated Step Level Retrieval Reasoning

ASecurity

Improve credit assignment in retrieval-augmented reasoning by truncating trajectories at single decision points. Generate k samples sharing a common prefix, differing only at the next step to isolate variation and reduce gradient variance by T-fold on T-step tasks.

  • 6 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added September 9, 2026
ai-agentspythongit

Works with

  • cli

Security analysis

A100/100

Scanned September 9, 2026

npx -y skills add ADu2021/skillXiv --skill truncated-step-level-retrieval-reasoning --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Truncated Step Level Retrieval Reasoning?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Truncated Step Level Retrieval Reasoning
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/adu2021-truncated-step-level-retrieval-reasoning/badge)](https://www.skillsdirectory.com/skills/adu2021-truncated-step-level-retrieval-reasoning)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: truncated-step-level-retrieval-reasoning
title: "Truncated Step-Level Sampling with Process Rewards for Retrieval-Augmented Reasoning"
version: 0.0.2
engine: skillxiv-v0.0.2-claude-opus-4.6
license: MIT
url: "https://arxiv.org/abs/2602.23440"
keywords: [Retrieval-Augmented Generation, Variance Reduction, Step-Level Rewards, Multi-Hop Reasoning, Credit Assignment]
description: "Improve credit assignment in retrieval-augmented reasoning by truncating trajectories at single decision points. Generate k samples sharing a common prefix, differing only at the next step to isolate variation and reduce gradient variance by T-fold on T-step tasks."
---

# Truncated Step-Level Sampling for Retrieval-Augmented Reasoning

Standard reinforcement learning for retrieval-augmented generation (RAG) tasks suffers from poor credit assignment: high-variance gradient estimates make it hard to distinguish which search query or reasoning step improved final outcomes. Truncated step-level sampling fixes this by generating trajectories that share a prefix and diverge only at a single decision point. This isolation dramatically reduces variance while enabling per-step reward supervision through dense LLM-as-Judge scoring.

The core innovation treats each reasoning step as an isolated decision problem. By fixing all prior steps, the method achieves T-fold variance reduction on T-step tasks while maintaining gradient accuracy through reward decomposition.

## Core Concept

Truncated sampling operates through two coordinated mechanisms:

1. **Prefix-Sharing Trajectories**: Generate k candidate continuations from identical prefixes, isolating variation to a single step. This eliminates confounding from earlier decisions.

2. **Dense Step-Level Rewards**: Score each step independently via LLM judge on three dimensions:
   - Reasoning quality (is the thinking sound?)
   - Query quality (is the search formulation effective?)
   - Answer correctness (does the final answer match?)

These two components combine to provide stable, interpretable learning signals.

## Architecture Overview

- **Input**: Multi-hop retrieval task (question requiring multiple document lookups)
- **Prefix Generation**: Execute steps 1 to t-1 deterministically or sample once, reuse across batch
- **Branching**: Generate k different step-t candidates from shared prefix
- **Evaluation**: LLM judges each step t independently on three dimensions
- **Reward Aggregation**: Combine three reward signals (reasoning + query + answer)
- **Policy Update**: Gradient step on isolated step with reduced variance
- **Output**: Improved retrieval and reasoning policy

## Implementation Steps

**Step 1: Structure multi-hop reasoning task with explicit steps**

Define the reasoning process to enable step-level isolation.

```python
def decompose_rag_task(question):
    """
    Decompose RAG into explicit steps: search → retrieve → reason → search → answer.
    Each step is atomic and independently scorable.
    """
    steps = [
        {'step': 0, 'action': 'formulate_initial_query', 'state': question},
        {'step': 1, 'action': 'retrieve_documents', 'state': None},
        {'step': 2, 'action': 'synthesize_reasoning', 'state': None},
        {'step': 3, 'action': 'formulate_followup_query', 'state': None},
        {'step': 4, 'action': 'retrieve_evidence', 'state': None},
        {'step': 5, 'action': 'generate_final_answer', 'state': None}
    ]

    return steps

# Example: "Who was the first person to reach the South Pole?"
# Step 0: User question
# Step 1: Search "first person South Pole" → retrieve docs about Amundsen, Scott
# Step 2: Synthesize: "Roald Amundsen reached it in 1911"
# Step 3: Formulate follow-up: "Roald Amundsen nationality" (verify credibility)
# Step 4: Retrieve clarification docs
# Step 5: Generate final answer with sources
```

**Step 2: Generate prefix and sample k step-t candidates**

Execute deterministically up to step t-1, then generate k diverse continuations for step t.

```python
def generate_truncated_batch(model, question, num_candidates=4, target_step=2):
    """
    Generate batch of trajectories sharing prefix, differing only at target_step.

    Example with target_step=2:
    - Execute steps 0, 1 once (deterministic or single sample)
    - Generate k candidates for step 2
    - Execute steps 3+ identically for all k candidates
    """
    steps = decompose_rag_task(question)

    # Phase 1: Execute steps before target_step deterministically
    prefix_state = question
    for step_idx in range(target_step):
        step = steps[step_idx]
        if step_idx == 0:
            prefix_state = generate_initial_query(model, question)
        elif step_idx == 1:
            documents = retrieve_documents(prefix_state)
            prefix_state = documents
        # ... continue deterministically

    # Phase 2: Generate k candidates for step target_step
    candidates = []
    for _ in range(num_candidates):
        candidate_action = model.generate(
            f"Given context: {prefix_state}, generate next reasoning step:",
            max_tokens=150,
            temperature=0.7,
            top_p=0.9
        )
        candidates.append(candidate_action)

    # Phase 3: Continuation after target_step (reuse for all candidates)
    trajectories = []
    for candidate in candidates:
        # Forward pass through remaining steps
        trajectory = {
            'prefix': prefix_state,
            'step_t_action': candidate,
            'target_step': target_step,
            'full_trajectory': None
        }

        # Execute remaining steps using this candidate
        current_state = candidate
        for step_idx in range(target_step + 1, len(steps)):
            current_state = execute_step(model, steps[step_idx], current_state)

        trajectory['full_trajectory'] = current_state
        trajectories.append(trajectory)

    return trajectories
```

**Step 3: Compute per-step LLM-as-Judge rewards**

Score each step independently on reasoning quality, query quality, and answer correctness.

```python
def compute_three_part_reward(trajectory, question, reference_answer,
                             judge_model):
    """
    Decompose reward into three scoreable components:
    1. Reasoning quality: Is the thinking sound?
    2. Query quality: Is the search formulation likely to retrieve relevant docs?
    3. Answer correctness: Does final answer match reference?
    """
    target_step = trajectory['target_step']
    step_action = trajectory['step_t_action']
    full_trajectory = trajectory['full_trajectory']

    # Component 1: Reasoning quality (LLM judge)
    reasoning_prompt = f"""
Evaluate the reasoning quality of this step:
Question: {question}
Prefix context: {trajectory['prefix'][:100]}
Reasoning step: {step_action}

Rate on scale 0-1: Is the logic sound? Does it make progress toward answer?
"""

    reasoning_score = judge_model.score(
        reasoning_prompt,
        return_numeric=True  # 0-1 score
    )

    # Component 2: Query quality (relevance of search formulation)
    if 'query' in step_action.lower() or 'search' in step_action.lower():
        query_prompt = f"""
Evaluate query formulation quality:
Original question: {question}
Formulated query: {step_action}

Rate 0-1: Will this retrieve relevant documents?
"""

        query_score = judge_model.score(query_prompt, return_numeric=True)
    else:
        query_score = 0.5  # Neutral if not a query step

    # Component 3: Answer correctness (binary: correct/incorrect)
    final_answer = full_trajectory.split('\n')[-1]  # Last line of trajectory
    is_correct = final_answer.lower() in reference_answer.lower()
    answer_score = 1.0 if is_correct else 0.0

    # Combine components (equal weighting)
    total_reward = (reasoning_score + query_score + answer_score) / 3.0

    return {
        'reasoning_score': reasoning_score,
        'query_score': query_score,
        'answer_score': answer_score,
        'total_reward': total_reward
    }

# Evaluate batch
batch = generate_truncated_batch(model, question, num_candidates=4, target_step=2)
rewards = [compute_three_part_reward(traj, question, reference_answer, judge)
           for traj in batch]
```

**Step 4: Compute policy gradients with variance reduction**

Calculate gradients using isolated step rewards and prove T-fold variance reduction.

```python
def compute_pg_loss_truncated(model, batch, rewards, target_step):
    """
    Policy gradient loss with variance reduction from truncation.

    Key insight: By fixing prefix and only varying step t,
    var(gradient) is reduced by ~T-fold relative to full-trajectory sampling.
    """
    total_loss = 0.0

    for trajectory, reward in zip(batch, rewards):
        # Get log probability of the step-t action under current policy
        step_action = trajectory['step_t_action']
        prefix_context = trajectory['prefix']

        logits = model.forward(
            f"Context: {prefix_context}\nAction:",
            output_logits=True
        )

        # Log probability of sampled action
        action_logprob = compute_logprob(logits, step_action, model.tokenizer)

        # Policy gradient: maximize log-prob of high-reward actions
        # Loss = -logprob * reward (negative for gradient descent)
        step_loss = -action_logprob * reward['total_reward']

        total_loss += step_loss

    return total_loss / len(batch)

def analyze_variance_reduction(trajectories_full, trajectories_truncated):
    """
    Empirically verify T-fold variance reduction.
    trajectories_full: full-trajectory samples from baseline
    trajectories_truncated: truncated samples from this method
    """
    # Compute gradient variance
    grads_full = [compute_gradient(traj) for traj in trajectories_full]
    grads_truncated = [compute_gradient(traj) for traj in trajectories_truncated]

    var_full = np.var([g.norm().item() for g in grads_full])
    var_truncated = np.var([g.norm().item() for g in grads_truncated])

    # Expected T-fold reduction
    T = 5  # Assume 5-step tasks
    expected_ratio = T
    observed_ratio = var_full / var_truncated

    print(f"Variance reduction: {observed_ratio:.1f}x (expected ~{expected_ratio}x)")
    return observed_ratio
```

**Step 5: RL training loop with step-level updates**

Integrate truncated sampling into standard policy gradient training.

```python
def train_rag_with_truncated_sampling(model, judge_model, train_tasks,
                                     num_steps=10000, batch_size=32):
    """
    Train retrieval-augmented reasoning with truncated step-level sampling.
    """
    optimizer = torch.optim.AdamW(model.parameters(), lr=1e-4)

    for step in range(num_steps):
        # Sample tasks from training set
        task_batch = random.sample(train_tasks, batch_size)

        total_loss = 0.0

        for question, reference_answer in task_batch:
            # Randomly select which step to optimize
            target_step = random.randint(1, 4)

            # Generate truncated batch (k candidates for step target_step)
            trajectories = generate_truncated_batch(
                model,
                question,
                num_candidates=4,
                target_step=target_step
            )

            # Compute rewards
            rewards = [
                compute_three_part_reward(traj, question, reference_answer, judge_model)
                for traj in trajectories
            ]

            # Compute loss
            loss = compute_pg_loss_truncated(model, trajectories, rewards, target_step)

            total_loss += loss

        # Gradient step
        optimizer.zero_grad()
        (total_loss / len(task_batch)).backward()
        torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
        optimizer.step()

        if (step + 1) % 100 == 0:
            print(f"Step {step + 1}: Loss = {(total_loss / len(task_batch)).item():.4f}")

    return model
```

**Step 6: Evaluation on multi-hop QA benchmarks**

Benchmark on tasks requiring multiple retrieval steps (TREC, HotpotQA, etc.).

```python
def evaluate_on_multihop_qa(model, eval_tasks):
    """
    Evaluate on multi-hop QA: questions requiring 2-4 retrieval steps.
    """
    correct = 0
    total = 0

    for question, reference_answer, num_hops in eval_tasks:
        # Execute full trajectory
        trajectory = execute_full_rag(model, question, max_steps=num_hops)

        # Extract final answer
        final_answer = trajectory.split('\n')[-1]

        # Check correctness
        if final_answer.lower() in reference_answer.lower():
            correct += 1

        total += 1

    accuracy = correct / total
    print(f"Multi-hop QA accuracy: {accuracy * 100:.1f}%")

    return accuracy
```

## Practical Guidance

**Hyperparameter Selection:**
- **Number of candidates k**: 2-4. More candidates = better coverage; diminishing returns beyond 4.
- **Target step selection**: Sample uniformly from [1, T] for balanced training, or focus on early steps for faster learning.
- **Reward weights**: Equal [1/3, 1/3, 1/3] standard; can emphasize answer_score for final tuning.
- **Judge model temperature**: 0.5-0.7 for stable scoring; lower = more consistent, higher = more nuanced.

**When to Use:**
- Multi-hop retrieval tasks (2-5 steps) where single-step methods fail
- Scenarios where every reasoning step affects downstream success
- Settings with access to LLM judge for step-level scoring
- Tasks where variance reduction significantly improves convergence

**When NOT to Use:**
- Single-hop retrieval (no multi-step credit assignment benefit)
- Tasks with very long trajectories (T > 10); variance reduction diminishes
- Real-time systems where multiple forward passes are expensive
- Scenarios without reliable reference answers for reward feedback

**Common Pitfalls:**
- **Inconsistent judge scoring**: If judge is non-deterministic, rewards are noisy. Use deterministic judge or averaging over multiple runs.
- **Step isolation breaks dependencies**: Some steps may be impossible without specific prefix context. Validate that sampled candidates are valid.
- **Reward signal too sparse**: If most samples have zero answer_score, provide auxiliary rewards (query quality, reasoning soundness) to maintain signal.
- **Target step bias**: If always sampling early steps, later steps never get optimized. Ensure uniform or curriculum-based step selection.

## Reference

arXiv: https://arxiv.org/abs/2602.23440

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.