feat: add Pi session support (v2 subagent dirs + v3 native format)#155
feat: add Pi session support (v2 subagent dirs + v3 native format)#155adamjramirez wants to merge 6 commits intoobsessiondb:mainfrom
Conversation
Support pi (https://github.com/mariozechner/pi-coding-agent) sessions that were previously invisible to Rudel. Handles two pi storage formats: Pi v2 (~282 sessions): subagent-only UUID dirs under ~/.claude/projects/ Pi v3 (~846 sessions): native format under ~/.pi/agent/sessions/ Approach: - New PiAdapter in packages/agent-adapters/src/adapters/pi/ - Registered via registerScanOnlyAdapter() to share source 'claude_code' without colliding in the adapter registry - v3 content transformed to Claude Code JSONL format so the ClickHouse materialized view can extract analytics (timestamps, tokens, model, etc.) - Session resolver updated to find pi sessions by UUID or path - Upload command routes to Pi adapter via isPiSession() detection - Enable command gracefully handles Pi's no-op hooks Closes obsessiondb#153
|
Thanks a lot for this. I would love to enable you to also use sessions from Pi in Rudel. Let me know if you need extra help, or which type of problems you run into during building. The docs and system are still a little bit rough around the edges. |
|
Got it. I think that's a valid approach and I see where you're going with it. Let me work on it in the background and I'll push the changes up when finished. I'll reach out if anything pops up. |
- Add 'pi' to SourceSchema (own source key, not sharing claude_code) - Create rudel.pi_sessions table with version column (UInt8) - Add pi_v2_session_analytics_mv: parses Claude Code format subagent JSONL - Add pi_v3_session_analytics_mv: parses native Pi v3 JSONL format - Both MVs write to shared session_analytics with source='pi' - Pi adapter stores raw content (no transform at ingest time) - Remove registerScanOnlyAdapter/transformV3Content (no longer needed) - Add optional version field to IngestSessionInput schema - Simplify CLI: getAdapter(source) works directly for Pi now Per PR obsessiondb#155 feedback: embrace the Pi format, store raw, parse in MVs.
|
What changed:
Migration: Tests: 23 pass, 70 assertions — covers v2/v3 raw storage, timestamp extraction, adapter isolation from Claude Code, error propagation, session discovery. Let me know if there's anything else to adjust/ |
- Fix version=0 in error path → undefined (avoids Zod min(1) rejection) - Remove Claude Code error patterns from v2 MV (isApiErrorMessage/is_error don't apply to Pi) — use toUInt32(0) like v3 MV - Add test for error path (nonexistent transcript → empty content, no version) - Extract resolveAdapterFromPath() to deduplicate upload.ts adapter resolution - Export extractV3SessionId from adapter, remove duplicate in session-resolver - Regenerate migration as single clean file (4 safe ops)
Match existing adapter pattern (Codex, Claude Code) — errors bubble up to upload command's retry logic instead of being swallowed. - Remove try/catch from PiAdapter.buildUploadRequest - version is now always set (number, not number|undefined) - Update test: assert rejects.toThrow() instead of empty content
Summary
Adds support for pi sessions that were previously invisible to Rudel. Closes #153.
Before: 60 sessions visible (Claude Code only)
After: 1,188 sessions visible (Claude Code + Pi v2 + Pi v3)
Two Pi storage formats
~/.claude/projects/<project>/<uuid>/subagents/agent-*.jsonl~/.pi/agent/sessions/--<encoded-cwd>--/<timestamp>_<uuid>.jsonlApproach
New
PiAdapterfollowing the existing adapter pattern (like Codex):registerScanOnlyAdapter()— sharessource: "claude_code"without colliding in the adapter registry. NoSourceSchemachanges needed.contentfield, populatessubagentsmapv3 content transformation
The MV parses
contentline-by-line expecting Claude Code format (type: "user"/"assistant"at line level,message.usage.input_tokens, etc.). Pi v3 uses different structures, so the adapter transforms on upload:{type: "message", message: {role: "user"}}→{type: "user", ...}input→input_tokens,output→output_tokens,cacheRead→cache_read_input_tokens,cacheWrite→cache_creation_input_tokensuuidandsessionIdso the web UI conversation parser worksapi,provider,stopReason,cost,totalTokens) to reduce content sizesession,compaction,thinking_level_change,toolResult)Changes
packages/agent-adapters/src/adapters/pi/index.tspackages/agent-adapters/src/registry.tsregisterScanOnlyAdapter()for shared-source adapterspackages/agent-adapters/src/index.tsapps/cli/src/lib/session-resolver.tsapps/cli/src/commands/upload.tsapps/cli/src/commands/dev/list-sessions.ts[Pi]label for pi sessionsapps/cli/src/commands/enable.tsapps/cli/src/__tests__/agents.test.tsZero API/schema/migration changes.
Tested
bun run check-types✅bun run lint✅bun test— 24 pass, 0 fail ✅Follow-up suggestion
Currently pi sessions show as
source: "claude_code"since we reuse the existing source to avoid schema changes. A natural follow-up would be adding"pi"toSourceSchemaso the UI can distinguish which harness produced a session.