Agent Test - Implementation Notes
Overview
tdx agent test provides automated testing of LLM agents using YAML test definitions. Tests are run against agents, and responses are evaluated by a judge agent (Claude Sonnet 4.5).
Architecture
┌─────────────────────────────────────────────────────────────────┐
│ tdx agent test │
├─────────────────────────────────────────────────────────────────┤
│ 1. Parse test.yml │
│ 2. Resolve agent (project/agent from folder structure) │
│ 3. Setup evaluator agent (create/update in tdx_default) │
│ 4. Run all test rounds (send messages, wait for completion) │
│ 5. Fetch chat history once after all rounds complete │
│ 6. Extract responses for each round from history │
│ 7. Evaluate each round against criteria using judge agent │
│ 8. Output results (JSON/JSONL/table/TSV to stdout or file) │
└─────────────────────────────────────────────────────────────────┘Key Components
Test Execution (src/sdk/agent/test/executor.ts)
Execution flow:
- Run rounds sequentially - send user input, consume stream to wait for completion
- After ALL rounds complete, fetch chat history once via
llm.history(chatId) - Parse history to extract responses for each round
History structure:
json
{
"data": [
{ "input": "user message 1", "at": "..." },
{ "content": "assistant response", "at": "..." },
{ "tool": { "functionName": "...", "content": "..." }, "at": "..." },
{ "content": "more response", "at": "..." },
{ "input": "user message 2", "at": "..." },
{ "content": "response to message 2", "at": "..." }
]
}Each input event marks a new round. Everything between one input and the next belongs to that round's response.
Evaluation (src/sdk/agent/test/evaluator.ts)
Evaluator agent:
- Auto-created in user's default project (
tdx_default_<username>) - Model: Claude Sonnet 4.5
- Version tracked via SHA-256 hash of config (auto-updates when prompt changes)
Evaluation process:
- For each round, pass conversation history + criteria to evaluator
- Evaluator returns:
{ pass: boolean, reason: string, plan: string, thinking: string } - Test fails if any round fails evaluation
Output (src/sdk/agent/test/output.ts)
Formats results as JSON, JSONL, TSV, or table for stdout or file output.
Test File Format
Two formats are supported:
Flat Format (Recommended for Single-Round Tests)
yaml
tests:
- name: greeting_test
user_input: What's the weather today?
criteria: Should respond with weather information
- name: calculation_test
user_input: What is 2 + 2?
criteria: Should respond with 4Rounds Format (For Multi-Round Tests)
yaml
tests:
- name: multi_turn_conversation
rounds:
- user_input: |
First message to agent
criteria: |
Agent must do X and Y
- user_input: |
Follow-up message
criteria: |
Agent must remember context and do ZMixed Format
You can mix both formats in the same file:
yaml
tests:
# Simple single-round test (flat format)
- name: simple_query
user_input: Hello
criteria: Should greet back
# Complex multi-round test (rounds format)
- name: context_memory
rounds:
- user_input: My name is Alice
criteria: Should acknowledge the name
- user_input: What's my name?
criteria: Should remember and respond with AliceRe-evaluation (--reeval)
The --reeval flag allows re-running evaluation against cached conversations without regenerating them. This speeds up criteria iteration.
Cache location: .cache/tdx/last_agent_test_run.json
Behavior:
- Cached tests are re-evaluated using updated criteria from test.yml
- New tests (not in cache) are executed normally if on the same site
Key Design Decisions
- Binary results (PASS/FAIL) - More reliable than scored evaluations
- Strong judge model - Sonnet 4.5 provides consistent, high-quality evaluation
- History from API - Authoritative source, includes all events
- Per-round criteria - Each round evaluated independently for precise feedback
- Cached re-evaluation - Speeds up criteria iteration without re-running conversations