Evaluate the tools you give to AI agents. Real agents (claude-code, codex, opencode) run verifiable tasks through your MCP server, CLI, or skill, and you get side-by-side numbers across harnesses and models: success rate, token cost, tool-call behavior.
Doubles as a dev loop when you're building the tools themselves: every trial saves the full trajectory, so you can diagnose failures and iterate.
Features:
- 📊 Side-by-side comparison of tool access: MCP / CLI / skill / mcpc
- 🤖 Multi-harness: claude-code, codex, opencode
- 🧠 Multi-model via OpenRouter or direct-to-provider
- 🔗 Cross-app tasks: one instruction can touch multiple apps at once (apify + github + ...)
- 📜 Full trajectory traces, verifier scores, and per-trial token/cost breakdowns
- 📈 Project dashboard with connector-comparison plots
- ☁️ Cloud sandboxes (E2B, Daytona, ...) or local Docker
- 🧩 Extensible: add apps or tasks by dropping a directory, no code changes
- 🐚 Built on Harbor
Harbor primitives (full reference: https://www.harborframework.com/docs/core-concepts):
- task - one test case: an instruction plus a verifier that scores the result.
- dataset - a collection of tasks.
- agent - the CLI harness that completes the task (claude-code, codex, opencode, ...).
- environment - the container the agent runs in (docker, e2b, daytona, ...).
- trial - one (task, agent, model) execution; produces a reward.
- job - a collection of trials, run in parallel.
connector-evals adds three project-specific pieces on top:
- app - third-party service the agent talks to (
apify,github,linear,notion, ...). - connector - how the agent reaches it:
mcp,cli,mcpc, orcli+skill. One connector per run by default;app_connectors:for hybrid. - cell - one (app, connector) pair on disk:
apps/<app>/<connector>/{cell.yaml, instruction.md, [setup.sh], [teardown.sh], [skills/]}.
Workflow:
- Create a cell for each
(app, connector)pair you want to compare underapps/<app>/<connector>/. - Define tasks in
tasks/: an instruction the agent should accomplish plus a verifier. Each task declares which apps it needs via[connector_evals].apps = [...]in itstask.toml; one task can use several apps at once (e.g. apify + github). connector-evals runlaunches the agents in sandboxes on every task with the chosen connector(s) wired up, and runs the verifiers.- Results and full execution traces land under
jobs/. Inspect with the results browser, the dashboard, or by pointing a coding agent at them.
Deep reference (cell file layout, cell.yaml fields, MCP-proxy wrapper template,
verifier env contract, step-by-step for wiring a new app):
docs/mcp-task-pattern.md.
uv tool install git+https://github.com/apify-projects/connector-evalsFrom a local fork/checkout instead (changes in the checkout apply immediately, no reinstall):
uv tool install --editable /path/to/connector-evalsTo use it as a library (from connector_evals import ...) add it as a project
dependency instead: uv add git+https://github.com/apify-projects/connector-evals, then
invoke the CLI via uv run connector-evals.
- OpenRouter account - single key for all LLM providers. https://openrouter.ai/keys
- E2B account (default sandbox) - https://e2b.dev
- Daytona account (alternative cloud sandbox,
--env daytona) - https://app.daytona.io - Docker (alternative local sandbox,
--env docker) - OrbStack works.
Put keys in a .env in the directory you run from; connector-evals run auto-loads
it (--env-file overrides). At minimum OPENROUTER_API_KEY plus the sandbox
key (E2B_API_KEY by default).
The CLI reads eval definitions relative to the directory you run it from:
apps/<app>/<connector>/-cell.yaml(MCP servers, env, setup_env) plus optionalinstruction.md,setup.sh,teardown.sh,skills/. Examples in this repo'sapps/.tasks/<name>/- Harbor task dirs (task.toml, instruction, verifier).task.tomldeclares which apps the task needs via[connector_evals].apps.images/base/Dockerfile- one shared sandbox image with every app tool installed; copied unchanged into each task'senvironment/at run time.
connector-evals run [-c CONFIG] [--connector CONNECTOR] [--app NAME]...
[--apps-dir PATH] [-a AGENT] [-m MODEL]
[-t TASK]... [-p DATASET_PATH]
[--task-name GLOB]... [--exclude-task-name GLOB]...
[--job-name NAME] [-o JOBS_DIR] [-k N_ATTEMPTS] [-n N_CONCURRENT]
[--env BACKEND] [--env-file PATH] [-y]
Every flag overrides the corresponding config field; connector-evals run --help for
details. Defaults: E2B sandbox (--env docker / --env daytona to switch),
./apps, ./jobs.
One task, one agent (apps auto-resolved from task.toml):
connector-evals run --connector mcp -t tasks/my-task \
-a claude-code -m anthropic/claude-haiku-4.5 -yA multi-app task (one task, two apps wired up at once):
connector-evals run --connector mcp -t tasks/cross-actor-meta-and-repo-meta -a oracle -yA whole task dataset with name filtering, oracle agent (replays the reference solution, no LLM):
connector-evals run --connector mcp -a oracle \
--dataset-path tasks --task-name 'apify-*' -yFrom a config (see configs/ in this repo for the schema):
connector-evals run -c configs/my-eval.yaml -yEval definitions somewhere else than the cwd (e.g. inside an app repo):
connector-evals run --apps-dir evals/apps -t evals/tasks/my-task \
-o evals/jobs --connector mcp -a claude-code -m ... -y--apps-diris also a config field (apps_dir);-o/--jobs-dirmatchesharbor run.- Paths in a config file resolve relative to the cwd, not the config location.
- Explicit
skills:entries incell.yamlresolve relative to that yaml's directory and support globs (skills: ["../../src/lib/skills/*"]). Each match must be a directory with aSKILL.md; a pattern matching nothing is an error. The siblingskills/subdir is still auto-discovered and de-duplicated against explicit entries. - Every task's
environment/is materialized at run time by copyingimages/base/into it (replacing whatever is there). Harbor requiresenvironment/inside the task dir, so this cannot be redirected; gitignoretasks/*/environment/, as this repo does.
Project dashboard (MCP vs CLI vs skill comparisons and other project-specific plots):
connector-evals dashboard [JOBS_DIR] # defaults to ./jobsconnector-evals dashboard --help for flags. See dashboard/README.md for
first-time setup (dedicated streamlit venv).
Harbor's generic browsers are also available for per-trial inspection:
harbor view jobs # trial browser
harbor view tasks # task browser- Codex trajectories carry no per-step token metrics (harbor converter gap), so the
prompt_baseline_tokensmetric and the dashboard's token timeline are unavailable for codex trials; per-trial totals are unaffected. Seedocs/harbor-constraints.mdanddocs/todo.md. - Opencode truncates large tool outputs in the trajectory (the observation records a
"Full output saved to: ..." stub instead of the full content), so
connector_output_charsundercounts for verbose calls; cli/mcpc variants benefit most from this. Token totals are unaffected (reported by the API, not derived from content). Seedocs/todo.md. - First-time e2b template builds for the same task can race and cross-cancel
(
BuildException/SandboxException 404). Pre-warm with one of theconfigs/*-prewarm.yamlconfigs before parallel sweeps. E2B itself accepts heavy concurrency once templates are warm: a 17-trial sweep with-n 17ran with zero sandbox / quota / 429 errors, so set-nto whatever your dataset size and upstream API rate limits allow. - If a run aborts with
FileExistsErroronjobs/<job-name>/, the previous run left the dir behind:rm -rf jobs/<job-name>and rerun. - Apify MCP sweeps need
-n ≤ 3. Higher concurrency times out the stdio probe during MCP server startup. Applies to--connector mcpruns touching theapifyapp; other connectors and other apps are unaffected. - OpenRouter models (
-m openrouter/...) must pin a provider preset or prompt caching drops to ~0 across turns, since OpenRouter re-routes providers per request. Use@preset/<provider>-only, e.g.openrouter/anthropic/claude-haiku-4.5@preset/anthropic-provider-only. See AGENTS.md § Models.
GitHub tasks (tasks/github-*) are ported from bench-github in
kunchenguid/axi.
Project goals, architecture, and conventions: see AGENTS.md and docs/.
Requires Python 3.13+.
uv sync # creates .venv, installs harbor[e2b] + connector-evals
cp .env.example .env # then set OPENROUTER_API_KEY, E2B_API_KEY, ...
uv run pytest # unit testsSmoke tests against the bundled evals, zero-cost (no LLM, just verifies the Harbor + sandbox loop):
uv run connector-evals run --connector mcp -t tasks/apify-fetch-actor-id -a oracle -yReal LLM via OpenRouter (cents):
uv run connector-evals run --connector mcp -t tasks/apify-fetch-actor-id -a claude-code -m anthropic/claude-haiku-4.5 -yharbor is also usable standalone (pipx install harbor; add
pipx inject harbor daytona for the --env daytona path). For raw
harbor run, .env is not auto-loaded:
set -a; source .env; set +a
# or use direnv: echo 'dotenv' > .envrc && direnv allow