ruvnet/ruflo · error · Error
unknown strategy "${name}". Available: ${roster.map((r) => r
Error message
unknown strategy "${name}". Available: ${roster.map((r) => r.name).join(', ')} What it means
Thrown by findStrategy() when no entry in the classic roster matches the requested name. The roster is built per-game by classicRoster(game) (strategies.ts:103); matching is permissive — a roster entry counts as a hit if its name equals the query OR starts with the query (r.name === name || r.name.startsWith(name)). The message lists every roster name for the game so the caller can pick a valid one.
Source
Thrown at plugins/ruflo-arena/src/domain/strategies.ts:130
antiCopy(a, 0, 'suspicious-anti-tft'),
alternate(a, 'alternate'),
random(a, 'random'),
];
}
return [
constant(a, 0, `always-${a[0]}`),
constant(a, 1, `always-${a[1]}`),
copyOpponent(a, 0, 'copy-opponent'),
antiCopy(a, 0, 'anti-copy'),
alternate(a, 'alternate'),
random(a, 'random'),
];
}
export function findStrategy(game: GameSpec, name: string): Strategy {
const roster = classicRoster(game);
const s = roster.find((r) => r.name === name || r.name.startsWith(name));
if (!s) throw new Error(`unknown strategy "${name}". Available: ${roster.map((r) => r.name).join(', ')}`);
return s;
}
// --- Evolvable FSMs — random genomes + mutation operators (ADR-148) ------------------------
export function randomFSM(game: GameSpec, rng: () => number, nStates = 2, name = 'evolved'): FsmStrategy {
const a = game.actions;
const states = [];
for (let i = 0; i < nStates; i++) {
states.push({
action: choice(rng, a),
next: Object.fromEntries(a.map((x) => [x, randInt(rng, nStates)])),
});
}
return { kind: 'fsm', name, nStates, start: randInt(rng, nStates), states };
}
function cloneFSM(fsm: FsmStrategy): FsmStrategy {View on GitHub (pinned to 6b01dc5a68)
Solutions
- Use an exact roster name from the error's 'Available:' list, or a unique prefix of one (e.g. 'tit' resolves to 'tit-for-tat' on PD).
- Confirm the roster is for the right game — classicRoster returns a different list for 'prisoners-dilemma' vs other games.
- If you need a custom/evolved strategy, construct it directly (e.g. via randomFSM/mutateFSM) instead of going through findStrategy, which only resolves the classic roster.
- At an API boundary, validate name against the roster via findStrategy's prefix semantics and reject unknown names with a 400.
Example fix
// before
const s = findStrategy(game, req.body.strategy); // throws on miss
// after
import { classicRoster, findStrategy } from './strategies';
const roster = classicRoster(game);
const matches = roster.filter(r => r.name === req.body.strategy || r.name.startsWith(req.body.strategy));
if (matches.length === 0) {
return res.status(400).json({ error: `unknown strategy; available: ${roster.map(r => r.name).join(', ')}` });
}
const s = findStrategy(game, req.body.strategy); Defensive patterns
Strategy: validation
Validate before calling
import { classicRoster } from './strategies';
function resolveStrategy(game, name: unknown): Strategy | null {
if (typeof name !== 'string') return null;
const roster = classicRoster(game);
return roster.find(r => r.name === name || r.name.startsWith(name)) ?? null;
}
const s = resolveStrategy(game, input);
if (!s) return res.status(400).json({ error: 'unknown strategy' }); Type guard
function isKnownStrategy(game: GameSpec, name: string): boolean {
return classicRoster(game).some(r => r.name === name || r.name.startsWith(name));
} Try / catch
try { const s = findStrategy(game, name); } catch (e) { if (e instanceof Error && e.message.startsWith('unknown strategy')) return res.status(400).json({ error: e.message }); throw e; } Prevention
- Remember findStrategy supports prefix matches, so use a unique prefix to avoid ambiguity.
- Use strategy names from the roster that matches the game you passed.
- Do not route evolved/FSM strategy names through findStrategy.
When it happens
Trigger: Calling findStrategy(game, name) where name matches neither exactly nor as a prefix of any roster entry. For prisoner's-dilemma the roster is tit-for-tat, always-cooperate, always-defect, grim, pavlov, suspicious-anti-tft, alternate, random; for other games it is always-<a0>, always-<a1>, copy-opponent, anti-copy, alternate, random. Examples: findStrategy(pd, 'generous-tit-for-tat') (not in roster), findStrategy(mon, 'tit-for-tat') (PD-only strategy used against a different game).
Common situations: Using a strategy name documented for a different game than the one passed; expecting prefix 'tf' to match when the roster entry is 'tit-for-tat' (it will match, but 'tft' will not); passing an evolved/FSM strategy name to findStrategy (those are not in the classic roster); user-supplied strategy string from a config file or HTTP body.
Related errors
- unknown game "${key}". Known: ${Object.keys(GAMES).join(', '
- Invalid completion type
- Invalid hostname
- No models available to build validation schema
- Invalid route entry: ${JSON.stringify(r)}
AI-assisted analysis of ruvnet/ruflo@6b01dc5a68 (2026-08-12).
Data as JSON: /api/errors/8df4598af09389ff.
Report an issue: GitHub.