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 (agenticow-speculate-tools.ts:146) when the candidates argument is not a non-empty array. Speculative exploration forks the base memory into one branch per candidate, so at least one {label, ingest} candidate must be supplied for explore() to have anything to score and merge back.
Solutions
- Pass at least one candidate: candidates: [{ label: 'exp-a', ingest: [{ vector: [...] }] }]
- Wrap a single candidate in an array rather than passing it bare
- Skip the call entirely when your candidate generator produced zero entries
Example fix
// before
await callTool('agenticow_speculate', {
basePath: 'memory/main.rvf',
candidates: [], // throws: at least one candidate is required
});
// after
await callTool('agenticow_speculate', {
basePath: 'memory/main.rvf',
candidates: [{ label: 'exp-a', ingest: [{ vector: [0.1, 0.2] }] }],
}); Defensive patterns
Strategy: validation
Validate before calling
function isCandidateList(v: unknown): v is Array<{ label: string; ingest: unknown[] }> {
return Array.isArray(v) && v.length > 0 && v.every((c) => c && typeof c.label === 'string' && Array.isArray(c.ingest) && c.ingest.length > 0);
}
if (!isCandidateList(args.candidates)) throw new TypeError('candidates must be a non-empty array of {label, ingest[]}'); Type guard
const isNonEmptyArray = (v: unknown): v is unknown[] => Array.isArray(v) && v.length > 0;
Prevention
- Skip the speculate call when your candidate generator produced zero entries
- Always wrap a single candidate in an array
- Validate the whole candidates array client-side — one bad entry fails the whole call server-side
When it happens
Trigger: Calling agenticow_speculate with candidates omitted, candidates: [], or candidates as an object instead of an array (e.g. a single candidate passed unwrapped: candidates: {label: 'x', ingest: [...]}).
Common situations: Candidates built dynamically and a filter/map step returned an empty array; unwrapping a list into a single object by mistake; an LLM omitting the array when the plan had zero branches.
Related errors
- candidate must ingest at least one vector
- records must be a non-empty array of
- scoreBy='nearest' requires a probe vector
- each record requires a non-empty numeric vector
- label exceeds 256 chars
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/9e8190755ae63357.
Report an issue: GitHub.
Appendix: 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 fa13ee4ad6)