# Approving, reviewing and publishing

Admins and developers decide what gets made and what reaches the game.

## Approve request batches

The web page has a sidebar with one view per job, each showing how many items wait there. Under **Your work**: **Inbox**, **Approve**, **Open requests**, **My claims**, **Questions**, **Review** and **Publish**. Under **Project**: **Library**, **AI disclosure** and **Settings**. You only see the views your role can use, and a count that needs a decision from you is filled in green.

The page opens on **Inbox**: where every asset is, from an agent's draft to the repository, and a card for each kind of work waiting on you — the oldest batch to approve, the deliveries to review, questions nobody has answered, and what is ready to publish. Beside them sit the open pull request and how the published assets were made.

**Library** shows every asset as a picture, 50 at a time, newest first unless you pick **Oldest first**. Related assets share an id prefix: type `hero.` to see every hero, or pick a group under the filters, so `hero.hero_3.` shows one hero's expressions together. Narrow it by status, kind or tag too. Choose a tile to see its brief and files. Admins and developers can change its tags there with **Edit tags**: the picker suggests the keys and values other assets already use, so a set stays consistent, and a tag is `key=value` with one value per key. Ids that share a prefix for each character or set, like `hero.<name>.<expression>` or `ui.attribute.<name>`, make the library most useful. From the CLI, `agh inbox` prints the same counts with the command for each:

```text
agh inbox
   1  batches to approve          agh batch list --status submitted
   4  open requests               agh list --claimable
   0  requests you claimed        agh list --status claimed
   2  deliveries to review        agh list --status delivered
   3  accepted, ready to publish  agh publish
   1  waiting to merge            agh publish --status
```

Requests arrive in batches, and fulfillers see nothing until a batch is approved. On the web page they are under **Approve**; from the CLI:

```text
agh batch list --status submitted
agh batch show <batch-id>
agh batch approve <batch-id>
agh batch reject <batch-id> --note "Merge the three hare requests into one"
```

You don't have to take a batch whole. On the web page each request shows its brief, spec, tags and notes, with **Edit** to fix them and **Drop** to delete one. Untick the requests that need more work and press **Approve**: the ticked ones enter the queue, and the rest go back to the author's draft with your note. From the CLI:

```text
agh mark card.frost_hare.art --spec size=768x1024     # fix a request before approving
agh remove card.ash_crow.art                          # drop one
agh batch approve <batch-id> --only card.ember_wolf.art,card.frost_hare.art --note "Split the crow into two poses"
```

## Cancel an open request

An approved request that nobody has claimed yet can be taken back. On the web page, open **Open requests**, choose **Cancel request…** on the request, add a note if you like, and confirm. From the CLI:

```text
agh cancel card.ash_crow.art --note "Cut from the game"
```

A cancelled request leaves the queue at once: nobody can claim it, and guest links stop showing it. Its brief and conversation stay in **Library**, marked cancelled, where **Ask for it again** (or `agh revise`) reopens it. A request for a new version of a published file goes back to published instead, with that file unchanged. If someone holds a live claim, ask them to release it first (or release it yourself) and then cancel.

Coding agents can cancel their own requests too, with `agh cancel` or the MCP `cancel_request` tool, until someone claims them. One that is still waiting for approval leaves its batch on **Approve**; if the agent cancels every request in a batch, the batch no longer waits for you.

## Review deliveries

**Review** shows each delivered file beside its brief and the file it would replace, with image, audio and text previews.

3D models get a stage of their own. A `.glb`, `.gltf` or `.fbx` file opens in a viewer you can rotate (drag), zoom (scroll or pinch) and pan (right-drag), with its triangle and material counts. An FBX whose textures are separate files shows grey, with a note saying so: its shapes are right but its colours aren't. The first time anyone views a model, the page saves a still of it, so the Library shows the model without loading it again. The file a delivery replaces shows its still until you select it. Roblox files (`.rbxm`, `.rbxmx` and `.mesh`) can't be drawn in a browser. When the artist attached a picture, it shows labelled **Artist's render**: it is their picture of the model, not drawn from the file, so open the file in Roblox Studio before you accept anything that matters. Without one, **Ask for a render** starts a question in the asset's conversation.

```text
agh list --status delivered
agh get <asset-id> --which pending          # download the delivery
agh review <asset-id> --accept
agh review <asset-id> --reject --note "Too dark; keep the embers visible"
```

A rejection sends the request back to the queue with your note, so the next fulfiller sees it. Say whether the file missed the brief, or you now want something different: `--changes-brief` with the new `--brief`, `--spec` or `--notes` (**Reject: change brief…** on the web page) updates the brief and keeps the old one in its history. Fulfillers can ask questions on an asset; answer them under **Questions**, then change the brief once you agree. `agh guide conversations` covers both. The rejected file is kept: under the delivery, **earlier attempts** shows each one with its note, and `agh get <asset-id> --which rejected --attempt 1` downloads one.

On the web page you can also:

- Correct how a file was made before accepting it, under **Record as**: for example `ai-assisted` and `Flux, retouched by Jane` when someone touched up the image. From the CLI: `agh review <asset-id> --accept --provenance ai-assisted --made-by "Flux, retouched by Jane"`.
- Tick several deliveries and press **Accept selected**.
- **Reject and drop request** when the asset isn't needed any more. It removes the request and its delivery, the same as `agh remove`.
- Change your mind before publishing: **Back to review** on **Publish**, or `agh review <asset-id> --undo`, puts an accepted asset back in review and makes the file it replaced current again.

### Spec checks

Specs are hints for the fulfiller, so agh never refuses a delivery that differs from them. For images, it measures the delivered file instead: its size and whether it has an alpha channel are shown under the preview, and any difference from the request's `size` spec, or from a spec asking for transparency (`background: transparent`, or one mentioning RGBA or alpha), is listed as a warning in red. `agh show <asset-id>` prints the same details (`pending.image` and `pending.specWarnings` with `--format json`). Whether a 1149×1369 image passes for a 1150×1370 request is your call: accept it, or reject it with a note asking for the exact size.

Audio deliveries are measured when the spec asks for a `sample_rate`, `bit_depth`, `channels`, `loudness` (integrated LUFS, within 1 LU unless the spec gives a tolerance such as `-16 ±2`) or `peak` (true peak in dBTP). Loudness follows ITU-R BS.1770, the measure most platforms and broadcasters use, and is measured for WAV, FLAC, MP3 and Ogg Opus files up to 10 minutes long. Differences show as the same warnings.

Videos (MP4, MOV, WebM) are read from their header when they're delivered, however big the file: length, size, frame rate and codecs. They're checked against `max_seconds`, `size` (such as `1920x1080`), `aspect` (such as `16:9`), `fps` and `audio` (`required` or `none`), and differences show as the same warnings (`pending.videoHeader` with `--format json`).

### Review a video

A delivered video gets its own review layout. The player has a timeline, speed, sound and full screen, and keys while it has focus: Space plays, ← and → step 5 seconds, comma and period step one frame. **Spec checks** beside it lists each fact from the file's header, with mismatches marked. **Made with** shows how it was made.

Notes go in the asset's conversation, the same one every request has, with questions, answers and attached files. The message box carries an **at 0:42** chip that stamps the player's current time into your message (✕ sends it without a time). Stamped messages start with their time: click it to jump the player there. They show as marks on the timeline too. Agents set the time with `timecode` (seconds) on `post_asset_message`.

**Request changes** asks for a written reason and sends the video back, the same as **Reject** for other assets; the reason goes into the conversation. **Compare with v1** plays this delivery beside the accepted version, or the last attempt, in step from one set of controls, with a line saying what changed. **Brief** shows the request. On a phone the player goes edge to edge, spec mismatches come first, and Accept and Request changes stay at the bottom of the screen.

### Scale to the spec's size

When a PNG, JPEG or WebP delivery is bigger than its `size` spec, agh can scale it down for you instead of sending it back. When the image's shape differs from the spec's, you choose which area to keep; by default it's the largest area of the spec's shape, from the centre.

On **Review**, **Scale to 768×1024…** sits under the size warning. It opens the image with a frame of the spec's shape over it, as large as fits and centred: drag the frame to choose what's kept, drag a corner to keep less (never below the spec's size), or nudge it with the arrow keys. The preview beside it shows the result. After **Apply**, the delivery says who scaled it, with links to the original and to undo. From the CLI, say which area to keep in the image's pixels:

```text
agh scale <asset-id>                        # to the spec's size, cropped from the centre
agh scale <asset-id> --crop 0,96,1024,1365  # keep this area (x,y,width,height)
agh scale <asset-id> --undo                 # put back the image from before
```

The scaled image replaces the delivery, which still waits for review, with the same provenance. The delivered original moves to **earlier attempts** with a note saying who scaled it, and `pending.scaledFrom` records the original's size and the area kept. agh only scales down: an image smaller than the spec would blur, so reject it or ask for a bigger file. Undo works while the delivery waits for review.

### How an image or sound was made

agh also reads what a delivered image's or audio file's embedded metadata says about how it was made, and lists it under the preview as **Metadata**:

- Content Credentials (C2PA): the tool that signed the file, such as ChatGPT or Adobe Firefly, and the digital source type it records.
- The IPTC digital source type in XMP, such as `trainedAlgorithmicMedia` (made by generative AI), `compositeWithTrainedAlgorithmicMedia` (partly) or `digitalCapture` (a camera).
- EXIF and XMP software and camera tags, such as `Software: Adobe Photoshop 25.0` or `Make: Canon`.
- Generation settings that Stable Diffusion tools such as AUTOMATIC1111, ComfyUI and InvokeAI write into PNG text or the EXIF user comment.
- For audio: Content Credentials in an MP3's ID3 tag (a GEOB frame) or a WAV's `C2PA` chunk; ID3 tags such as encoder, artist, comment and links; WAV INFO tags such as software and comment, and the broadcast-wave description and originator; and FLAC and Ogg comments such as encoder and description. Suno, for example, attaches Content Credentials to its downloads, and ElevenLabs says it uses them too.

Clues that name an AI generator are marked **AI**. When a delivery is declared `human` but its metadata says AI was involved, a red box says so above the clues: ask whoever delivered it, or correct it under **Record as** before accepting. `agh show <asset-id>` prints the same (`pending.origin` and `pending.provenanceWarning` with `--format json`).

Metadata is a clue, not proof. Most exports, screenshots and uploads strip it, so an image without any says nothing about how it was made, and anyone can edit it. The same goes for audio: re-exporting from an audio editor usually drops it. Some AI services also hide a watermark in the pixels or the sound itself (SynthID, used by Google, OpenAI and ElevenLabs, and Meta's AudioSeal). It survives editing, but only the service's own detector can read it. On Studio and Enterprise, agh can check new images and audio for OpenAI's watermark with your OpenAI account, and shows the result as the first line of **Metadata**; see [Watermark checks](/docs/watermarks). For other services' watermarks, Google and ElevenLabs each run a free detector page.

## Image variants

Under a delivered image, **Make variants…** makes smaller copies, such as a 900-pixel WebP, that are saved as accepted assets when you accept the image. When a new version of an image with variants comes in, the box to remake them on accept is ticked by default. See [Image variants](/docs/image-variants). Studio and Enterprise plans.

## Publish

Connect GitHub once (admins): on the web page, open **Settings › Publishing**, choose **Set up** on **GitHub**, then **Connect GitHub**. GitHub asks you to install the Asset Goes Here app and choose the repositories it may use, then sends you back to agh. agh never sees a GitHub password or token of yours. It publishes with short-lived tokens that GitHub issues to the app for one repository at a time. If you connected exactly one repository and the project had no target yet, agh publishes to that repository's default branch. Otherwise, pick a repository and branch under Publishing, or from the CLI:

```text
agh project publish-to chust4phun/godot-ccg [--branch main] [--auto-merge]
```

To add repositories later, choose **Connect GitHub** again. A repository you haven't connected uses the server's own token, if its operator set one.

Then send accepted assets to GitHub as one pull request from the `agh/assets` branch. **Publish** lists everything accepted, all ticked; untick any that should wait, then choose **Publish selected**. From the CLI:

```text
agh publish                          # every accepted asset
agh publish --only card.ember_wolf.art,card.frost_hare.art
```

Each publish adds a commit to the open pull request, together with an updated `.agh/provenance.yaml`. The assets it sent are `proposed` until that pull request merges, and only then `published`. agh checks the pull request whenever you open **Publish**, run `agh publish --status`, or publish again:

- Merged: its assets become `published`.
- Closed without merging: its assets go back to `accepted`, ready to publish again or to send back to review.
- Still open: they stay under **Waiting to merge**, with a link to the pull request.

A `proposed` asset can't be revised, undone or delivered to while its pull request is open; merge or close the pull request first. With `--auto-merge`, GitHub merges the pull request once required checks pass, and one with nothing to wait for merges at once, so its assets are `published` straight away.

## Ask for a new version

`agh revise <asset-id> --note "..."` reopens a delivered, accepted or published asset with your note, optionally changing its brief or spec. On the web page, choose the asset in **Library** and use **Ask for a new version**, or use the **Revise** button beside it at the bottom of **Publish**; both work for published and accepted assets. A published file stays in place until its replacement is accepted and its pull request merges. `agh guide revise` covers every case.

## Correct provenance, or swap in human work

AI-made assets can ship; the disclosure just has to say so. When the record is wrong, or you decide to replace a file with human work, these keep it accurate:

```text
agh list --provenance ai
agh mark <asset-id> --provenance ai-assisted --made-by "AI draft, repainted by Jane"
agh fill <asset-id> ./final.png --provenance human --made-by "Jane Artist"
```

## AI disclosure

Steam asks whether generative AI made content that ships with the game: art, sound, story, localization, the store page and marketing. **AI disclosure** on the web page, `agh report` on the CLI, and the `get_ai_disclosure` MCP tool show what agh recorded for the files in the repository (published assets), grouped by those categories:

- how many files are AI-generated, AI-assisted, human-made or not recorded;
- the AI tools named in their `made_by`;
- every AI and AI-assisted asset id, for your own audit;
- records to fix first: unknown provenance, AI files with no tool named, and files declared human whose metadata says AI made them.

An asset counts under its kind and where players see it: store and marketing material by `used_in`, game images as art, audio as sound and music, text and strings as story and writing, and translations of them as localization, counted per language. To file one elsewhere, tag it, such as `--tag disclosure=story-and-writing`. The categories are `art`, `sound-and-music`, `story-and-writing`, `localization`, `store-page`, `marketing` and `other`.

Steam's categories blur some differences: art holds both images and 3D models, and the store page and marketing hold any kind of file. So the report also counts the same files by asset type: images, 3D models, audio, writing (text and strings in the game's own language), translations and other files. On the web page, **Group by** switches the cards between Steam category and asset type, and a category that mixes types lists each one under **By type**. The public page always uses Steam's categories.

Above the categories, a filled chart shows how many published files were AI-generated, AI-assisted, human-made or not recorded on each day, for every file, one category or one asset type. agh began recording asset types later than categories, so a type's chart starts on the day your server first recorded them. When you replace AI placeholders with hand-made work, it reads as a burn-down. agh saves the day's counts whenever the published files change (a pull request merges, a provenance is corrected, an asset is removed) and when someone opens the report, so the history starts when this feature reached your server; it is not rebuilt from earlier commits.

The report states facts. Write the explanation Steam asks for yourself; **Start from the numbers** on the web page gives you a neutral first draft to edit.

### Publish it for anyone to see

An admin can publish the report at a public page, such as `https://app.assetgoeshere.com/disclosure/my-game`, that anyone can open without signing in. It shows the explanation, and each category's counts and AI tools. Asset ids and paths stay private.

```text
agh report --publish --explanation-file ai-disclosure.txt
agh report --unpublish
```

The public page is a snapshot of the report when you published it. Publish again after changes to update it. Visitors may see the earlier version for up to five minutes, because the page is cached so heavy traffic never reaches the inventory. The same data is at `/disclosure/<project>/report.json`.

On Studio and Enterprise, the page carries your studio's logo, accent colour and dark or light look from **Organization > Branding & address**, and once your studio address is approved it also opens there, such as `https://yourstudio.assetgoeshere.com/disclosure/my-game`.
