Skip to content

feat(queries): add multidevice canonical query and host discovery - #121

Merged
ErikBjare merged 3 commits into
masterfrom
feat/multidevice-query
Sep 26, 2026
Merged

ErikBjare merged 3 commits into
masterfrom
feat/multidevice-query

Conversation

@ErikBjare

Copy link
Copy Markdown
Member

Adds a multidevice version of the canonical events query, so Python users (e.g. quantifiedme, aw-research) can get combined screen time across desktop hosts, hosts synced with aw-sync, and Android devices, the way the aw-webui multidevice view does.

What

  • canonicalMultideviceEvents(host_params): builds a query that runs canonicalEvents per host (with that host's own AFK filtering) into host-suffixed variables, then combines them with union_no_overlap in the given order. Earlier hosts win where hosts overlap, so time is never double counted. Leaves events and not_afk defined, like canonicalEvents.
  • multideviceHostParams(buckets, hosts=None, **common): discovers per-host params from get_buckets() metadata:
    • host = bucket hostname field (falls back to data.hostname, then the -synced-from-<host> suffix), unknown is skipped
    • window + afk bucket: DesktopQueryParams; only an Android/ScreenTime bucket: AndroidQueryParams (no AFK filter, since mobile devices have no afk bucket); otherwise skipped
    • when a host has several candidate buckets, prefers canonical watcher IDs (so e.g. aw-watcher-android-test-... or aw-watcher-window2_... are not picked), then local over synced copies, then most recently updated
    • default order: desktop hosts first, each group by most recent activity; hosts=[...] selects and orders explicitly
  • canonicalEvents gains keyword-only options used by the above (defaults keep the current behaviour): return_variable_suffix, merge_android, exact_bucket_ids. The multidevice path uses exact query_bucket("<id>") instead of find_bucket prefix matching, since discovered IDs are exact and prefix matching can pick the wrong host when names overlap (e.g. host.local vs host.localdomain, see Unnecessary call to find_bucket at activity page  aw-webui#590).
  • canonicalEvents now sets not_afk = events on Android (previously not_afk was undefined there), matching aw-webui.

Difference from aw-webui

aw-webui's canonicalEvents runs merge_events_by_keys(events, ["app"]) on Android hosts, also inside canonicalMultideviceEvents. Merged events keep only the first event's timestamp with the summed duration, so before a union_no_overlap they claim time the phone was not used (and are no longer sorted). Here Android events are not merged in the multidevice path. test_android_events_not_merged_before_union covers this and fails if the merge is re-enabled.

Tests

tests/test_queries.py executes the generated queries with aw-core's query2 engine against an in-memory datastore (no server needed): union and AFK filtering across a desktop and a synced phone, the no-merge case, host priority, combined not_afk, and discovery from bucket metadata. Added to make test.

Also smoke-tested against a real aw-server-rust instance with 8 desktop hosts (local and synced) plus Android and imported ScreenTime buckets.

Example:

from aw_client import ActivityWatchClient
from aw_client.queries import canonicalMultideviceEvents, multideviceHostParams

awc = ActivityWatchClient()
host_params = multideviceHostParams(awc.get_buckets())
query = canonicalMultideviceEvents(host_params) + "\nRETURN = events;"
events = awc.query(query, [(start, end)])[0]

Add canonicalMultideviceEvents(), which queries each host with its own AFK
filtering and combines the results with union_no_overlap in priority order,
following canonicalMultideviceEvents in aw-webui. Add multideviceHostParams(),
which builds per-host params (desktop window+afk, or Android/ScreenTime)
from bucket metadata, including buckets synced via aw-sync.

Unlike aw-webui, Android events are not merged by app before the union:
merged events keep only their first timestamp, so they would claim time the
device was not in use and break the overlap removal.

canonicalEvents() gains keyword-only options (return_variable_suffix,
merge_android, exact_bucket_ids) and now defines not_afk on Android.
@ErikBjare

Copy link
Copy Markdown
Member Author

@greptileai review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-26T14:30:56.329460Z b429ff4 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@greptile-apps

greptile-apps Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 5/5

[Medium risk] Adds multidevice query helpers to the query builder.

The PR appears safe to merge; no new actionable issue or outstanding previous finding remains.

Summary

The PR adds per-host bucket discovery and a multidevice canonical query that combines host timelines without double-counting overlap. The latest change restricts browser matching to the watcher portion of a bucket ID, addressing the remaining previous finding.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[Bucket metadata] --> B[Discover and order hosts]
  B --> C[Canonical events per host]
  C --> D[Union without overlap]
  D --> E[events and not_afk]
Loading

Reviews (3) · Last reviewed commit: "fix(queries): match browser buckets by w..."

Comment thread aw_client/queries.py
Comment thread aw_client/queries.py Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 674725199a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread aw_client/queries.py Outdated
Comment thread aw_client/queries.py Outdated
- attach host-attributed browser buckets to desktop hosts
- order hosts by the buckets actually selected, not rejected ones
- treat hostname "unknown" as missing before falling back to data.hostname
- never pass bid_browsers to Android params
- format with black
@ErikBjare

Copy link
Copy Markdown
Member Author

@greptileai review

Comment thread aw_client/queries.py
browsersWithBuckets matched a browser name anywhere in the bucket ID, so a
hostname containing a browser name (e.g. host "chrome-box") could map a
Firefox bucket to Chrome.
@ErikBjare

Copy link
Copy Markdown
Member Author

@greptileai review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b429ff419f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Makefile
ErikBjare added a commit to ErikBjare/quantifiedme that referenced this pull request Sep 26, 2026
)

* feat(screentime): discover ActivityWatch hosts from bucket metadata

Hosts are now discovered from bucket metadata, including buckets synced
with aw-sync, instead of requiring every hostname in the config. The
config hostnames list becomes an optional override (selection + priority
order), with new optional exclude_hostnames and include_mobile.

With aw-client that has multidevice support (ActivityWatch/aw-client#121),
all hosts, including Android devices, are loaded with one
canonicalMultideviceEvents query per week, cached with the host/bucket
spec as part of the key so the cache is invalidated when hosts change.
With the currently released aw-client, it falls back to the previous
per-host aw-research query for the discovered desktop hosts.

* fix(screentime): typecheck against aw-client without multidevice helpers

* fix(screentime): keep $hostname by querying hosts individually

Load each discovered host with the aw-client per-host canonical query and
combine them client-side in priority order, instead of one server-side
multidevice union, so events keep $hostname (used by the Dashboard
notebook to show time per device). Caching is now per host and week.

Also add a mocked test for combining hosts.
@ErikBjare
ErikBjare merged commit 2e28ac0 into master Sep 26, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant