A lightweight harness designed to be simple to use, efficient, universially compatible with any coding agent to evaluate them over a broad set of coding tasks and languages.
See the codeA lightweight evaluation harness for coding agents that runs high-signal, compact but challenging problems in isolated Docker containers. Evaluate agents across 26 tasks in 6 languages with weighted scoring, integrity verification, and detailed reporting.
Note: v1.8.x is now live, both in releases and the leaderboard. Use v1.6.1 with --legacy flag if you want to compare run data to the old legacy leaderboard.
--parallel.sanity-cache/ mountsgit clone https://github.com/lemon07r/sanityharness.git
cd sanityharness
make tools # Install dev tools (first-time only)
make build # Build the CLI
| Flag | Short | Description |
|---|---|---|
--config | Config file path (default: ./sanity.toml) | |
--tasks-dir | External tasks directory | |
--verbose | -v | Enable debug logging |
./sanity list # List all tasks
./sanity list --json # JSON output
./sanity list --language go # Filter by language
./sanity list --tier core # Filter by tier
./sanity list --difficulty hard # Filter by difficulty
./sanity init go/bank-account # Create workspace with stub files
./sanity init go/bank-account -o ./my-dir
./sanity run go/bank-account # Run tests once
./sanity run go/bank-account --watch # Re-run on file changes
./sanity run go/bank-account -w ./my-impl --timeout 60
./sanity eval --agent gemini # Evaluate against core tasks
./sanity eval --agent gemini --model gemini-3-pro # Specify model
./sanity eval --agent gemini --tier all --parallel 4 # All tasks, 4 concurrent
./sanity eval --agent gemini --dry-run # Preview without running
./sanity eval --agent droid --reasoning high # Set reasoning effort
./sanity eval --agent gemini --use-mcp-tools # Enable MCP tools
./sanity eval --agent opencode --use-skills # Enable Agent Skills mode
./sanity eval --agent opencode --disable-mcp # Disable MCP tools / currently only supported for opencode
./sanity eval --agent opencode --keep-workspaces # Keep workspaces for debugging
./sanity eval --agent gemini --no-sandbox # Disable bubblewrap sandbox
./sanity eval --agent gemini --legacy # Legacy mode (hidden tests visible to agent)
./sanity eval --resume ./eval-results/2026-01-07T120000-gemini # Resume interrupted eval
./sanity show sessions/go-bank-account-2026-01-15T143022-a1b2c3d4
./sanity show sessions/go-bank-account-2026-01-15T143022-a1b2c3d4 --json
./sanity verify ./eval-results/2026-01-07T120000-gemini
./sanity clean # Interactive cleanup
./sanity clean --all --force # Clean everything
./sanity version # Show version, commit, build date
Tasks can be referenced as:
<language>/<slug> (e.g., go/bank-account) - always unambiguousbank-account - works if unique across languages26 tasks across 6 languages with varying difficulty:
| Language | Tasks | Tiers | Difficulty |
|---|---|---|---|
| Go | 6 | 4 core, 2 extended | Hard - Expert |
| Rust | 6 | 4 core, 2 extended | Hard - Expert |
| TypeScript | 5 | 4 core, 1 extended | Hard |
| Kotlin | 3 | 3 extended | Hard |
| Dart | 3 | 3 extended | Hard |
| Zig | 3 | 3 extended | Hard - Expert |
See docs/TASKS.md for complete task listings and metadata.
Create sanity.toml in your project root (see sanity.toml.example):
[harness]
max_attempts = 10
default_timeout = 60
session_dir = "sessions"
[docker]
go_image = "ghcr.io/lemon07r/sanity-go:latest"
auto_pull = true
Config files are searched in order:
./sanity.toml~/.sanity.toml~/.config/sanity/config.tomlSee docs/CONFIGURATION.md for all options.
| Agent | Description |
|---|---|
gemini | Google Gemini CLI |
kilocode | Kilo Code CLI |
opencode | OpenCode CLI |
claude | Anthropic Claude Code |
codex | OpenAI Codex CLI |
kimi | Moonshot Kimi CLI |
crush | Crush CLI |
copilot | GitHub Copilot CLI |
droid | Factory Droid CLI |
iflow | iFlow CLI |
qwen | Qwen Code CLI |
amp | Sourcegraph Amp CLI (modes: smart, rush) |
codebuff | Codebuff CLI (modes: max, lite) |
vibe | Mistral Vibe CLI |
goose | Block Goose CLI |
junie | JetBrains Junie CLI |
ccs | Claude Code Switch (profiles: agy, glm, kimi, etc.) |
cline | Cline CLI |
pi | Pi CLI |
[agents.my-agent]
command = "/path/to/my-agent"
args = ["--auto-approve", "{prompt}"]
model_flag = "-m"
env = { API_KEY = "xxx" }
See docs/CONFIGURATION.md#agent-configuration for full schema.
Workspace isolation: During
sanity eval, each agent runs in an isolated temporary workspace under/tmprather than insideeval-results/. This prevents agents from reading other eval results, sibling task solutions, or their ownagent.log. After the agent finishes, files are copied back toeval-results/for validation. Combined with the bubblewrap sandbox (which uses--tmpfs /tmp), agents have zero visibility into other evaluations.
Sandbox note:
sanity evalruns agents inside a bubblewrap sandbox where$HOMEis read-only by default. A configurable allowlist is mounted read/write ([sandbox] shared_readwrite_dirs) and read-only ([sandbox] shared_readonly_dirs), with additional writable paths available via[sandbox] writable_dirs. Non-allowlisted top-level home directories are masked, and extra sensitive paths can be masked with[sandbox] readable_denylist. Use--no-sandboxto disable.
Legacy mode: Prior to v1.6.0, a bug caused hidden tests to be included in the workspace during
sanity eval, making them visible to agents. The--legacyflag reproduces this behavior so that older evaluation runs can be fairly compared or resumed. When--legacyis active, hidden test files are written to the workspace at init time (instead of being overlaid just before validation), and the hidden-test overlay step is skipped. Use this flag when resuming runs that were originally executed with the buggy behavior.
sleep infinity; commands execute via docker exec for fast reuse/workspace in the container.sanity-cache/ for faster buildsEach sanity run creates:
sessions/<session-id>/
├── result.json # Structured results
├── report.md # Markdown summary
├── logs/ # Per-attempt logs
└── workspace/ # Final code
Each sanity eval creates:
eval-results/<timestamp>-<agent>/
├── summary.json # Complete results with weighted scores
├── attestation.json # BLAKE3 hashes for verification
├── report.md # Human-readable report
├── submission.json # Leaderboard format
├── run-config.json # Config for resume capability
└── <task>/
├── agent.log # Agent output during task execution (includes HARNESS timeout footer)
├── validation.log # Test runner output + HARNESS validation footer (always non-empty)
├── integrity.json # Present on integrity violations; forensic metadata
├── integrity-files/ # Present on integrity violations; expected/actual file copies
└── integrity-diff/ # Present on integrity violations; per-file diffs
Resume interrupted evals: If interrupted (CTRL+C), the harness saves partial results and prints a resume command. Use ./sanity eval --resume <dir> to continue.
See docs/SCORING.md for scoring details and output schemas.
sanityharness/
├── cmd/sanity/ # CLI entry point
├── internal/
│ ├── cli/ # Cobra commands
│ ├── config/ # TOML configuration
│ ├── errors/ # Error summarization
│ ├── result/ # Session/attempt types
│ ├── runner/ # Docker execution
│ └── task/ # Task loading
├── tasks/ # Embedded task files
└── containers/ # Dockerfiles
See docs/DEVELOPMENT.md for architecture details.
For a full summary of all changes since v1.6.1 (the entire 1.7.x line and further), see docs/ROAD-TO-V2-Overhaul.md.
Current stable release: v1.8.10.
Contributions are welcome! Please see docs/CONTRIBUTING.md for guidelines.
Quick start:
make pre-commit # Run before committing
make test # Run tests
MIT License
125 commits
Go
74.6%
Kotlin
5.3%
Zig
5.1%
Rust
4.8%
Dart
3.9%
TypeScript
3.7%
Makefile
2.6%
A lightweight harness designed to be simple to use, efficient, universially compatible with any coding agent to evaluate them over a broad set of coding tasks and languages.
See the codeA lightweight evaluation harness for coding agents that runs high-signal, compact but challenging problems in isolated Docker containers. Evaluate agents across 26 tasks in 6 languages with weighted scoring, integrity verification, and detailed reporting.
Note: v1.8.x is now live, both in releases and the leaderboard. Use v1.6.1 with --legacy flag if you want to compare run data to the old legacy leaderboard.
--parallel.sanity-cache/ mountsgit clone https://github.com/lemon07r/sanityharness.git
cd sanityharness
make tools # Install dev tools (first-time only)
make build # Build the CLI
| Flag | Short | Description |
|---|---|---|
--config | Config file path (default: ./sanity.toml) | |
--tasks-dir | External tasks directory | |
--verbose | -v | Enable debug logging |
./sanity list # List all tasks
./sanity list --json # JSON output
./sanity list --language go # Filter by language
./sanity list --tier core # Filter by tier
./sanity list --difficulty hard # Filter by difficulty
./sanity init go/bank-account # Create workspace with stub files
./sanity init go/bank-account -o ./my-dir
./sanity run go/bank-account # Run tests once
./sanity run go/bank-account --watch # Re-run on file changes
./sanity run go/bank-account -w ./my-impl --timeout 60
./sanity eval --agent gemini # Evaluate against core tasks
./sanity eval --agent gemini --model gemini-3-pro # Specify model
./sanity eval --agent gemini --tier all --parallel 4 # All tasks, 4 concurrent
./sanity eval --agent gemini --dry-run # Preview without running
./sanity eval --agent droid --reasoning high # Set reasoning effort
./sanity eval --agent gemini --use-mcp-tools # Enable MCP tools
./sanity eval --agent opencode --use-skills # Enable Agent Skills mode
./sanity eval --agent opencode --disable-mcp # Disable MCP tools / currently only supported for opencode
./sanity eval --agent opencode --keep-workspaces # Keep workspaces for debugging
./sanity eval --agent gemini --no-sandbox # Disable bubblewrap sandbox
./sanity eval --agent gemini --legacy # Legacy mode (hidden tests visible to agent)
./sanity eval --resume ./eval-results/2026-01-07T120000-gemini # Resume interrupted eval
./sanity show sessions/go-bank-account-2026-01-15T143022-a1b2c3d4
./sanity show sessions/go-bank-account-2026-01-15T143022-a1b2c3d4 --json
./sanity verify ./eval-results/2026-01-07T120000-gemini
./sanity clean # Interactive cleanup
./sanity clean --all --force # Clean everything
./sanity version # Show version, commit, build date
Tasks can be referenced as:
<language>/<slug> (e.g., go/bank-account) - always unambiguousbank-account - works if unique across languages26 tasks across 6 languages with varying difficulty:
| Language | Tasks | Tiers | Difficulty |
|---|---|---|---|
| Go | 6 | 4 core, 2 extended | Hard - Expert |
| Rust | 6 | 4 core, 2 extended | Hard - Expert |
| TypeScript | 5 | 4 core, 1 extended | Hard |
| Kotlin | 3 | 3 extended | Hard |
| Dart | 3 | 3 extended | Hard |
| Zig | 3 | 3 extended | Hard - Expert |
See docs/TASKS.md for complete task listings and metadata.
Create sanity.toml in your project root (see sanity.toml.example):
[harness]
max_attempts = 10
default_timeout = 60
session_dir = "sessions"
[docker]
go_image = "ghcr.io/lemon07r/sanity-go:latest"
auto_pull = true
Config files are searched in order:
./sanity.toml~/.sanity.toml~/.config/sanity/config.tomlSee docs/CONFIGURATION.md for all options.
| Agent | Description |
|---|---|
gemini | Google Gemini CLI |
kilocode | Kilo Code CLI |
opencode | OpenCode CLI |
claude | Anthropic Claude Code |
codex | OpenAI Codex CLI |
kimi | Moonshot Kimi CLI |
crush | Crush CLI |
copilot | GitHub Copilot CLI |
droid | Factory Droid CLI |
iflow | iFlow CLI |
qwen | Qwen Code CLI |
amp | Sourcegraph Amp CLI (modes: smart, rush) |
codebuff | Codebuff CLI (modes: max, lite) |
vibe | Mistral Vibe CLI |
goose | Block Goose CLI |
junie | JetBrains Junie CLI |
ccs | Claude Code Switch (profiles: agy, glm, kimi, etc.) |
cline | Cline CLI |
pi | Pi CLI |
[agents.my-agent]
command = "/path/to/my-agent"
args = ["--auto-approve", "{prompt}"]
model_flag = "-m"
env = { API_KEY = "xxx" }
See docs/CONFIGURATION.md#agent-configuration for full schema.
Workspace isolation: During
sanity eval, each agent runs in an isolated temporary workspace under/tmprather than insideeval-results/. This prevents agents from reading other eval results, sibling task solutions, or their ownagent.log. After the agent finishes, files are copied back toeval-results/for validation. Combined with the bubblewrap sandbox (which uses--tmpfs /tmp), agents have zero visibility into other evaluations.
Sandbox note:
sanity evalruns agents inside a bubblewrap sandbox where$HOMEis read-only by default. A configurable allowlist is mounted read/write ([sandbox] shared_readwrite_dirs) and read-only ([sandbox] shared_readonly_dirs), with additional writable paths available via[sandbox] writable_dirs. Non-allowlisted top-level home directories are masked, and extra sensitive paths can be masked with[sandbox] readable_denylist. Use--no-sandboxto disable.
Legacy mode: Prior to v1.6.0, a bug caused hidden tests to be included in the workspace during
sanity eval, making them visible to agents. The--legacyflag reproduces this behavior so that older evaluation runs can be fairly compared or resumed. When--legacyis active, hidden test files are written to the workspace at init time (instead of being overlaid just before validation), and the hidden-test overlay step is skipped. Use this flag when resuming runs that were originally executed with the buggy behavior.
sleep infinity; commands execute via docker exec for fast reuse/workspace in the container.sanity-cache/ for faster buildsEach sanity run creates:
sessions/<session-id>/
├── result.json # Structured results
├── report.md # Markdown summary
├── logs/ # Per-attempt logs
└── workspace/ # Final code
Each sanity eval creates:
eval-results/<timestamp>-<agent>/
├── summary.json # Complete results with weighted scores
├── attestation.json # BLAKE3 hashes for verification
├── report.md # Human-readable report
├── submission.json # Leaderboard format
├── run-config.json # Config for resume capability
└── <task>/
├── agent.log # Agent output during task execution (includes HARNESS timeout footer)
├── validation.log # Test runner output + HARNESS validation footer (always non-empty)
├── integrity.json # Present on integrity violations; forensic metadata
├── integrity-files/ # Present on integrity violations; expected/actual file copies
└── integrity-diff/ # Present on integrity violations; per-file diffs
Resume interrupted evals: If interrupted (CTRL+C), the harness saves partial results and prints a resume command. Use ./sanity eval --resume <dir> to continue.
See docs/SCORING.md for scoring details and output schemas.
sanityharness/
├── cmd/sanity/ # CLI entry point
├── internal/
│ ├── cli/ # Cobra commands
│ ├── config/ # TOML configuration
│ ├── errors/ # Error summarization
│ ├── result/ # Session/attempt types
│ ├── runner/ # Docker execution
│ └── task/ # Task loading
├── tasks/ # Embedded task files
└── containers/ # Dockerfiles
See docs/DEVELOPMENT.md for architecture details.
For a full summary of all changes since v1.6.1 (the entire 1.7.x line and further), see docs/ROAD-TO-V2-Overhaul.md.
Current stable release: v1.8.10.
Contributions are welcome! Please see docs/CONTRIBUTING.md for guidelines.
Quick start:
make pre-commit # Run before committing
make test # Run tests
MIT License
125 commits
Go
74.6%
Kotlin
5.3%
Zig
5.1%
Rust
4.8%
Dart
3.9%
TypeScript
3.7%
Makefile
2.6%