ruvnet/ruflo · error

Proposal not found: ${proposalId}

Error message

Proposal not found: ${proposalId}

What it means

EvolutionPipeline.simulate() runs golden traces through baseline and candidate configs for a change proposal stored in the in-memory `proposals` Map, keyed by the ID returned from propose(). It throws when that ID is unknown — typically a stale ID from before a restart or a typo'd/copied value. Simulation is the first lifecycle step that consumes a proposal, so it is often where an invalid ID is discovered.

Source

Thrown at v3/@claude-flow/guidance/src/evolution.ts:319

    return proposal;
  }

  // ==========================================================================
  // Simulate
  // ==========================================================================

  /**
   * Run golden traces through both baseline and candidate configs to measure
   * divergence. The evaluator is called once per golden trace per config.
   */
  simulate(
    proposalId: string,
    goldenTraces: unknown[],
    evaluator: TraceEvaluator,
  ): SimulationResult {
    const proposal = this.proposals.get(proposalId);
    if (!proposal) {
      throw new Error(`Proposal not found: ${proposalId}`);
    }

    proposal.status = 'simulating';

    // Evaluate each trace against both configs
    const baselineResults = goldenTraces.map(t => evaluator(t, 'baseline'));
    const candidateResults = goldenTraces.map(t => evaluator(t, 'candidate'));

    // Compute composite trace hashes
    const baselineTraceHash = this.hashTraceResults(baselineResults.map(r => r.traceHash));
    const candidateTraceHash = this.hashTraceResults(candidateResults.map(r => r.traceHash));

    // Compute decision diffs
    const decisionDiffs: DecisionDiff[] = [];
    for (let i = 0; i < goldenTraces.length; i++) {
      const bDecisions = baselineResults[i].decisions;
      const cDecisions = candidateResults[i].decisions;
      const maxLen = Math.max(bDecisions.length, cDecisions.length);

View on GitHub (pinned to fa13ee4ad6)

Solutions

  1. Use the exact proposalId returned by `pipeline.propose(...)`
  2. Guard with `if (!pipeline.getProposal(proposalId)) return;` before simulating
  3. Re-propose if the pipeline restarted (state is in-memory)
  4. Keep the whole propose→simulate→compare→stage→advance lifecycle on one pipeline instance

Example fix

// before
const sim = pipeline.simulate(hardcodedId, traces, evaluator); // throws

// after
if (pipeline.getProposal(hardcodedId)) {
  const sim = pipeline.simulate(hardcodedId, traces, evaluator);
} else {
  const { proposalId } = pipeline.propose(changes);
  const sim = pipeline.simulate(proposalId, traces, evaluator);
}
Defensive patterns

Strategy: validation

Validate before calling

if (pipeline.getProposal(proposalId) === undefined) {
  // stale/unknown ID — re-propose instead of simulating
}

Try / catch

try {
  const sim = pipeline.simulate(proposalId, traces, evaluator);
} catch (err) {
  if (err instanceof Error && err.message.startsWith('Proposal not found')) {
    // restart wiped the Map — re-propose and retry once
  } else throw err;
}

Prevention

When it happens

Trigger: Calling `pipeline.simulate(id, traces, evaluator)` with an ID not returned by propose(); pipeline process restarted between propose() and simulate(); ID passed through queues/logs and mangled.

Common situations: Proposing in one service instance and simulating in another; long queues between propose and simulate surviving a redeploy; test code hard-coding IDs from a previous run.

Understand the failure class

Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.

Related errors


AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18). Data as JSON: /api/errors/8e7f0d41337e3612. Report an issue: GitHub.