---
type: note
alexandria_core: true
tags: [screentime, reference]
author: alexandria
---

# Reading Screen Time (local, read-only)

The `screentime` module lets the agent read this Mac's **local Screen Time
store** (`~/Library/Application Support/Knowledge/knowledgeC.db`) on demand, so
it can answer "where did my time go?", "how long was I on X?", or "was
yesterday focused?" with per-day totals, top apps, longest focus blocks, and
categories. **Read-only against the source** — the live DB is never even
opened (it is snapshot-copied to a temp dir and the copy is queried); the only
files the reader writes are its own two history series (below).

The reader (`python -m alexandria_vault.engine.screentime_read`) is
**plumbing**. The owner never runs it or sees a command — they ask in plain
language and the agent does the rest, then answers with a story, not a dump.

## Where it runs — this Mac, locally
knowledgeC.db records *the machine it lives on*: an automation host's copy
would show the host's own headless usage, and **nothing syncs into this store**
— there is no iPhone/iPad data here. Run it in-session on the machine the
owner actually works on, like the iMessage reader and unlike the digest/sweep
engine.

## Prerequisite — Full Disk Access (one-time)
macOS TCC blocks reading `knowledgeC.db` until the **app running the
assistant** has Full Disk Access. Grant it to Claude (or the terminal the
agent runs in): **System Settings → Privacy & Security → Full Disk Access →
enable it → re-open the app.** Until then reads fail; the reader prints that
with the fix. This is a permission grant, not a stored credential.

## The two series (know this before synthesizing)

**1. App-usage (the headline).** knowledgeC's `/app/usage` stream: per-app
foreground time — the same method as Apple's Screen Time pane. The reader
rolls it into per-day totals, top apps, per-app sessions and longest
stretches, category shares, an hourly profile, and each day's longest
continuous screen block (chunks ≤5 min apart merge — knowledgeC opens a new
event on every focus switch, so raw events are not sessions).

**2. Screen-on (the fallback).** `/app/usage` can silently stop being donated
(an OS-level outage seen in the wild). Once its rows age out (~4 weeks) a
naive reader becomes indistinguishable from "laptop never opened". The reader
detects a dead stream and falls back to **screen-on time** — the
`/display/isBacklit` lit intervals, split at the same day boundary: per-day
totals, first/last, blocks, longest block, hourly profile, after-midnight
minutes. **No per-app breakdown exists in this mode**, and it is slightly
generous (idle-with-display-lit counts — though it tracks app-usage within a
few percent where both streams overlap).

### What an outage looks like, and how the reader reports it
- `app_usage_status` (top-level in `--json`, a ⚠ line in the human summary,
  and always on the stderr coverage line): `ok`, or `dead since …`. Dead is
  judged **against the `/display/isBacklit` stream, not the calendar** —
  display-lit days continuing more than a day past the last app-usage event
  mean the stream stopped, while both streams quiet together just means the
  laptop stayed shut (no false alarm after an off-laptop stretch).
- `headline_metric`: `app-usage`, or `screen-on` when app-usage can't cover
  the window. In fallback mode, say so when answering and never invent an app
  breakdown.
- The stderr coverage line every run prints:
  `window <start> → <end> · N day(s) with data · headline <metric> · /app/usage <status>`
  (stderr, so `--json` stdout stays parseable). Read it before synthesizing.

## The day boundary
Days end at a **boundary hour** — default **04:00 local**, so a 1am session
counts toward the evening's day, not the next morning's. Tunable via
`screentime.day_boundary_hour` in `Agent/config.json`. Events carry their own
GMT offset, so days bucket correctly across travel. A `04:00` first/last
endpoint means activity ran up to (or from before) the boundary.

## Retention and the history files
knowledgeC retains only **~4 weeks**, so each run upserts a per-day rollup
into `Agent/scripts/screentime/history.json` (app-usage) and
`history-screen-on.json` (the fallback series — its own file; it never
contaminates the app-usage series). The reader creates them on first save and
they are **meant to be committed** — long-run trends survive the rolling
window. Upsert rules for both: one entry per logical day; a final day is never
overwritten by a partial one; **zero-usage days are never upserted** (a gap is
honest — a dead `/app/usage` stream manufactures zero-days out of mere
notification events, and a zero is a claim about the owner's day the data
can't support); the file is untouched when a run has nothing to upsert.
**An unread stretch near the ~4-week horizon is data about to age out — read
it promptly and flag it.**

## The reader (flags, for the agent)
```sh
P="${ALEXANDRIA_PYTHON:-python3}"   # the interpreter with the alexandria-vault package
"$P" -m alexandria_vault.engine.screentime_read --vault .                 # last 7 logical days + save history
"$P" -m alexandria_vault.engine.screentime_read --vault . --json          # machine-readable (prefer for synthesis)
"$P" -m alexandria_vault.engine.screentime_read --vault . --since today   # today | yesterday | 2w | 30d | YYYY-MM-DD | all
"$P" -m alexandria_vault.engine.screentime_read --vault . --history       # both saved series (app-usage + screen-on)
"$P" -m alexandria_vault.engine.screentime_read --vault . --no-save       # ad-hoc run; skip the history writes
"$P" -m alexandria_vault.engine.screentime_read --vault . --top 15        # more apps in the human summary
```

## Weak signals — hedge accordingly
- **Unlock counts** (`/device/isLocked`) are logged sporadically on modern
  macOS — a floor, not a count.
- **Per-domain web usage** comes only from Safari-family donations (Chromium
  browsers report nothing).
- **Notification counts** include system daemons.
- In fallback mode, the *running* lit block is only donated when the display
  next sleeps, so "today so far" lags by the current block — and a drifting
  machine clock can mis-bucket late-night days.
- **Coverage starts at machine setup** — earlier usage lived on a previous
  machine and isn't in this store.

## Optional config (`Agent/config.json` → `screentime`)
All optional; built-in defaults apply when absent.
- `screentime.day_boundary_hour` — local hour a logical day rolls over, 0–23 (default `4`).
- `screentime.default_since` — window when none is asked for (default `"7d"`).
