- 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
173 lines
5.5 KiB
Markdown
173 lines
5.5 KiB
Markdown
# Session Storage Fix - Simplified Approach
|
|
|
|
## Problem Summary
|
|
|
|
The evaluation framework couldn't find sessions created by the SDK because:
|
|
|
|
1. **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/`
|
|
2. **Hash Calculation**: We couldn't reliably calculate the project hash that OpenCode uses
|
|
3. **Project Path Confusion**: Tests run from `/evals/framework` but 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)
|
|
```typescript
|
|
// 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)
|
|
```typescript
|
|
// 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 ✅
|
|
1. **config.ts**: Removed complex path calculations, kept only simple helpers
|
|
2. **session-reader.ts**: Now SDK-first, simple disk scan fallback
|
|
3. **test-runner.ts**: Passes SDK client to evaluators, no waiting
|
|
4. **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:
|
|
|
|
1. **No project path dependency**: We don't care where the agent runs
|
|
2. **Session ID is universal**: Every session has a unique ID
|
|
3. **SDK knows everything**: The SDK tracks all sessions regardless of project
|
|
4. **Disk scan is comprehensive**: Scans all hash directories
|
|
|
|
### Example: Testing Multiple Agents
|
|
```typescript
|
|
// 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
|
|
|
|
1. **Use the SDK**: Don't reverse-engineer storage formats
|
|
2. **Keep it simple**: Scan by ID when SDK unavailable
|
|
3. **Async all the way**: SDK calls are async, embrace it
|
|
4. **Agent-agnostic**: Design for testing any agent, not just one
|
|
|
|
## Files Changed
|
|
|
|
- `src/collector/session-reader.ts` - Simplified to SDK-first approach
|
|
- `src/collector/timeline-builder.ts` - Made async for SDK calls
|
|
- `src/evaluators/evaluator-runner.ts` - Added SDK client support, made async
|
|
- `src/sdk/test-runner.ts` - Passes SDK client to evaluators
|
|
- `src/config.ts` - Removed complex path logic, added git root helper
|
|
|
|
## Migration Notes
|
|
|
|
If you have existing code using SessionReader:
|
|
|
|
```typescript
|
|
// 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.
|