pki.sgit.ai / experiments / push-to-github

Push to GitHub

By what chain of grants does a hosted agent change a repository — and where does the thing that stops it living on the wrong branch actually live?

One engine, many worlds. This page is rendered by the scenario engine from scenario.json, which holds no capabilities of its own — it references a twin, claude-code-remote__ccr-container__2026-08-26.json, and every card below is read out of the twin at build time. The scenario may decorate a card; it may not add, remove or restate one, and the build fails if it tries.

The players — and the grant chain between them

◯
The Userperson
authorises the Claude GitHub App, in GitHub's UI — the grant is born here, two steps from where it is exercised
▦
GitHubplatform
holds the asset, and is the one party positioned to enforce a boundary
◆
The Repoasset
the thing every capability ultimately reaches
◈
Claudeservice
the model — its guardrails are one of the places the soft mandate lives
▤
Claude Codeharness
the harness that turns intents into commands
▣
The Containerenvironment THE TWIN
the execution environment — THE TWIN of this scenario; the token lives here, and everything extractable from it
The User —authorises→ GitHub —mints a scoped token for→ The Container —runs→ Claude Code —is driven by→ Claude —acts on→ The Repo

Where the mandate lives — the same constraint, three possible rooms

The soft mandate, shown as a place: the thing that keeps this world on the right branch lives in one of three slots, and the slot decides the tier. Every status below is derived from a file, never typed.

EXPECTATION in the agent's context — prose, in the loop LIVE
the twin, by byte: “branch discipline lives in the session's instructions (develop on the designated branch; push elsewhere only with explicit permission). Nothing mechanical was observed to test whic…”
the decision happens inside the agent, every time, and this is where mistakes occur
SETTING in a pre-push hook — code, outside the loop, inside the grant LIVE
the signed mandate: “the hook file is inside the grant it bounds — the agent can edit it, unset core.hooksPath, or pass --no-verify. It moves the constraint from expectation to sett…”
BOUNDARY on the platform — config, outside the grant entirely SHUT
the doors view: 0 built by this estate — the shut door, inside a scenario

The capabilities — watchable, evidenced, rung-scored, dated

Each card is a twin node wearing scene clothes: a micro-animation of the capability acting, its tier and evidence class, and a confidence rung computed from the evidence — the gradient from hypothesis to reality as arithmetic. No card in this world exceeds rung 2, because every measurement here is self-measurement: nothing in this estate has independent evidence yet, and that shut door prints on every deck it affects.

n1 rung 1
runs as the container's root user
runs as root: inside this world, it is everyone
none observed as of 2026-08-26
n2 rung 1
outbound HTTPS, through a mandatory egress proxy
the packet passes the proxy — and bounces where the wall is real
boundary observed as of 2026-08-26
n3 rung 1
push commits to the attached repository
a commit travels container → repo; nothing mechanical asks which branch
expectation observed as of 2026-08-26
n4 rung 2
GitHub API operations on in-scope repositories
API operations, scoped by the platform's tool layer
boundary observed as of 2026-08-26
n5 rung 1
execute arbitrary programs and install software
the probe stopped at the classifier — a boundary, observed working on the measurer
setting observed as of 2026-08-26
n6 rung 1
read and write the session's own accumulated record
the session record unrolls — the grant is a union over prior turns
none observed as of 2026-08-26
n7 rung 1
hold signing keys and vault keystores
keys and keystores, held and usable
none observed as of 2026-08-26
n8 rung 0
harness configuration and lifecycle hooks
the probe that would read this was refused — a hole, marked
unknown none as of 2026-08-26
n9 rung 0
reach non-allowlisted external hosts
non-allowlisted egress: unknown, not no
unknown none as of 2026-08-26

The story — each beat cites its artefact

  1. The user authorises the Claude GitHub App once; every session after that inherits the decision. the artefact
  2. This session pushed release commits to dev — permitted by mandate v2, refused once by v1, and the refusal is the estate's best story. the artefact
  3. The constraint that keeps this agent off main is prose in its own context — tier expectation, quoted below from the twin itself. the artefact

What this scenario does not claim. The animations illustrate; they do not simulate — a travelling dot is a depiction of a capability, not an execution of one. The rungs top out at 2 because the twin is self-measured, and a claim about somebody else's product at rung ≤ 2 is a floor, not a census. Version-stamped 2026-08-26: a capability claim without a date is a claim about all versions at once, which is a claim about none.

Engine: admin/build/gen_scenario.py · specified in brief v0.33.68 · gates: twin must exist; cards == twin nodes exactly; every decor key names a node; every slot derives from a real file, and the platform slot must agree with the doors view; every story beat cites an artefact that exists.