npx skills add ...
npx skills add langchain-ai/deepagentsjs --skill eval-writer
Create new eval suites for the deepagentsjs monorepo. Handles dataset design, test case scaffolding, scoring logic, vitest configuration, and LangSmith integration. Use when the user asks to: (1) create an eval, (2) write an evaluation, (3) add a benchmark, (4) build an eval suite, (5) evaluate agent behaviour, (6) add test cases for a capability, or (7) implement an existing benchmark (e.g. oolong, AgentBench, SWE-bench). Trigger on phrases like 'create eval', 'new eval', 'add eval', 'benchmark', 'evaluate', 'eval suite', 'write evals for'.
npx skills add langchain-ai/deepagentsjs --skill eval-writer
Create new eval suites for the deepagentsjs monorepo. Each eval is an
independent workspace package under evals/ that uses the @deepagents/evals
harness, runs via vitest, and reports results to LangSmith.
Read the existing eval infrastructure to understand current patterns:
Scan existing evals for conventions:
Clarify with the user:
Every eval is a workspace package under evals/<name>/.
Add extra dependencies as needed (e.g. zod for tool schemas, langchain
for tool() helper, dataset-specific packages).
Adjust testTimeout for long-running evals (multi-turn, code execution).
Add "./vitest.setup.ts" to setupFiles if the eval needs custom setup (dataset loading, etc.).
Check that pnpm-workspace.yaml includes "evals/*". It should already
be there — if not, add it.
Best for small, hand-crafted test suites. Each test is an ls.test() call
with inputs and optional referenceOutputs.
Best for 10-200 cases from a fixture file. Load the data and iterate:
The fixture JSON must be an array of objects with at minimum { inputs: {...} }.
Optional fields: referenceOutputs, id, metadata, split.
For published benchmarks (oolong, AgentBench, SWE-bench, etc.), download and cache the dataset in a setup file.
Create vitest.setup.ts:
Add .cache/ to .gitignore in the eval package.
Then register it as a vitest setup file in vitest.config.ts:
And in the test file:
Pull test cases from a LangSmith dataset. Useful for collaborative curation where non-engineers add examples via the LangSmith UI.
The harness provides vitest matchers that also log LangSmith feedback scores. Use these as the primary building blocks:
Log additional LangSmith feedback scores beyond what matchers provide:
For subjective quality, use ls.wrapEvaluator() to create a traced evaluator
that logs feedback automatically:
getDefaultRunner() — reads EVAL_RUNNER env var. Throws if not set.runner.name — used as ls.describe name → becomes the LangSmith dataset name.runner.run({ query, initialFiles? }) — pure invocation. Returns AgentTrajectory.runner.extend({ systemPrompt?, tools?, subagents?, ... }) — returns a new runner with agent config overrides. Use for tests that need custom agent setup.projectName in ls.describe config — sets the LangSmith project for tracing. Convention: "deepagents-js-<eval-name>".upsert: true — reuse existing dataset/project instead of creating new ones each run.expect from vitest — the harness extends it with custom matchers at import time.ls.logOutputs() is called inside the runner — do NOT call it in test code.The default eval runners use the in-memory StateBackend — the agent can
read/write files but cannot execute shell commands, install packages, or
interact with a real OS. This is fine for testing tool selection, reasoning,
and file operations.
For evals that need real execution (SWE-bench, code generation, agentic benchmarks), the agent must run against a sandbox backend. Available sandbox providers:
| Provider | Package | Use case |
|---|---|---|
| Modal | @deepagents/modal | Remote containers, GPU support |
| Daytona | @deepagents/daytona | Cloud dev environments |
| Deno | @deepagents/deno | Lightweight local sandboxes |
| Node VFS | @deepagents/node-vfs | In-process virtual filesystem + shell |
Pass the sandbox via extend({ backend }). Manage its lifecycle with
beforeAll / afterAll (suite-level) or beforeEach / afterEach
(per-test isolation):
For per-test isolation (each test gets a fresh sandbox):
When to containerize:
execute() (shell commands)When in-memory is fine:
StateBackend + initialFiles)Add the sandbox provider to package.json dependencies:
And increase testTimeout in vitest.config.ts — sandbox creation adds
overhead:
Add the new eval to evals/README.md in the "Available eval suites" table:
The Python deepagents package has eval suites in
libs/deepagents/tests/evals/. The JS evals should maintain parity.
When creating a new eval, check the Python source at
https://github.com/langchain-ai/deepagents/blob/v0.5/libs/deepagents/tests/evals/
for the reference implementation.
| Python eval | JS eval | Status |
|---|---|---|
test_system_prompt.py | evals/basic/ | ✅ Covered |
test_file_operations.py | evals/files/ | ✅ Covered |
test_subagents.py | evals/subagents/ | ✅ Covered |
test_memory.py | evals/memory/ | ✅ Covered |
test_hitl.py | evals/hitl/ | ✅ Covered |
test_skills.py | evals/skills/ | ✅ Covered |
test_summarization.py | evals/summarization/ | ❌ Missing |
HITL evals require multi-step invocation (invoke → check interrupts → resume
with Command). The eval runner's run() does a single invocation, so HITL
tests construct agents directly via createDeepAgent() with a checkpointer
and interruptOn config. See evals/hitl/index.test.ts for the pattern.
Summarization evals need SummarizationMiddleware with low token thresholds,
a checkpointer, a real/virtual filesystem backend, and multi-turn invocations.
These tests would bypass the standard EvalRunner and construct agents
directly, similar to HITL.
ls.test.each is the most powerful pattern for data-driven evals. The table
must be an array of objects with at least { inputs }:
| Concept | LangSmith entity |
|---|---|
ls.describe(name, ...) | Dataset (name = dataset name) |
ls.test(name, { inputs, referenceOutputs }, fn) | Example in dataset |
| Running the test suite | Experiment on the dataset |
ls.logFeedback(...) | Feedback on the experiment run |
ls.logOutputs(...) | Experiment output (called by runner) |
| Variable | Purpose |
|---|---|
EVAL_RUNNER | Which model runner to use (e.g. sonnet-4-5) |
LANGSMITH_API_KEY | LangSmith auth |
LANGSMITH_PROJECT | Override tracing project (normally set via projectName) |
LANGSMITH_TEST_TRACKING | Set to "false" to disable LangSmith reporting |
ANTHROPIC_API_KEY | For Anthropic model runners |
OPENAI_API_KEY | For OpenAI model runners |
Defined in internal/eval-harness/src/setup.ts:
| Runner name | Model |
|---|---|
sonnet-4-5 | Claude Sonnet 4.5 |
sonnet-4-5-thinking | Claude Sonnet 4.5 with extended thinking |
opus-4-6 | Claude Opus 4.6 |
gpt-4.1 | GPT-4.1 |
gpt-4.1-mini | GPT-4.1 Mini |
o3-mini | o3-mini |
When implementing an existing benchmark, follow these attribution and methodology guidelines.
Always credit the original benchmark authors. In the eval's README.md:
ls.test.each with fixture JSONrunner.run() calls sharing state, or multi-message queriesrunner.extend({ tools: [...] }) with custom tools that return canned responsesrunner.extend({ backend: sandbox }), write code to file, execute tests via sandboxinitialFiles, check final textStateBackend is sufficientrunner.extend({ backend: sandbox }) with per-test sandbox isolationFor benchmarks with thousands of cases:
split field in test cases to categorise (e.g. "easy", "hard"). Run subsets via vitest filtering..cache/ (gitignored).--dry-run that validates fixtures without calling the LLM.