Why API-first (not local-clone-write)
When PETROVA’s verbs need to write to a consumer repo, they do so via the GitHub Contents API, not by editing a local clone and pushing. This page explains why.
What the rejected approach would have looked like
Section titled “What the rejected approach would have looked like”A reasonable alternative: the verb has access to a local clone of
the target repo, edits files directly, runs git add && git commit && git push, then opens a PR via gh pr create. Many automation
tools work this way.
PETROVA explicitly does not.
Why API-first wins for this system
Section titled “Why API-first wins for this system”1. Two clones drift; one canonical write path doesn’t
Section titled “1. Two clones drift; one canonical write path doesn’t”If verbs write through local clones, you have two sources of truth on disk: the operator’s working tree, and any agent fleet’s working tree. They’ll diverge — the operator pulls, the fleet doesn’t (or vice versa), and a verb retry against the fleet’s stale clone overwrites work the operator just landed.
API writes have no working tree. Every commit is built from the
target branch’s current SHA, freshly fetched. Concurrent edits
surface as 422 Unprocessable Entity (SHA mismatch) rather than
silent overwrites.
2. Authentication concentration
Section titled “2. Authentication concentration”A local-clone-write design needs git push credentials wherever the verb runs. A fleet running in a CI job, a local session, and an agent service would each need credentials provisioned, rotated, and revoked separately.
API writes go through one auth surface — the
PETROVA_GITHUB_TOKEN PAT or GitHub App installation. One identity,
one rotation cycle, one revocation switch.
3. The audit trail lives in PR bodies, not commits
Section titled “3. The audit trail lives in PR bodies, not commits”Every PETROVA PR body carries a metadata block with the verb, idempotency key, actor, trigger, schema fingerprint, and MR citations. None of that fits in a commit message.
Local-clone writes make the commit the audit unit. API writes make the PR the audit unit, with commits as a thin substrate. This matches PETROVA’s verb-shaped semantics — a verb is one PR, with N commits inside.
4. No working tree means no working-tree state to manage
Section titled “4. No working tree means no working-tree state to manage”Local-clone writes have to think about: shallow clones (faster but
limit git log reach), branch protection that requires signed
commits (would need GPG keys provisioned), .gitattributes smudge
filters (would run unintended transforms), pre-commit hooks (would
fire and possibly fail). API writes sidestep all of this — they
produce blobs and trees directly via the API.
The cost we pay
Section titled “The cost we pay”- Multi-file commits are awkward. The Contents API commits one file per request. For a verb that needs to land 5 files atomically, we make 5 sequential calls. Tolerable at PETROVA’s scale (most verbs touch 1–2 files); the documented upgrade path is the Trees API for atomic multi-file commits when this becomes a problem.
- Rate limits are coarser. A PAT gets ~5000 req/hr; a GitHub App installation ~15000. With one verb = ~6 API calls (get default branch SHA, get file SHAs, put files, create PR, optionally enable auto-merge), you get ~830 verb invocations/hour on PAT and ~2500 on App. Plenty for current scale.
- Latency is meaningful. Each verb round-trip is ~10–30 seconds vs ~1 second for local-clone-and-push. We accepted this trade for the audit + concentration benefits above.
See also
Section titled “See also”- Why no central database — the related rejection (also choses git as source-of-truth).
- Trade-offs accepted — the full list.
request_review— the canonical PR-emitting verb.