Problem Context
I needed an accountability system that couldn't be manipulated. Most time trackers rely on manual input or can be disabled when convenient. The requirement was stricter: capture all foreground window activity, classify it as productive or distracting, and enforce consequences automatically.
Constraints:
- Must run continuously without noticeable CPU impact
- All data stays local (no cloud, no analytics)
- Once logged, data cannot be edited or deleted
- The system should intervene when I'm not meeting productivity targets
Initial Approach
The first version used Python with polling every second and stored data in JSON files. This worked for collecting activity but had problems:
What didn't work:
- JSON files became megabytes within days, slowing down reads
- Python's startup time made the system service feel sluggish
- No enforcement mechanism—just passive tracking
- File-based storage was easy to delete (defeating the immutability goal)
Design Decisions & Trade-offs
Language: Rust
Rust was chosen for:
- Memory safety without garbage collection: The Windows API polling needs to be reliable
- Small binary size: The entire release build is <10MB
- Safe FFI: Interfacing with
user32.dllfor window handles is straightforward - Low resource usage: Runs at <5MB RAM with negligible CPU
The trade-off: Rust's learning curve made development slower initially, but eliminated entire classes of runtime errors.
Architecture: Four-Layer Data Flow
The system processes data through four isolated stages:
1. System Watcher (Raw Observation)
- Polls
GetForegroundWindow()every 10 seconds - Writes append-only logs to SQLite
- No classification logic at this layer—just observation
2. Session Engine (Merge & Classify)
- Merges raw logs into continuous sessions
- Applies app classification rules (productive vs distracting)
- Enforcement logic lives here: If effective work < 2 hours, terminate distracting apps
3. Daily Evaluator (Scoring)
- Freezes data at end of day
- Calculates "Effective Work" using:
Effective Work = Productive Time - (Distracting Time × 0.75) - The 0.75 multiplier is subjective but reflects that distraction actively undoes focus
4. Streak Engine (Judgment)
- Simple boolean: Did I meet the target? Yes → Streak++, No → Streak = 0
- No partial credit
This separation prevents mixing concerns and makes each layer testable independently.
Database: SQLite with Append-Only Pattern
SQLite was chosen over cloud storage for:
- Offline operation: No network dependency
- Atomic writes: Guaranteed consistency even if the process crashes
- Query flexibility: Can analyze historical patterns with SQL
The append-only pattern ensures immutability. Once a record is written, it's never updated or deleted through the UI.
Schema evolution challenge: When I added "focus session" tracking later, I used SQLite migrations but preserved old data structures. This meant carrying forward schema complexity, but it was worth it to maintain historical integrity.
Implementation Notes
Windows API Polling
The System Watcher uses GetForegroundWindow() to get the active window handle, then GetWindowTextW() to extract the title. This runs on a background thread with a 10-second interval.
Trade-off: 10 seconds was chosen because:
- 1-second polling felt intrusive (CPU spikes)
- 30-second polling missed brief app switches
- 10 seconds captures meaningful activity without overhead
Process Termination for Distracting Apps
When effective work is below the threshold, the Session Engine terminates processes by name using taskkill. This is blunt but effective.
What I would change:
- This doesn't handle browser tabs running distracting sites (YouTube, Reddit)
- A smarter approach would hook into browser APIs, but that introduces complexity
UI: Read-Only by Design
The Tauri-based UI only displays data—it cannot mutate the database. This enforces the rule: the system is an observer, not an editor.
Results & Impact
The system has been running daily for three months. It successfully:
- Captures all activity without user intervention
- Enforces app termination when I fall below productivity targets
- Maintains a tamper-proof historical record
What stayed hard:
- Classifying ambiguous apps (e.g., is "VS Code" always productive?)
- Handling system updates that restart the service
- Browser-based distractions remain untracked
Takeaways
This project reinforced that immutability isn't just a data structure property—it's a design constraint that influences architecture. Once I committed to append-only logs, the rest of the system fell into place.
Building enforcement mechanisms taught me that deterrence works best when it's automatic and impartial. The system doesn't negotiate, which is exactly what makes it effective.
For anyone building similar tools: start with the simplest possible data model, make it immutable from day one, and resist the urge to add "undo" features.
