Lifetime numbers for Bob (TimeToBuildBob), an autonomous AI agent: PRs, issues, commits and sessions.
This repo is the source of truth for Bob's lifetime numbers. Quote them; don't re-estimate them.
Estimates like "how many PRs has Bob merged?" used to come out differently depending on when and how they were computed. This repo stores them in a month-frozen ledger, so a quoted number stays consistent with every earlier and later quote.
| Metric | Lifetime | Current month (partial) | As of | Scope |
|---|---|---|---|---|
Merged public PRs (prs_merged_public) |
4,214 | 440 (2026-09) | 2026-09-20 | public |
Opened public PRs (prs_opened_public) |
4,565 | 465 (2026-09) | 2026-09-20 | public |
Opened public issues (issues_opened_public) |
355 | 27 (2026-09) | 2026-09-19 | public |
Public commits (default branches) (commits_public_default_branch) |
8,423 | 580 (2026-09) | 2026-09-20 | public |
Definitions and caveats: LIFETIME.md. Public and private rows are never summed.
Always give the date, and link here:
As of YYYY-MM-DD: N merged public PRs (https://github.com/TimeToBuildBob/stats)
Take N and the date from LIFETIME.md or data/lifetime.json. If you need a number that isn't here, add a metric instead of estimating it ad hoc.
data/ledger.csv holds one row per metric and month:
metric,month,value,frozen,source,query,computed_at
- Recompute only while open. A month's value is recomputed daily until
month_end + 3 days(UTC). After that the row isfrozen=true. - Frozen rows never change. If a recompute of a frozen month gives a different value, the collector refuses to write it. It logs the difference to
data/drift.logand exits non-zero. - Definition changes get a new name. For example,
prs_merged_public_v2. History is never rewritten. - Lifetime = sum over months. It is the frozen months plus the current open month.
- Every row records its exact query, so any value can be re-derived.
These rules are enforced in ledger.py and covered by tests/.
| Collector | Where it runs | Metrics |
|---|---|---|
collect_public.py |
GitHub Actions, daily; uses GITHUB_TOKEN only |
prs_merged_public, prs_opened_public, issues_opened_public, commits_public_default_branch |
collect_private.py |
Computed on Bob's host by a daily systemd timer, which reads the private brain repo and sends the totals as a repository_dispatch event. Applied by the same workflow |
brain_commits, sessions |
Every public query includes is:public. Private collection sends monthly integer totals only: no repo names, titles or content leave the host. The workflow validates each payload and refuses anything but private integer rows. Only the Actions workflow writes the ledger, and each collector writes only its own metrics.
render.py regenerates the charts, LIFETIME.md, data/lifetime.json (for websites and profile READMEs) and the table above. The output is deterministic, so a commit only happens when a number changes.
- Public and private are never summed.
brain_commits(a private repo) andcommits_public_default_branchmeasure different things. - Commit search sees default branches only. Squash merges collapse a PR into one commit, so public commits undercount the commits Bob actually wrote.
sessionsis approximate and a lower bound. It starts at 2026-06, the first month with reliable session records.brain_commitsstarts at 2025-08, when Bob began committing under his own identity.- Frozen months are fixed at freeze time. A repo made private or deleted later doesn't change them.
The exact per-metric definitions are in LIFETIME.md.
- gptme/stats: project stats for gptme (stars, downloads, releases). That is a separate ledger, and its numbers are never mixed with Bob's.
- Bob's timeline: month-by-month highlights. The source is
_data/timeline.yml. - Bob's profile README