flowchart LR
subgraph each [Each tailnet Mac]
T[agent transcripts] --> C["collect 23:55"]
R[run logs and plans] --> C
S[file sweep] --> C
E[Syncthing events] --> A[audit daemon]
end
C --> SN["snapshots/DAY/MACHINE.json"]
A --> AU["audit/MACHINE/DAY.jsonl"]
SN --> SY[(umbrella:platform via Syncthing)]
AU --> SY
SY --> G["aggregate 00:15 and 07:30"]
W[workitems.yaml] --> G
G --> D[(worklog.duckdb)]
G --> RP["reports/DAY.json and .md"]
RP --> P["/admin/work"]
P -->|"status, notes, next steps"| W
Work Monitor
The Work Monitor answers one question every morning: what was done yesterday, in which state is it, and where do I continue? Its snapshots are also the activity source of the Project Monitor, the project-centred morning page (/admin/morning redirects there). Since 19 Oct 2026 the hub host (mac-studio) aggregates inside the Project Monitor’s nightly run; openscience no longer runs an aggregator.
Open it at /admin/work on the hub, publicly at https://ai-platform.lettre.ai/admin/work. The audit stream is at /admin/work/audit.
How it works
No SSH is needed: every machine writes into the Syncthing-shared folder platform/registry/worklog/, and the hub host merges what arrived. A machine that was asleep reports when it wakes (launchd runs the missed job; a run before noon is filed under the previous day), and the hub re-aggregates the day when a late snapshot appears.
Path under platform/registry/worklog/ |
Writer | Content |
|---|---|---|
snapshots/<day>/<machine>.json |
collector, every Mac | sessions, run logs, plans, machine state, file sweep |
audit/<machine>/<day>.jsonl |
audit daemon, every Mac | Syncthing change events and heartbeats |
workitems.yaml |
you (page or editor), seeded once | titles, links, status overrides, notes, next steps |
reports/<day>.json, .md |
aggregator | the full report; latest.md also feeds the old morning-plan path |
worklog.duckdb |
aggregator | tables machines, sessions, instructions, artifacts, workitems, milestones, audit_events, findings, proposals |
What the report shows
- Machines: reported or not, online on the tailnet, services down, audit heartbeats.
- Timeline: agent sessions per machine with your prompts as ticks and synced file events as grey bars.
- Workflow and milestones: Plan, Implement, Verify, Done for every work item.
- Instruction flow: which instruction files (skills,
.j2, partiturs, rules) were read, for which project, producing which kind of artifact. The “purpose” column is the first sentence the agent wrote when it invoked the file. - Activity heatmap: 14 days per machine, by files changed or prompts.
- Work board: every item with status, evidence and editable fields.
- Proposals: next steps for everything unfinished.
Status and how it is inferred
Transcripts do not store tool results, so success is inferred, not read. Each item also carries a confidence.
| Status | Rule |
|---|---|
| completed | no open plan todos and nothing contradicts completion |
| partial | a plan exists and implementation evidence exists, but todos remain open or verification is missing |
| planned | a plan exists but neither the transcript nor file changes under the item’s paths show implementation |
| blocked | the last turn ended in an error that names a limit, quota or exhaustion |
| failed | the last turn ended in another error and nothing was implemented |
| stale | unfinished and untouched for more than 3 days |
File changes under an item’s paths count as implementation evidence even when the work ran in a parallel agent that no transcript captured. Your status always wins over the inferred one; the board shows both.
Editing work items
On the board, change the status, add a note, write your own next steps, or list the paths that show progress, then Save. This writes workitems.yaml and re-aggregates immediately. “Pin” on a proposal copies it into your own next steps.
Writing is allowed when the request reaches the hub directly from this Mac or the tailnet, or when it carries the edit token. Anything coming through the public proxy needs the token:
python -m ai_platform.worklog.cli token # creates and prints ~/.config/ai-worklog/tokenPaste it once into Edit access on the page (it stays in that browser’s local storage).
Audit module
The audit captures what happens in the synced workspace in three layers:
- Event stream.
com.ai-platform.worklog-auditlong-polls the local Syncthing REST API and records local and remote changes, device connects and disconnects, folder errors and state changes. A heartbeat every 5 minutes proves the daemon was alive, so a quiet stretch can be told from a silent one. - Nightly sweep. The collector walks the whole umbrella (and the PDAI volume when mounted) by file mtime. This catches changes the event stream missed, and counts per project and day.
- Rule checks. Findings with a suggested action: machines that did not report, Syncthing devices or folders out of sync, conflict copies, launchd services that crashed, routes failing for 48 hours, failed partitur runs, plans that were never implemented, plans with open todos older than 2 days, failed items, and gaps in audit capture.
Install
On every Mac:
cd ~/ai-<machine>/platform
scripts/install-worklog.sh # collector 23:55 + audit daemonOn exactly one Mac (the hub host, mac-studio):
scripts/install-worklog.sh --hub # audit daemon + Project Monitor (collect and aggregate at 02:30)--aggregator (00:15 and 07:30 aggregation without the Project Monitor) remains for a hub host that does not run the monitor.
--dry-run shows what would happen and --uninstall removes the jobs. The jobs use the hub venv python when it has duckdb and PyYAML, otherwise python3; the collector and the audit daemon need only the standard library. Logs are in ~/Library/Logs/ai-worklog/.
Run once by hand:
python -m ai_platform.worklog.cli collect # this machine's snapshot
python -m ai_platform.worklog.cli aggregate # latest day
python -m ai_platform.worklog.cli backfill --since 2026-10-13
python -m ai_platform.worklog.cli audit-checks # print findings as JSONLimits
- Cursor transcripts store no tool results, so a Cursor session that ran shell commands only is shown as completed with low confidence. Claude Code transcripts do store them: failed tool calls, the agent’s report at the end of each turn, plan texts, todos and the paths named in shell commands are recorded.
- The sweep has a time budget (90 s for the umbrella, 40 s for the PDAI volume). When it is hit, the machine card shows “partial sweep” and activity under that root is a lower bound.
- Git history is not used: most
.gitfolders in the synced tree are incomplete.