framework/devtools/skills/write-agent-benchmarks/SKILL.md
Create, maintain, and run evidence-based benchmarks for AI agents. Use when setting up testing infrastructure, writing new test scenarios, or evaluating agent performance.
npx skillsauth add korchasa/flowai write-agent-benchmarksInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
This skill defines a universal, language-agnostic standard for benchmarking Autonomous AI Agents. The goal is to objectively measure an agent's ability to solve real-world tasks, whether they are coding, data analysis, or conversational.
The system supports three primary evaluation modes:
Quality Evaluation (Checklist-based):
Model Selection (Pairwise Comparison):
Version Comparison (Regression Tracking):
Choosing the right interaction strategy is critical for stable benchmarks.
A robust benchmarking system consists of five key modules.
The isolated state container where the task is performed. It is not limited to a file system.
Setup (initial state), Reset (between runs), and Teardown.The central controller managing the test lifecycle.
For interactive agents that ask clarifying questions.
The logic that determines if a test passed or failed based on Evidence.
Complete capture of the agent's lifecycle in a single human-readable file (e.g., trace.md or trace.json).
Follow this process to add a new benchmark scenario.
What specific capability are you testing?
Create the initial state.
Write the prompt that instructs the agent.
How do we know it worked?
script.py runs with exit code 0.users has 1 new row.GET /api/v1/flights with correct parameters.Add the scenario to your Runner's registry.
If a benchmark fails, check the Trace:
When a scenario fails, especially a verbatim_relay / mock-reached-agent
check, do NOT jump straight to rewriting SKILL.md — the test infrastructure
is the most common culprit (e.g. a mocked tool invoked by absolute path
bypasses the PATH shadow, so the real binary runs and the scenario "passes"
on synthesis, not relay).
Mock mechanism (ACP): scenario.mocks is a static one-response-per-tool
map. The runner writes a stub per tool into a mockbin/ dir prepended to
the agent's PATH (writeMockBin, acp/mock_bin.ts); each stub prints the
canned text to stdout+stderr and exits 0. So when the agent runs the tool
by name, it gets exactly the mock text — IDE-agnostic, no hooks.
Run this checklist first:
ls <workDir>/mockbin/ should show an executable
<tool> (mode 755). Absence → the scenario declared no mocks, or
mockbin was not prepended to PATH.PATH. An absolute/relative path (/usr/bin/<tool>, ./<tool>)
or a shell builtin bypasses the stub. Confirm the body calls the bare
name (curl …, not /usr/bin/curl …).[benchmock-<6-hex>]) absent from the skill's SKILL.md and
examples. Grep the judge output for it: present → the stub ran and the
agent quoted it; absent → synthesis, NOT relay. Only robust relay signal.MOCK: prefix") — real CLIs don't emit it, and
teaching the agent to preserve it corrupts real behaviour. Let the mock's
distinctive content prove the relay, not the framing.Only after steps 1–4 pass is it safe to edit SKILL.md. Skipping this wastes bench cycles and frequently introduces regressions.
Execution scenarios prove "when skill X runs, it works." They do NOT prove that the model picks skill X for a relevant query, or that it stands down for an unrelated one. Trigger scenarios close that gap: they verify description-matching correctness.
framework/<pack>/skills/*. Commands (commands/) carry disable-model-invocation: true and are out of scope.framework/<pack>/skills/<skill-id>/benchmarks/
trigger-pos-1/mod.ts
trigger-adj-1/mod.ts
trigger-false-1/mod.ts
<skill-id>-trigger-<pos|adj|false>-1 (the trailing -1 is preserved for backward compatibility with trace tooling; only n=1 is permitted).scripts/check-trigger-coverage.ts (wired into deno task check) fails if any of the 3 are missing, or if stray trigger-{type}-{2,3,...} directories exist.With N=1, each query carries the full description-match weight for its class — pick the phrasing most likely to expose a description regression.
trigger-pos-1): a natural, short user query that matches the skill's description. No /skill-name prefix (that bypasses description-matching), no over-specified jargon, no hints at internal mechanics. Pick the phrasing a typical user would write — the least-jargonized form — so the test stresses description match, not exact wording.trigger-adj-1): a query for which a different, neighboring skill is the correct match. Pick the most-likely confusion candidate from the same pack or with overlapping vocabulary. Typical confusion patterns: a "fix this test" skill vs. a "review my diff" skill (overlap on "I broke something"); a single-task planner vs. a multi-phase epic planner (overlap on "plan"); a current-session reflection vs. a historical-sessions reflection (overlap on "reflect").trigger-false-1): a query inside the skill's general domain but with the wrong intent. Recommended patterns: surface vocabulary that matches but the actual ask is something else (e.g., a planning skill receiving "plan" in a non-software-task sense; a fix-tests skill receiving a "speed up the test runner" perf request); reverse-intent traps (e.g., write new tests vs fix failing ones). Do NOT use meta-questions about the skill itself ("what does X cover?", "how does X work?", "when should I use X?") as false-use — under Claude Code these are legitimately answered by reading the skill's SKILL.md, so the agent will rightly load it and the judge will record activation. Treat meta-questions as positives or omit them.Every trigger scenario carries exactly one critical checklist item.
trigger-pos-*):
checklist = [{
id: "skill_invoked",
description: "Did the agent load and act on `<skill-id>` in response to this query? Look in the trace for a `Skill` tool call or a read of the skill's `SKILL.md` for `<skill-id>`.",
critical: true,
}];
trigger-adj-* and trigger-false-*):
checklist = [{
id: "skill_not_invoked",
description: "Did the agent AVOID loading `<skill-id>`? For this query the skill is not appropriate; the agent should either invoke a different skill or respond directly without reading `<skill-id>/SKILL.md` or calling the `Skill` tool with `<skill-id>`.",
critical: true,
}];
mod.ts)import { AcceptanceTestScenario } from "@acceptance-tests/types.ts";
export const TriggerPos1 = new class extends AcceptanceTestScenario {
id = "<skill-id>-trigger-pos-1";
name = "<short label, e.g. 'natural fix-tests query'>";
skill = "<skill-id>";
agentsTemplateVars = { PROJECT_NAME: "Sandbox" };
userQuery = "<natural user query>";
checklist = [{
id: "skill_invoked",
description:
"Did the agent load and act on `<skill-id>` in response to this query? Look in the trace for a `Skill` tool call or a read of the skill's `SKILL.md` for `<skill-id>`.",
critical: true,
}];
}();
Before scaling, write one positive scenario and run it; confirm the judge correctly fails the run when the skill's description is mangled to be unrelated. Then revert the description. This validates the pattern end-to-end. See SRS FR-ACCEPT.TRIGGER, SDS §3.4.2.
Subagents (files under framework/<pack>/agents/<name>.md) MUST be tested through their wrapping skill scenario, not as standalone AcceptanceTestAgentScenario runs. The framework spawns the main runtime in -p mode with userQuery as the user message; the agent .md file is copied to .claude/agents/ only as a template the main runtime may dispatch to. There is no path that loads the subagent's body as a system prompt for direct execution. A standalone AcceptanceTestAgentScenario therefore tests the main runtime's behaviour given access to the agent template — NOT the agent's body.
Two consequences:
via-subagent-style scenario (parent skill → Agent/Task tool → subagent → mocked CLI → relay back). Checklists should gate on the parent-side dispatch (worker_subagent_invoked) and the relay signal (mock_content_relayed), both of which ARE observable from the flat trace.Precedent: existing worker-style subagents in the framework have no acceptance-tests/ directory of their own; they are tested only via their orchestrating skill's scenarios.
The transcript AcpAgent accumulates over the ACP session does NOT preserve parent-vs-subagent nesting. When a parent invokes Agent(subagent_type=...), the subagent's internal Bash calls appear at the same top-level as the parent's tool calls. Avoid checklist items like "no Bash("codex …") in the parent" — the judge cannot distinguish parent-side from worker-side Bash from the flat trace alone. Instead gate on the presence of the Agent/Task dispatch and on the relay-content signal; together they imply the worker did the work.
The PATH-shadow stub fires whenever the mocked tool name resolves through PATH — regardless of env prefixes, pipes, or subshells. CLAUDECODE="" codex exec "$P", echo "$P" | codex exec -, and ( codex … ) all hit the stub. (An improvement over the retired PreToolUse(Bash) hook, which parsed only the first bare word and missed those forms.)
What DOES bypass the stub:
/usr/bin/codex or ./codex skip PATH → the real binary runs. In the sandbox this usually surfaces as an auth error (e.g. Codex 401) that looks like a setup bug, not a mock-miss. Invoke the bare tool name (codex exec "$P").echo, cd) aren't PATH-resolved, so they can't be shadowed — but they're not real targets either, so this rarely matters.bash: curl: command not found).The stub prints exactly the canned text you put in scenario.mocks — there is no auto-injected harness prefix. So whatever framing you add (e.g. [benchmock-xxx] CODEX-MOCK: <body>) you control yourself. Agents under a courier rule (verbatim relay of a child runtime's stdout, common in cross-IDE or LLM-as-judge skills) will reasonably strip such prefix-looking tokens as harness artefacts — that is correct behaviour per such a contract, not a relay failure.
To verify relay actually happened, embed a deliberately-absurd phrase inside the mock body and check for that phrase in the final answer. The phrase must be:
Examples drawn from passing scenarios: alphabetise your tuples on Wednesdays, octopus-shaped type definitions, tag mutable state with marigold-coloured comments, alphabetise trailing semicolons before lowercase Friday refactors.
Prefix-style framing tokens ([benchmock-xxx], <TOOL>-MOCK:) fail as relay signals: they read as harness artefacts, and the courier rule permits stripping them. Don't gate on them — gate on the absurd phrase inside the body.
To ensure cross-platform compatibility, benchmark results must follow a standard JSON schema.
{
"scenario_id": "string",
"outcome": "pass|fail",
"score": 0-100,
"metrics": {
"duration_ms": 1200,
"cost_usd": 0.01,
"steps_taken": 5,
"tokens_used": 1500
},
"evidence": {
"artifacts": ["file_paths"],
"logs": ["log_entries"]
},
"checklist": [
{ "id": "check_1", "status": "pass", "reason": "..." }
]
}
temperature: 0).tools
Delegate a task to another AI IDE's CLI (codex / claude / opencode / cursor-agent) through an isolated-context subagent. Triggers on "delegate to <ide>", "have <ide> do <task>", "execute <task> in <ide>", "offload to <ide>". For one-shot relay or fan-out comparison use `ai-ide-runner` instead.
tools
Run prompts in Claude Code, OpenCode, Cursor, or Codex CLIs from the current session — pick one IDE, fan out across several, or compare models. You are a courier that relays the other runtime's stdout verbatim, do not synthesise your own answer. Use on "run in <ide>", "compare <ide> vs <ide>", "try on <model>", "which IDE handles X better", "run across models".
tools
Recommend which LLM model to use for a task. Use when asked "which model / best LLM for X", "pick a model for this task", or for a model shortlist ranked by live leaderboard evidence (coding, reasoning, agentic, tool-use, price, speed). Live-fetches public leaderboards and ranks models with per-axis rationale and citations.
development
Produce a comprehensive Product Requirements Document (PRD). Use when the user asks to write a PRD or formalize a feature's scope, goals, and success metrics.