mirror of
https://github.com/nexu-io/open-design.git
synced 2026-05-31 19:04:39 +07:00
recording-picker: structured OD_MOCKS_POOL + hard-fail no-match
Siri-Ray review: \`OD_MOCKS_POOL=outcome:failed\` was documented as a supported selection knob, but the matcher only checked tags and \`meta.agent\` — so the negative-path pool found 0 candidates and silently fell through to global random, validating against any recording instead of a failed trace. Fix: - Parse \`<dim>:<value>\` shape and route each dim to the right meta field: \`outcome\` → \`meta.outcome\`, \`agent\` → \`meta.agent\`, \`skill\` → \`tags[]\`. Bare values still fall back to tag substring. - If the env was set and matched nothing, throw with the failing value and a jq one-liner for inspection. Same loud-fail policy as OD_MOCKS_TRACE — silent fallback was the original bug. Verified locally: outcome:failed, agent:codex, skill:agent-browser all route correctly; outcome:nonsense throws the explicit error. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
df8ee162a3
commit
e576074ad9
1 changed files with 29 additions and 11 deletions
|
|
@ -73,26 +73,44 @@ export async function pickRecording({ prompt } = {}) {
|
|||
if (picked) return { traceId: picked, path: join(dir, `${picked}.jsonl`), method: 'hash' };
|
||||
}
|
||||
|
||||
// 3. pool by tag
|
||||
// 3. pool by tag — supports structured `<dimension>:<value>` shortcuts
|
||||
// documented in README (agent:claude, skill:agent-browser,
|
||||
// outcome:failed). The dimension routes to the right meta field;
|
||||
// bare values fall back to tag substring match. Mirrors the
|
||||
// OD_MOCKS_TRACE policy: if the env is set and matches nothing,
|
||||
// refuse to fall through to global random — surface the typo.
|
||||
const pool = process.env.OD_MOCKS_POOL;
|
||||
if (pool) {
|
||||
const colonIdx = pool.indexOf(':');
|
||||
const dim = colonIdx >= 0 ? pool.slice(0, colonIdx) : null;
|
||||
const value = colonIdx >= 0 ? pool.slice(colonIdx + 1) : null;
|
||||
|
||||
const candidates = [];
|
||||
for (const id of all) {
|
||||
const meta = await readMeta(dir, id);
|
||||
if (!meta) continue;
|
||||
const tags = meta.tags ?? [];
|
||||
if (
|
||||
tags.includes(pool) ||
|
||||
meta.agent === pool ||
|
||||
tags.some(t => typeof t === 'string' && t.includes(pool))
|
||||
) {
|
||||
candidates.push(id);
|
||||
}
|
||||
|
||||
let match = false;
|
||||
if (dim === 'outcome' && meta.outcome === value) match = true;
|
||||
else if (dim === 'agent' && meta.agent === value) match = true;
|
||||
else if (dim === 'skill' && tags.some(t => t === `skill:${value}`)) match = true;
|
||||
else if (tags.includes(pool)) match = true;
|
||||
else if (meta.agent === pool) match = true;
|
||||
else if (tags.some(t => typeof t === 'string' && t.includes(pool))) match = true;
|
||||
|
||||
if (match) candidates.push(id);
|
||||
}
|
||||
if (candidates.length > 0) {
|
||||
const picked = pickRandom(candidates, process.env.OD_MOCKS_SEED);
|
||||
if (picked) return { traceId: picked, path: join(dir, `${picked}.jsonl`), method: 'pool', pool };
|
||||
if (candidates.length === 0) {
|
||||
throw new Error(
|
||||
`OD_MOCKS_POOL="${pool}" matched no recordings in ${dir}. ` +
|
||||
`Supported shapes: agent:<name>, skill:<name>, outcome:<succeeded|failed|errored>, ` +
|
||||
`or any tag substring. Check candidates with ` +
|
||||
`\`jq '[.entries[] | {agent, outcome, skills}] | unique' mocks/manifest.json\`.`,
|
||||
);
|
||||
}
|
||||
const picked = pickRandom(candidates, process.env.OD_MOCKS_SEED);
|
||||
if (picked) return { traceId: picked, path: join(dir, `${picked}.jsonl`), method: 'pool', pool };
|
||||
}
|
||||
|
||||
// 4. random
|
||||
|
|
|
|||
Loading…
Reference in a new issue