{"record":{"id":"9e8190755ae63357","repo":"ruvnet/ruflo","slug":"at-least-one-candidate-is-required","errorCode":null,"errorMessage":"at least one candidate is required","messagePattern":"at least one candidate is required","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/cli/src/mcp-tools/agenticow-speculate-tools.ts","lineNumber":146,"sourceCode":"          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`).',\n          default: false,\n        },\n      },\n      required: ['basePath', 'candidates'],\n    },\n    handler: async (input) => {\n      const api = await loadAgenticow();\n      if (!api) return degradedResult('agenticow-not-found');\n\n      const basePath = resolveMemoryPath(String(input.basePath));\n      const dimension = input.dimension as number | undefined;\n      const scoreBy = (input.scoreBy as string) === 'count' ? 'count' : 'nearest';\n      const k = Number.isInteger(input.k) && (input.k as number) > 0 ? (input.k as number) : 1;\n      const probe = Array.isArray(input.probe) ? (input.probe as number[]) : null;\n\n      const rawCandidates = input.candidates as CandidateInput[];\n      if (!Array.isArray(rawCandidates) || rawCandidates.length === 0) {\n        throw new Error('at least one candidate is required');\n      }\n      if (scoreBy === 'nearest' && !probe) {\n        throw new Error(\"scoreBy='nearest' requires a probe vector\");\n      }\n\n      // Build the generic {label, fn} candidates. Each fn ingests into its own\n      // branch handle, then (for 'nearest') probes it so we can score.\n      // Map validated label → explicit branchPath so the branchPath() resolver\n      // below is O(1) instead of re-scanning + re-validating rawCandidates per\n      // candidate (explore() calls branchPath once per candidate → was O(n²)).\n      const explicitBranchPaths = new Map<string, string>();\n      const candidates: SpeculativeCandidate<CandidateOutcome>[] = rawCandidates.map((c) => {\n        const label = validateLabel(String(c.label));\n        if (!Array.isArray(c.ingest) || c.ingest.length === 0) {\n          throw new Error(`candidate ${label} must ingest at least one vector`);\n        }\n        if (typeof c.branchPath === 'string' && c.branchPath) {\n          explicitBranchPaths.set(label, c.branchPath);","sourceCodeStart":128,"sourceCodeEnd":164,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/v3/@claude-flow/cli/src/mcp-tools/agenticow-speculate-tools.ts#L128-L164","documentation":"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.","triggerScenarios":"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: [...]}).","commonSituations":"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.","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"],"exampleFix":"// before\nawait callTool('agenticow_speculate', {\n  basePath: 'memory/main.rvf',\n  candidates: [], // throws: at least one candidate is required\n});\n\n// after\nawait callTool('agenticow_speculate', {\n  basePath: 'memory/main.rvf',\n  candidates: [{ label: 'exp-a', ingest: [{ vector: [0.1, 0.2] }] }],\n});","handlingStrategy":"validation","validationCode":"function isCandidateList(v: unknown): v is Array<{ label: string; ingest: unknown[] }> {\n  return Array.isArray(v) && v.length > 0 && v.every((c) => c && typeof c.label === 'string' && Array.isArray(c.ingest) && c.ingest.length > 0);\n}\nif (!isCandidateList(args.candidates)) throw new TypeError('candidates must be a non-empty array of {label, ingest[]}');","typeGuard":"const isNonEmptyArray = (v: unknown): v is unknown[] => Array.isArray(v) && v.length > 0;","tryCatchPattern":null,"preventionTips":["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"],"tags":["validation","agenticow","speculative","mcp","empty-array"],"backgroundTag":"empty-array-parameter","analyzedSha":"fa13ee4ad60ac2090b1480656eb233521790d640","analyzedAt":"2026-08-18T21:34:22.708Z","contentChangedAt":"2026-08-18T21:34:22.708Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}