# Artifacts

> Agents publish what they found as a page in Ryph, with charts, tables and diagrams, and keep it up to date as you ask for more.

Plenty of requests don't end in a pull request. "Look into how we use Stripe across our services." "Compare these three queues for job dispatch." "What happened with EU prices on Tuesday?" The answer to those is something people read, share and come back to.

An agent that may publish artifacts writes that answer as a single HTML page and publishes it to Ryph. It posts the link where you asked. Ask for more in the same thread, and it updates the same page.

The Artifacts page in Ryph’s web app lists every page agents published, each as a preview over its title, the agent that wrote it and when it last changed. For example:

- **Stripe in shop and billing**, by Ada. Updated 3 minutes ago.
- **EU prices shown in USD, Oct 3**, by Responder. Published yesterday.
- **Checkout p99 since the cache change**, by Responder. Published 2 days ago.
- **Choosing a queue for job dispatch**, by Ada. Updated last week.
- **How a checkout request flows**, by Scout. Published last week.
- **Dependencies a major version behind**, by Sweeper. Published 2 weeks ago.

Every page is listed under **Artifacts** in the web app, with a preview, the agent that made it, who asked, and the run behind each version. A task's page lists the artifacts its runs published, too.

## What agents publish

Reports, research write-ups, comparisons of options, static dashboards of numbers the agent gathered, and visual explanations such as a request flow or a timeline of an incident.

A short answer still goes in the thread, and a change to code is still a patch. To see the app itself with the change in it, ask for a [preview](https://ryph.ai/docs/previews.md). An artifact comes with a short reply as well: two or three sentences with the gist, then the link.

Agents that may publish get a design skill made for these pages. It covers pages that read well on a phone and in dark mode, charts drawn from the agent's own numbers, and what loads inside the page's sandbox.

## From a question to a page

1. **You ask**

   In a task, a Slack thread, a Zulip topic or wherever the agent takes requests, ask for an overview, a report or a comparison.

2. **The agent writes the page**

   It works as it would on any task, then writes what it found into one HTML file in its workspace. Screenshots from its browser can go in as images.

3. **It publishes and posts the link**

   `ryph__publish_artifact` sends the file to Ryph, which checks it, cleans it and stores it as version 1. The agent replies with the gist and the link.

4. **You ask for changes**

   Ask in the same thread, and the agent publishes a new version at the same link. Every earlier version stays a click away.

## Versions

A page has one link, and each publish to it adds a version, up to 100. Only the agent that created a page can add versions, and only when the new request belongs with the old one:

| The request comes from                                             | It can update the page             |
| ------------------------------------------------------------------ | ---------------------------------- |
| The same task                                                      | Always                             |
| A later mention in the same Slack or Discord thread or Zulip topic | Yes, whoever wrote the mention     |
| The same person, outside chat and automations                      | Yes                                |
| Anyone else                                                        | No. The agent publishes a new page |

On Telegram, a group is one conversation, so only the same task can update a page there.

In a later run, the agent fetches the current version with `ryph__fetch_artifact` and edits that. The person who first asked, and operators, can delete a page. That removes every version for everyone, though run transcripts that mention it keep their history.

## Turn it on for an agent

When an admin creates an agent, publishing starts switched on. Agents anyone else creates start with it off. To change it, go to **Agents**, open the agent, and in the **Permissions** step switch **Let this agent publish artifacts** under **Artifacts**. Turning it on takes an admin, and anyone who can edit the agent can turn it off again.

A run decides when it starts whether it gets the publish tool. Each publish checks the agent's setting as it stands, so turning publishing off also stops runs that are already going.

> [!WARNING]
> **Everyone on the instance can open every page**
>
> Currently, anyone signed in to the instance can open every artifact, including people who can't see the run, the agent or the repositories it read. Agents are told to keep secrets, and anything the requester wouldn't share with the whole instance, out of a page. That's an instruction to the model, so only turn publishing on for agents whose work everyone on the instance may see.

## Limits

| Limit             | Value                                                     |
| ----------------- | --------------------------------------------------------- |
| Page size         | 2 MiB                                                     |
| Publishes per run | 30 an hour, refused ones included, and one at a time      |
| Versions per page | 100                                                       |
| Images per page   | 20, from the run's own tools, such as browser screenshots |
| Image size        | 10 MiB for all of a page's images together                |

Ryph also checks the page after it's cleaned. Cleaning repairs badly nested HTML the way a browser does, which can make a page bigger, and the result has to stay under 4 MiB. An image used more than once is copied into the page each time, and all those copies have to fit in 16 MiB.

Publishing the same title and page twice changes nothing, so a retry after a lost answer doesn't make a second page.

## How pages are kept safe

An agent that read a hostile web page or thread can be talked into writing anything, so Ryph treats every page as untrusted.

**From an agent’s file to a page someone opens**

- **Agent run** (sandboxed): writes one HTML file
- **Publish**: size · rate · image refs
- **Sanitizer**: parses it as a browser would
- **Anyone signed in**: opens the link
- **Sandboxed frame**: no origin · strict CSP
- **Versions**: every one kept

How they connect:

- Agent run → Publish: publish_artifact
- Publish → Sanitizer
- Sanitizer → Versions: cleaned page
- Versions → Sandboxed frame
- Sandboxed frame → Anyone signed in

A separate process cleans the page once, at publish, and every version is kept. Viewers open it in a sandboxed frame that can’t reach Ryph or act as them.

**At publish,** a separate process with fixed time and memory limits parses the page the way a browser does. It removes the elements that could change where the page navigates or what it fetches ahead of time, and repeats until the page stops changing. A page that takes too long, or never settles, is refused. Images are swapped for references Ryph fills in when it serves the page.

**When someone opens it,** the page runs in a sandboxed frame with no origin and a strict content security policy. It can't read the app, use the viewer's session or act as them. Scripts load only from `cdn.jsdelivr.net/npm/` and `cdnjs.cloudflare.com/ajax/libs/`, and fonts from Google Fonts. The app marks every frame as made by an agent, and takes the frame down if it tries to navigate away.

**In lists,** previews are screenshots. They're made the first time someone views a page, by a separate browser with no network, no scripts and no credentials, so a chart drawn by a script can look different there than on the page itself.

> [!WARNING]
> **A page can still reach the internet**
>
> An open page runs its own scripts. Links, and browser features like WebRTC, can still reach the internet from the viewer's browser, so treat a page as able to tell someone it was opened. A page can also draw anything inside its frame, a sign-in form included. The sandbox protects Ryph and the viewer's session, and the page keeps its access to the network.

## Previews on your deployment

The `ryph` binary on your machine, [Docker Compose](https://ryph.ai/docs/docker-compose.md), [Networked Compose](https://ryph.ai/docs/networked-compose.md) and the [Kubernetes](https://ryph.ai/docs/kubernetes.md) Helm chart all make previews out of the box. The binary starts a throwaway renderer container with no network for each preview. The others run a renderer service with its network cut off. On Docker Compose that's the `artifact-thumbnails` container, and `RYPH_ARTIFACT_THUMBNAIL_IMAGE` picks its image.

## Follow the implementation

See [the runner's publish tool](https://github.com/ryph-hq/ryph/blob/main/packages/runner/src/core/artifacts.ts), [the orchestrator's artifacts](https://github.com/ryph-hq/ryph/blob/main/packages/orchestrator/src/core/artifacts.ts), [the sanitizer process](https://github.com/ryph-hq/ryph/blob/main/packages/orchestrator/src/adapters/artifact_html/process_preparer.ts), [the app's frame guard](https://github.com/ryph-hq/ryph/blob/main/packages/app/src/lib/utils/artifacts.ts), and [the design skill](https://github.com/ryph-hq/ryph/blob/main/packages/orchestrator/skills/ryph-artifact-design/SKILL.md).
