- Create separate test folder for opencoder agent with 4 tests - Update openagent tests to use multi-turn prompts for approval workflow - Fix test runner to properly handle expectedViolations with shouldViolate: true - Add mustUseAnyOf behavior expectation for flexible tool matching - Add unit tests for timeline-builder and context-loading-evaluator - Update documentation to reflect agent-specific test structure - Remove outdated AGENT_DETECTION_STATUS.md Opencoder tests: 4/4 passing Key insight: openagent uses text-based approval, opencoder uses tool permissions only
5.5 KiB
5.5 KiB
Session Storage Fix - Simplified Approach
Problem Summary
The evaluation framework couldn't find sessions created by the SDK because:
- Path Mismatch: SDK stores sessions in
~/.local/share/opencode/storage/session/{hash}/but evaluators looked in~/.local/share/opencode/project/{encoded-path}/storage/session/info/ - Hash Calculation: We couldn't reliably calculate the project hash that OpenCode uses
- Project Path Confusion: Tests run from
/evals/frameworkbut sessions created in/opencode-agents(git root)
Solution: SDK-First with Disk Fallback
Instead of reverse-engineering OpenCode's storage format, we now:
1. Use SDK Client Directly (Primary Method)
// SessionReader now accepts SDK client
const sessionReader = new SessionReader(sdkClient, sessionStoragePath);
// Get session via SDK (always up-to-date, no disk delays)
const session = await sessionReader.getSessionInfo(sessionId);
Benefits:
- ✅ No path calculations needed
- ✅ No hash discovery required
- ✅ No waiting for disk writes
- ✅ Always gets latest data
- ✅ Works for any agent, any project
2. Simple Disk Scan (Fallback)
// If SDK unavailable, scan all session directories for the session ID
private findSessionFile(sessionId: string): string | null {
const sessionBasePath = '~/.local/share/opencode/storage/session';
// Scan all hash directories
for (const hashDir of fs.readdirSync(sessionBasePath)) {
const sessionFile = path.join(sessionBasePath, hashDir, `${sessionId}.json`);
if (fs.existsSync(sessionFile)) {
return sessionFile;
}
}
return null;
}
Benefits:
- ✅ Simple: Just find file by ID
- ✅ No project path matching
- ✅ Works for any agent
- ✅ Resilient fallback
What Was Removed
Complex Logic Eliminated ❌
Hash calculation (unreliable)Git root detection (unnecessary)Project path encoding (fragile)Multiple fallback paths (confusing)Session data polling (slow)Project hash caching (complex)
Files Simplified ✅
- config.ts: Removed complex path calculations, kept only simple helpers
- session-reader.ts: Now SDK-first, simple disk scan fallback
- test-runner.ts: Passes SDK client to evaluators, no waiting
- evaluator-runner.ts: Made async to support SDK calls
Architecture
┌─────────────────┐
│ Test Runner │
│ │
│ 1. Creates │──────┐
│ session │ │
│ │ │
│ 2. Gets │ │
│ sessionId │ │
│ │ │
│ 3. Passes SDK │ │
│ client to │ │
│ evaluators │ │
└────────┬────────┘ │
│ │
▼ │
┌─────────────────┐ │
│ Evaluators │ │
│ │ │
│ SessionReader │◄─────┘ SDK Client
│ (SDK-based) │
│ │
│ 1. Try SDK │──────► session.get(id)
│ first │ ✅ Fast, reliable
│ │
│ 2. Fallback │──────► Scan disk by ID
│ to disk │ ✅ Simple, works
└─────────────────┘
Testing Different Agents
This approach works for any agent because:
- No project path dependency: We don't care where the agent runs
- Session ID is universal: Every session has a unique ID
- SDK knows everything: The SDK tracks all sessions regardless of project
- Disk scan is comprehensive: Scans all hash directories
Example: Testing Multiple Agents
// Test OpenAgent
const openAgentTests = await loadTestCases('agents/openagent/tests/**/*.yaml');
await runner.runTests(openAgentTests);
// Test OpenCoder
const openCoderTests = await loadTestCases('agents/opencoder/tests/**/*.yaml');
await runner.runTests(openCoderTests);
// Works for both! No configuration needed.
Results
Before Fix ❌
Test FAILED
Errors: Evaluator error: Session not found: ses_xxx
Events captured: 4
Violations: N/A (evaluators couldn't run)
After Fix ✅
Test PASSED
Duration: 5063ms
Events: 4
Violations: 0 (0 errors, 0 warnings)
Evaluators: ✅ All ran successfully
Key Takeaways
- Use the SDK: Don't reverse-engineer storage formats
- Keep it simple: Scan by ID when SDK unavailable
- Async all the way: SDK calls are async, embrace it
- Agent-agnostic: Design for testing any agent, not just one
Files Changed
src/collector/session-reader.ts- Simplified to SDK-first approachsrc/collector/timeline-builder.ts- Made async for SDK callssrc/evaluators/evaluator-runner.ts- Added SDK client support, made asyncsrc/sdk/test-runner.ts- Passes SDK client to evaluatorssrc/config.ts- Removed complex path logic, added git root helper
Migration Notes
If you have existing code using SessionReader:
// Old (synchronous, disk-based)
const reader = new SessionReader(projectPath, sessionStoragePath);
const session = reader.getSessionInfo(sessionId);
// New (async, SDK-first)
const reader = new SessionReader(sdkClient, sessionStoragePath);
const session = await reader.getSessionInfo(sessionId);
All SessionReader methods are now async. Update your code accordingly.