Human Token Tracker
Every project has a cost in human hours and AI tokens, and nobody wants to log either by hand. These jobs read the effort off the systems where it already happens: pull requests on GitHub, session telemetry from the Claude Code recorder, and the effort-log file each repo carries. The ledger is rebuilt from keys, so running a job twice never counts anything twice.
Work in, ledger out
Five steps, one of them human. The team works as normal; four scheduled jobs turn what they did into rows, then into summaries. The rail is the orientation; the step cards and the exceptions table are the truth.
How each step works
A repo enters the tracker when someone commits a registration file to the tracker’s telemetry branch. The PR sync processes those files first, verifies that the committer is the person named in the request, and creates the repo record. There is no auto-enrol; an already-registered repo is a no-op, so a file that cannot be deleted is retried harmlessly every night. After that, the team just works.
The recorder plugin posts a Claude Code session the moment it ends, and the row is stored before the request returns. The Mac mini job covers the other paths: it pulls recorder telemetry from the tracker branch and backfills the Mac mini’s own local transcripts, deduplicating token counts by message id. Both paths apply the same rules: the repo must be registered, the client’s own identities are excluded, the Claude token row is keyed by session so a retry is an update, and the hours rule recomputes each day the session touched.
For every tracked repo with a GitHub repo, the job fetches pull requests updated since that repo’s last sync and upserts them by GitHub id. The author is resolved to a person from the author block in the PR body first, then from the GitHub login; an unresolved author is stored unattributed and counted. Session token rows on the same branch are linked to the PR afterwards, and the run writes its own sync row with repos synced, PRs upserted and errors. The hosted job returns as skipped until its GitHub token exists; the launchd job on the office Mac mini runs the same handler in the meantime.
Each tracked repo may carry a committed effort-log file. Every entry becomes up to two token rows: Claude tokens keyed on the session id, and human hours as centihours keyed on the session id plus a suffix. Only entries by the client’s own identities are kept, because Edge8 contributors are already captured by telemetry and would otherwise be counted twice. Persisting is delete-then-insert by session id, so the same file ingested twice yields the same rows.
Per repo, three things may be written and each has a rule for when the model is allowed to run. The executive summary is generated once from the repo’s status page and after that only when someone explicitly regenerates it. The digest of the latest ten PRs regenerates only when the set of PR numbers and states changed since last night. The FAST goal is suggested when the repo has none yet or its status page changed, and a goal set by hand is never replaced. The whole sweep shares a budget of forty model calls as a circuit breaker; steady-state nights spend a handful.
Every exception has an owner
Most of these are quiet on purpose: a ledger that guesses is worse than one with a gap. Each row is a condition the code actually handles, what it does, and where a human sees it.
| Condition | What happens | Where you hear about it |
|---|---|---|
| GitHub token not configured in the hosted environment | The hosted job returns without doing anything; the Mac mini job carries the load | Settings → Agents, as a skipped run |
| Repo archived, or registered without a GitHub repo | Left out of every sync, ingest and refresh | Nowhere, by design |
| PR author cannot be matched to a person | PR stored with no person; counted as unattributed | The sync run row and the run result |
| One repo’s GitHub fetch fails | Error recorded against that repo; the other repos still sync | The sync run’s error list |
| Repo homepage cannot be read | Existing live URL kept, never cleared (best-effort) | Nowhere, by design |
| Effort-log entry has no session id or no day | Dropped, because it could not be re-ingested safely | The run result, as a skipped count |
| Effort-log entry belongs to an Edge8 contributor | Filtered out; telemetry already covers them | Nowhere, by design |
| Session arrives for a repo nobody registered | Not stored; there is no auto-enrol | The ingest result; the plugin holds the record and re-probes |
| Session author is one of the client’s own identities | Excluded, never billed | The ingest result |
| Repo has no status page | Executive summary marked as such; no model call, re-checked cheaply next night | The run report |
| PR set unchanged since the last digest | Digest skipped; no model call | The run report |
| Model budget spent (40 calls a night) | The sweep stops and says so; remaining repos wait for tomorrow | The run result, budgetExhausted |
| A goal was set by hand | Never overwritten by the nightly suggestion | Nowhere, by design |
| Sync run row cannot be opened | The sync aborts before touching a PR, rather than run unobserved | Settings → Agents, as an error run |
| Local job log contains an error | Run recorded as error, with the log attached | Settings → Agents |
The seven elements
Every workflow we document has the same anatomy: seven elements, each assigned to a human, a machine, or both. This is the Centaur Map from our workflow design method.
Time. 03:30 (+07) on the office Mac mini, then 09:10, 10:10 and 11:10 (+07) for the three hosted jobs. Nobody fills in a timesheet.
GitHub pull requests for every tracked repo, each repo’s committed effort-log file, Claude session telemetry from the recorder plugin, and the Mac mini’s own local Claude transcripts.
The machine decides whose work a PR is, which sessions count, and whether a summary needs regenerating. A human decides what the numbers mean, and only a human registers a repo.
Every row lands in one schema keyed by repo, person and day. Client-side identities are excluded before anything is written; Edge8 contributors are never counted twice across telemetry and effort logs.
Pull request rows, token entries (human hours as centihours, Claude tokens), work sessions behind the hours rule, one executive summary and one PR digest per repo, and one FAST goal suggestion per repo.
The ledger and the per-repo pages in the team app. The weekly check-in summary reads the same tables for its Human and AI token report.
Each run leaves a row on Settings → Agents with its counts, errors and the model calls it spent. A skipped or error run is the alarm; an unattributed PR count that keeps growing is the second one.
Reads and writes
- Reads: tracked repos, client identities, GitHub PRs and repo homepages, each repo’s effort-log file and status page, recorder telemetry, local Claude transcripts
- Writes: pull requests, token entries, work sessions and the hours ledger, per-repo summaries, project goals, a sync run row, and a routine run row per job
- Keys: GitHub PR id for PRs; session id for token rows; repo, person and day for the hours ledger; a fingerprint of PR numbers and states for the digest; the status page’s blob sha for the executive summary and the goal
The standing rules
- No auto-enrol: a repo is tracked only after an explicit, committer-verified registration
- A client’s own identities are captured and excluded, never billed
- Every write is keyed, so any job can be re-run from any machine without double counting
- The model runs only on change, inside a nightly budget; a summary is pinned until a human asks for a new one