OpenWriterControle/evals/framework/SESSION_STORAGE_FIX.md
darrenhinde 8eb4b31ef4 feat(evals): add opencoder test suite and fix expected violation handling
- 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
2025-11-26 01:52:18 +00:00

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.