forked from code-yeongyu/oh-my-opencode
-
Notifications
You must be signed in to change notification settings - Fork 0
Closed
Description
Context
Parent Issue: #5 (Distributed agent cluster)
Depends On: #6 (Resource monitoring)
Priority: HIGH (foundational for cluster)
Problem
Current UsageTracker persists to local JSON file (oh-my-opencode-usage.json) per machine. In a cluster, each node has its own usage data with no aggregation.
Goal
Centralize usage ingestion and storage so all nodes report to a single source of truth.
Implementation Plan
Phase 1: Pluggable Storage Interface
Create adapter pattern for UsageTracker:
interface StorageAdapter {
init?(config: unknown): Promise<void>
saveRecord(record: UsageRecord): Promise<void>
getAllSummaries(): Promise<Record<string, ProviderUsageSummary>>
getProviderTrends(provider: string, rangeDays: number): Promise<TrendData>
getAllRecords(format?: string): Promise<UsageRecord[]>
flush?(): Promise<void>
}Adapters to implement:
FileAdapter- Wraps currentstorage.tslogic (default, backward compatible)HttpIngestAdapter- Posts usage events to central service
Phase 2: Central Ingestion Service
Minimal HTTP server with endpoints:
POST /ingest/usage- Accept usage events from nodesGET /api/usage/summary- Aggregated summaries (all providers)GET /api/usage/provider/:provider/trends- Provider trendsGET /api/usage/export- Export as JSON/CSV
Storage: SQLite initially (simple, file-based), migrate to Postgres/TimescaleDB later
Phase 3: WebUI Integration
Modify WebUI routes to query central service when configured:
- Check config:
usage.backend === "http" - If HTTP: fetch from central service
- If file: use local
UsageTracker(backward compatible)
Files to Modify
New Files
src/features/usage-tracker/adapters/index.ts- Interface definitionsrc/features/usage-tracker/adapters/file-adapter.ts- Current behaviorsrc/features/usage-tracker/adapters/http-ingest-adapter.ts- HTTP clientsrc/server/ingest-service/index.ts- Central servicesrc/server/ingest-service/db.ts- SQLite schema/queriessrc/server/ingest-service/routes.ts- API endpoints
Modified Files
src/features/usage-tracker/tracker.ts- Accept adapter in constructorsrc/webui/server.ts- Route to central service when configuredsrc/config/schema.ts- Add usage backend config
Configuration
Testing Strategy
- Unit tests: Adapter interface, file adapter (existing behavior)
- Integration test:
- Start central service (in-memory SQLite)
- Configure node with HttpIngestAdapter
- Record usage events
- Query central service, verify aggregation
- Load test: 5 nodes posting usage concurrently
Success Criteria
- FileAdapter maintains exact current behavior (no regressions)
- HttpIngestAdapter posts events to central service
- Central service aggregates usage from multiple nodes
- WebUI shows cluster-wide usage when configured
- Graceful fallback if central service unavailable (local buffering)
- All existing tests pass
Migration Path
- Deploy central ingestion service
- Update node configs to use HTTP backend
- Restart nodes (they start reporting to central service)
- WebUI automatically shows cluster-wide data
Backward compatible: Nodes without config continue using file storage
Future Enhancements
- Postgres/TimescaleDB backend (better for time-series)
- Authentication/authorization for ingestion endpoint
- Compression for usage events (reduce network overhead)
- Batch ingestion (reduce HTTP overhead)
- Real-time streaming (WebSocket/SSE for live updates)
Related Issues
- feat: distributed agent cluster for massively parallel task execution #5 - Distributed agent cluster (parent)
- feat: resource monitoring and OOM detection for parallel agents #6 - Resource monitoring (sibling)
- feat: implement mixed provider pricing strategy for free/paid/sometimes-free tiers #2 - Hybrid provider pricing (will benefit from cluster-wide usage view)
Priority: HIGH
Estimated Effort: 3-4 days
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels
{ "usage": { "backend": "file" | "http", // Default: "file" "http": { "endpoint": "http://master-node:3001", "retryAttempts": 3, "localBufferSize": 1000 // Queue locally if service unavailable } } }