# Riverdiff — instructions for agents and automation Riverdiff turns two texts into a side-by-side visual diff, and turns that diff into a shareable link. If you are an agent that has produced two versions of something and wants to hand a human a link to the comparison, this file is the contract. ## TL;DR 1. URL-encode each side (`encodeURIComponent`). 2. Open, in a real browser: /#agent=short&a=&b= 3. Wait for the element `#agent-out` to carry a **settled** `data-status` — `ok` or `error`. The element is present from the start with an empty `data-status`, and it is `pending` while the mint runs, so wait for the value, not for the element or the attribute. 4. On `ok`, read `data-link`. That is the share link, and it looks like `/?s=#k=`. Give it to the user. On `error`, read `data-error` for the reason. The documents travel in the URL **fragment**, after the `#`. A fragment is never sent to a server, so the text does not leave the browser even during this call. ## Why a browser is required The compression, the encryption and the key generation all run in page script using the browser's WebCrypto. There is no server-side API to call: a plain HTTP request to the deployment returns the app, not a link. Drive a real browser (headless is fine). ## Parameters All go in the fragment, `&`-separated, each value `encodeURIComponent`'d. | key | meaning | | -------- | ------------------------------------------------------------- | | `agent` | must be `short` — this is what engages agent mode | | `a` | the left document (required) | | `b` | the right document (required) | | `m` | the middle document, for a three-way merge view (optional) | | `an` | name shown over the left pane (optional) | | `bn` | name shown over the right pane (optional) | | `mn` | name shown over the middle pane (optional) | | `lang` | syntax highlighting language, e.g. `python` (optional) | | `iw` | `1` to ignore whitespace when comparing (optional) | | `ic` | `1` to ignore case when comparing (optional) | ## Reading the result The page writes a hidden `#agent-out` element:
{"status":"ok","link":"/?s=#k="}
`data-status` runs `""` (not started) → `pending` → `ok` or `error`. Only the last two are settled; treat `""` and `pending` as "keep waiting". - `ok` — `data-link` holds the share link. - `error` — `data-error` explains why, in words meant to be reported as-is. Two causes account for nearly every error, and both are configuration rather than a fault in the request: - **Agent mode is not enabled on this deployment.** It is OFF by default, including on the project's own demo site, because minting a short link means uploading to a store and a default build has no store to upload to. A deployment turns it on by setting `agentMode` and `storeEndpoint` in its profile. There is no way for a caller to switch it on. - **The diff exceeds the store's size limit.** Neither has a scriptable fallback: minting a link of any shape needs the page, and the only programmatic entry point is the one described here. If you get an `error`, report `data-error` to your user rather than retrying — no amount of retrying will change it. A human can still open the deployment and share the diff by hand, including as a full self-contained link that needs no store. ## Example (Playwright) ```python from urllib.parse import quote url = (f"{BASE}/#agent=short" f"&a={quote(left, safe='')}" f"&b={quote(right, safe='')}" f"&an={quote(left_name, safe='')}" f"&bn={quote(right_name, safe='')}") page.goto(url) # Wait for a SETTLED status. `#agent-out[data-status]` would match at once: # the element ships with the attribute present and empty. page.wait_for_function( """() => { const el = document.querySelector('#agent-out'); const s = el && el.dataset.status; return s === 'ok' || s === 'error'; }""", timeout=30000) el = page.query_selector('#agent-out') if el.get_attribute('data-status') == 'ok': link = el.get_attribute('data-link') else: raise RuntimeError(el.get_attribute('data-error')) ``` ## Showing a whole changeset (multiple files) The page does not fetch a change from a review server itself — every server has its own API, most want credentials the page has no business holding, and many send no CORS headers. Two options: - **Paste the diff.** Open the page normally and use **Load diff**: paste the output of `git diff`, `hg diff`, `jj diff --git` or `p4 diff` and every changed file becomes browsable in the *Changed files* bar. Good for a human; not scriptable from outside the page. - **One link per file.** Get each file's two sides yourself and mint one link per file with the flow above, then return them as a list. For git: git diff --name-only git show : # left git show : # right ## The two link shapes - **Short link** — `?s=#k=`. Only AES-GCM ciphertext is uploaded to the store; the key travels in the `#k=` fragment, which is never sent to any server. This is what agent mode mints. - **Full link** — `#d=E&k=`. The entire encrypted diff is in the URL, so nothing is uploaded at all and the link never expires. It grows with the size of the diff. Either way the content is encrypted in the browser before it goes anywhere, and the key is in the fragment. A store, a proxy, or a request log sees ciphertext. **The link is the secret.** Anyone holding a link that includes its key can read the diff. Treat it as you would the content. ## Not triggered by accident Agent mode engages only when the fragment carries `agent=short`. Opening the page any other way is completely unaffected, and no automation runs.