Skip to main content
Preview. The production path today ends at the human-readable comment and its public run report. The bytes below are the renderer’s v6.10.0 golden; live comments now carry "contract":"6.13.0", and blocks marked v6.13 show what that version added. The markers, the garnet:summary register, the JSON twin and the Runtime Review MCP are not yet a supported machine interface. Pin your parser to the contract field and expect it to move with a contract bump.
The comment is the interface. This page is its contract: every line it can emit, how review bots and coding agents read it, and how a merge gate parses it. Humans read the same comment here.

The comment contract

Every line the comment can emit, verbatim. Parse against this.

Agentic reviewers

CodeRabbit, Copilot, Bugbot, Qodo, Greptile, Devin Review. One record, one gate, one shared paragraph.

Coding agents and the MCP

Devin, Claude Code, Cursor, Codex. One hosted URL, no auth.

The comment contract

One sticky comment per pull request, updated in place, pinned to the head commit it records. These are the bytes of a comparison comment at contract v6.10.0 — the renderer’s own locked render, one job changed and four unchanged. Parse against them. The same comment rendered: Rendered from the renderer’s v6.10.0 golden fixture (private testbed). Links inside the fixture are illustrative; the live comment is on garnet-runtime-review-reference#29.

Historical example

The logged-out comparison example, recorded with an earlier released action and sensor (contract v6.10.0), is: That comment compares head 23bbd88 with previously recorded 5a6561f — one job, unchanged. It shows the unchanged shape, while the fixture above shows the changed shape.

Every line the comment emits

Markers. Match the comment author garnet-runtime-review[bot] and the first marker. The second is namespaced by the posting frontend and carries the Execution Profile’s state. The third carries the full SHA the Execution Profile covers. The fourth is the machine register — the counts and the contract version, as JSON:
While jobs are still recording, the second marker reads garnet-control-plane-pending-pr-comment:v1:app.garnet.ai. Read counts from garnet:summary, not from the prose. It is the only place chain counts and runner-background movement are carried, and contract tells you which version of this page you are parsing. Headline. Bold body text, never a heading:
Pending headline. Until every job’s profile has landed. One headline, one quiet status line, and the how-to-read explainer: no counts, no timestamp, no permalink:
Terminal headline. Only after every workflow run on the head has finished without producing a profile. It replaces the pending comment, states one fact, and never renders while a run is still in progress:
Both shapes above are the v6.13 contract, which ships with a coming App update. It is not deployed yet: action v2.3.0 against the production control plane renders v6.10. Today’s live pending headline is v6.10:
v6.10 has no terminal state. Match the pending state on the marker and on recording in the headline, not on the full sentence.
Metadata line. Two blockquote lines in every recorded state: the finding, then the provenance. One fact per · segment, each count noun pluralized against its own number. Zero-delta comparisons state the verdict and name what they compared against:
The first shape is a snapshot, the other two are comparisons. +A −R counts destinations attributed to the workload; a zero side drops. Job fold row. One row per job, always. On a changed job the delta leads, so the left edge is the scan column; an unchanged job carries its destination count and the word unchanged:
The row carries no step sentence. unchanged sits directly after the destination count, so it claims the destination projection only — ancestry, steps, and other recorded context may differ. A job row counts destinations, not chains; read chains from garnet:summary. Changed job. The tree renders as a diff fence instead of a <pre> block — + only in the current record, − only in the previous one, unmarked in both:
Runner background. Never a fold of its own. Every recorded root of a job sits in that job’s one block, separated by a blank line. When a non-workload root’s subtree carries a +/− mark, the root line appends the boundary label — the count sits beside the marks it counts, and nothing is hidden:
Background movement never reaches the metadata line or the fold row. Its run-level totals are backgroundAdded / backgroundRemoved in garnet:summary. Observed action. A shaped terminal in the leaf position: ○ is a network destination — the first recorded name defanged at the final dot, or a bare IP when no name was recorded. □ (file) and ▷ (execution) are reserved and never render today. Read observation kinds from garnet:summary’s kinds, never from the shapes. PIDs stay in the Actions job summary; ports and protocol stay in the Execution Profile and the JSON:
Bracket context. Every (…) annotation is recorded or deterministically derived, and never a verdict: (step: "<name>"), (ran from /tmp/…), (dns resolver) — loopback on port 53, (cloud metadata) — 169.254.169.254, (github infra) — a name under .githubapp.com or .actions.githubusercontent.com, (github infra · rotated from <previous identity>) — a provable address rotation inside GitHub’s published ranges, counted in neither +A nor −R, and (garnet sensor) — Garnet’s own upload. Attribution. Structural, never by name: a root whose edges descend through Runner.Worker or bear a recorded step is the workload; the rest is runner background, labelled at the boundary when it moved. Typography carries no meaning of its own — <strong> marks the process that acted, <em> marks bracket context. No egress, and truncation. Both are stated, never silent:
Per-job permalink. The last line of every fold. Read the profile parameter; ignore the campaign parameters after it:
The selector resolves for one exact published (run, profile) pair and nothing else. A bare run URL, an unpublished or expired profile, and a profile that belongs to another run all return the same 404 — indistinguishable by design, so a 404 tells you nothing about whether a profile exists. The page’s canonical and og:url are the selector URL, its JSON alternate sits on the same host, and the page’s own copy-link button hands back the same URL without the utm_ parameters. The job summary’s permalink carries utm_medium=step_summary instead of pr_comment; the pair is what resolves, the campaign parameters never are.
Three transforms apply to display only — the Execution Profile, the Actions job summary, and the JSON keep the recorded value:
  • Hostnames are defanged at the final dot: registry.npmjs.org renders as registry.npmjs[.]org.
  • Step names lose the runner’s ordinal prefix and any unexpanded workflow expression: 4. Run workload ${{ matrix.name }} renders as Run workload. The comment never shows an ordinal.
  • Process names lose a trailing run of four or more digits: provjobd1326539233 renders as provjobd.
Fail closed on four states: no marked comment (recording never ran), pending headline (wait, don’t fail), terminal · not recorded headline (the run finished with no profile — v6.13), garnet:commit ≠ head SHA (stale record). In all four, there is no runtime evidence for the current head. Fall back to your normal policy. The comment carries recorded facts and no verdict; the decision is yours.

Agentic reviewers

Garnet decides, reviewers read. Each layer below has one job, and a reviewer integration uses all of them. Timing, staleness and fail-closed belong to the gate, never to instruction wording. The gate and the push run as workflows in your repository. On the pnpm fork, the mirror writes the description block for each head-bound comment. The gate is paused and runs by hand only: it cannot pass until a record declares its capture complete, and no comment does that yet.

The shared paragraph

Paste it unchanged into each reviewer’s instruction file:
If the reviewer calls MCP tools, put this line first:

Where each reviewer reads it

Vendor docs checked 2026-10-08. Recheck them before you rely on a plan or setting name.

How to tell it worked

Delivered (gate green, description block present) < pulled (CodeRabbit lists the tool under Additional context used) < consumed (the review repeats a fact bound to the current head, or correctly says there is no Execution Profile for this head) < judged (the review ties a recorded change to the diff). Only consumed and judged count. So far, head-bound citations have come from Devin Review (pnpm#102) and Greptile (posthog#107), both reading the pull request. None has come through an MCP call yet. Two demo pull requests carry the same manifest-only diff and different recorded egress. Use them to check that a reviewer reads the Execution Profile rather than the diff: registry-only egress and an install script reaching named hosts.

Coding agents and the MCP

The Runtime Review MCP gives coding agents, MCP-capable review bots and custom harnesses the same Execution Profile as structured data. It takes the pull request’s current head, finds the Runtime Review comment pinned to it, and separates workload changes from runner background and GitHub platform changes. Nothing is cached, so every answer is about the head that exists when you ask.
  • Endpoint: https://garnet-runtime-review-mcp.vercel.app/mcp
  • Authentication: none. Public repositories only.

Connect

Check the connection: ask the agent to “get the Garnet runtime review for garnet-labs/pnpm#94”. The answer is changed / workload-change, with registry.npmjs.org removed under Garnet Bootstrap Routing / bootstrap-routing.

Read the result

Agent instructions

Paste into the agent’s skills, rules or AGENTS.md:

Limits

  • Zero changed jobs read as undeterminable today. The comment does not yet declare a complete capture, and a missed connection looks exactly like no change. A recorded change stands either way, so no-change does not appear yet.
  • Public repositories only. The hosted endpoint refuses private repositories.
  • Shared rate limit. Hosted calls share GitHub’s anonymous API limit. GitHub returned 403 ... (rate limited) means retry later.
  • The comment is the index. The MCP finds a pull request’s Execution Profiles through the Runtime Review comment. No comment, no evidence.

Custom harnesses and gates

For a reproducible decision instead of a judgment, parse the comment directly. StampHog, the reference repository’s vendored PostHog merge-gate agent, does exactly that in the policy example: every static gate green, held solely on recorded egress the diff never mentions. Without a parser, call get_runtime_review and gate on assessment: start with workload-change only, because undeterminable covers every zero-change record today.

The harness on a pull request

The harness consuming the Execution Profile end to end, posting its own decision next to Garnet’s. The same harness on a replayed upstream change.
If your harness already locks its own output shape and gates, keep it. Carry the primitive into it instead: (1) fail closed on the four evidence states above, (2) gate on the comment’s own +A −R delta against the previous profiled commit, (3) make every finding cite its recorded chain and destination.
Every fold’s permalink has a JSON twin at the same exact selector. The response carries its own schema_version (currently runtime-review-public/v3); read it before parsing, and treat the shape as preview alongside the comment contract above:
The selector is required. Wrong or missing ?profile=, or a bare /public/runs/<run-id> URL, returns a 404 by design. There is never a silent fallback. See The public run report.