ruvnet/ruflo · error · Error
at least one candidate is required
Error message
at least one candidate is required
What it means
Thrown by the agenticow speculate tool handler when input.candidates is not an array or is empty. Speculation requires at least one candidate (each candidate ingests into its own branch and is scored against the probe); with zero candidates there is nothing to rank and the winner-promotion step is undefined, so the handler rejects early before touching the agenticow API.
Source
Thrown at v3/@claude-flow/cli/src/mcp-tools/agenticow-speculate-tools.ts:146
description: 'ADR-171 fail-closed gate. When true, the top-scored winner is NOT promoted (base stays unchanged) and a provenance-tagged receipt is emitted — score alone cannot graduate work. Use when speculating over TASK outcomes rather than pure memory A/B. Default false (score-only promotion, tagged `unverified`).',
default: false,
},
},
required: ['basePath', 'candidates'],
},
handler: async (input) => {
const api = await loadAgenticow();
if (!api) return degradedResult('agenticow-not-found');
const basePath = resolveMemoryPath(String(input.basePath));
const dimension = input.dimension as number | undefined;
const scoreBy = (input.scoreBy as string) === 'count' ? 'count' : 'nearest';
const k = Number.isInteger(input.k) && (input.k as number) > 0 ? (input.k as number) : 1;
const probe = Array.isArray(input.probe) ? (input.probe as number[]) : null;
const rawCandidates = input.candidates as CandidateInput[];
if (!Array.isArray(rawCandidates) || rawCandidates.length === 0) {
throw new Error('at least one candidate is required');
}
if (scoreBy === 'nearest' && !probe) {
throw new Error("scoreBy='nearest' requires a probe vector");
}
// Build the generic {label, fn} candidates. Each fn ingests into its own
// branch handle, then (for 'nearest') probes it so we can score.
// Map validated label → explicit branchPath so the branchPath() resolver
// below is O(1) instead of re-scanning + re-validating rawCandidates per
// candidate (explore() calls branchPath once per candidate → was O(n²)).
const explicitBranchPaths = new Map<string, string>();
const candidates: SpeculativeCandidate<CandidateOutcome>[] = rawCandidates.map((c) => {
const label = validateLabel(String(c.label));
if (!Array.isArray(c.ingest) || c.ingest.length === 0) {
throw new Error(`candidate ${label} must ingest at least one vector`);
}
if (typeof c.branchPath === 'string' && c.branchPath) {
explicitBranchPaths.set(label, c.branchPath);View on GitHub (pinned to 6b01dc5a68)
Solutions
- Pass a non-empty array of candidate objects, each with at least `label` and `ingest`.
- If your pipeline legitimately produces zero candidates, skip the speculate call rather than invoking with an empty array.
- Validate upstream: `Array.isArray(c) && c.length > 0` before calling the tool.
Example fix
// before
speculate({ basePath, candidates: [], scoreBy: 'nearest', probe: [0.1] }); // throws
// after
speculate({ basePath, candidates: [{ label: 'a', ingest: [{vector:[...]}] }], scoreBy: 'nearest', probe: [0.1] }); Defensive patterns
Strategy: validation
Validate before calling
function requireCandidates(c: unknown): Array<{ label: string; ingest: unknown[] }> {
if (!Array.isArray(c) || c.length === 0) throw new Error('candidates must be a non-empty array');
return c as Array<{ label: string; ingest: unknown[] }>;
} Type guard
const isNonEmptyArray = (v: unknown): v is unknown[] => Array.isArray(v) && v.length > 0;
Try / catch
null
Prevention
- Validate candidates is a non-empty array at your boundary before calling speculate.
- Skip the speculate call entirely when your pipeline legitimately has zero candidates.
- Add a unit test for the empty-array path so it surfaces in your layer.
When it happens
Trigger: Calling speculate without candidates; passing candidates as an empty array; passing a single object instead of an array; a pipeline that filtered out all candidates before invoking.
Common situations: Integrator built the candidates list conditionally and hit the empty branch; a fixture placeholder `candidates: []`; a serializer that returned undefined for the field; all candidates were filtered out by an upstream rule.
Related errors
AI-assisted analysis of ruvnet/ruflo@6b01dc5a68 (2026-08-12).
Data as JSON: /api/errors/9e8190755ae63357.
Report an issue: GitHub.