---
title: "Provenance for agent output — commit, branch and model on the card"
canonical_url: "https://krowk.com/blog/agent-output-provenance"
last_updated: "2026-09-02T08:10:35.493Z"
meta:
  description: "When a model takes the screenshot the picture has no author. What to record at push time, why the dirty flag matters, and why provenance belongs on the artifact rather than the run."
  "og:description": "When a model takes the screenshot the picture has no author. What to record at push time, why the dirty flag matters, and why provenance belongs on the artifact rather than the run."
  "og:title": "Provenance for agent output — commit, branch and model on the card"
---

**Written 26 August 2026**

# **A screenshot nobody stands behind**

When a model takes the picture, the picture arrives with no author. Seven facts fix that, all of them sitting in the environment ten seconds before the container exits.

[See what a card carries](https://krowk.com/how-it-works) [Read the docs](https://krowk.com/docs)

A screenshot used to be self-authenticating. Somebody took it, and that somebody was in the room — you could ask them what branch they were on and they would remember. The picture was evidence because a person stood behind it.

That stopped being true quietly. When a model takes the screenshot, the picture arrives with nobody behind it. It is a correct-looking image of a screen, and there is no way to tell from the image whether it came from the branch under review, from a stale build, from a local tree with three unrelated edits in it, or from a run that half-failed and got retried.

So a reviewer does one of two things, and both are bad. They trust it, which means the review is theatre. Or they pull the branch and run it themselves, which means the screenshot saved nobody any time.

## The missing field is not the image

The fix is boring: record what produced the file, at the moment it was produced, and show it next to the file. Not in a log, not in a sibling artifact, not derivable if you go and look — on the same surface as the picture, so a reader who was not going to investigate anything still sees it.

Concretely, that is the repository, the commit, the branch, whether the working tree was dirty, which harness was driving, which model, and the pull request the work belongs to. Seven facts. Every one of them is sitting in the environment at the moment of the push, and every one of them is gone ten seconds later when the container exits.

The one that surprises people is the dirty flag. A commit hash on a screenshot from a dirty tree is a claim about code that does not exist anywhere but that container — the hash names a tree, and the tree was not the one in the picture. So `krowk.vcs.dirty` travels with the artifact, because a commit reference without it is confidently wrong rather than merely incomplete.

## Why it goes on the artifact and not just the run

A session spans commits. An agent opens a task, pushes a screenshot, is told to fix something, pushes another. If the production context lives only on the run that groups them, both screenshots inherit one commit and one of them is now lying.

So the split is: the run carries the work-level facts — the pull request, the session, the title — and each artifact is stamped with the state found at its own push. A reader merges the two and the artifact wins. It also means an artifact re-homed later, claimed into a workspace or attached to a different run, keeps its own history rather than adopting its new parent's.

## Metadata is public, and that is the point

Pasting a link is choosing to publish what is behind it. The preview page needs no credential — the slug is the capability — so everything recorded on an artifact is readable by anyone holding the URL. That is why the keyless read embeds the run: the card has to render for the person the link was pasted to, who has no key.

The consequence is a rule rather than a caveat: never record a secret in metadata. Not a token, not an internal hostname you would not say out loud, not a customer name. The field that makes the screenshot trustworthy is the same field that makes it public.

## Naming things the way the ecosystem does

The keys follow OpenTelemetry where OpenTelemetry has a word for it — `vcs.repository.name`, `vcs.ref.head.revision`, `gen_ai.request.model` — and a `krowk.` namespace only where it does not. That is not tidiness for its own sake. Provenance is only useful if something downstream can read it, and a vocabulary invented here would need a translator at every boundary.

## What this does not solve

None of this proves the screenshot is of the code it names. A recorded commit hash is a claim made by the client at push time, and a client can be wrong or lied to. What the record does is make the claim checkable and make its absence visible: a card with no commit on it looks like a card with no commit on it, which is already more than an image carries on its own.

Signed attestations, a build the registry verified itself, provenance you could take to an auditor — none of that exists here. Someone will need it eventually. The cheap version is worth having first, because the current state of the art is a screenshot with nothing attached at all.

## **The commit is on the card, or the card is decoration.**

Every push records the repository, commit, branch, dirty flag, harness and model, read from the environment rather than passed by hand.

[Read the docs](https://krowk.com/docs) [Verify a UI change without pulling it](https://krowk.com/blog/verify-agent-ui-changes)