---
title: Artifacts
description: Stream agent-generated files (reports, charts, code) to the UI
---

Artifacts are files produced by the agent during a task that can be streamed to the UI. The SDK surfaces them via `useSandAgentChat` and `useArtifacts` hooks.

## Quick UI Example

```tsx
import { useSandAgentChat } from "@sandagent/sdk/react";

export default function ChatPage() {
  const {
    messages,
    sendMessage,
    artifacts,
    selectedArtifact,
    setSelectedArtifact,
  } = useSandAgentChat({ apiEndpoint: "/api/ai" });

  return (
    <div className="flex">
      <div className="flex-1">{/* chat UI */}</div>

      {artifacts.length > 0 && (
        <div className="w-96 border-l">
          <div className="flex gap-2 p-2 border-b">
            {artifacts.map((artifact) => (
              <button
                key={artifact.artifactId}
                onClick={() => setSelectedArtifact(artifact)}
              >
                {artifact.artifactId}
              </button>
            ))}
          </div>

          {selectedArtifact && (
            <div className="p-4">
              <pre>{selectedArtifact.content}</pre>
            </div>
          )}
        </div>
      )}
    </div>
  );
}
```

## useArtifacts (Standalone)

If you have a custom chat UI, extract artifacts directly:

```tsx
import { useArtifacts } from "@sandagent/sdk/react";

function MyArtifactPanel({ messages }) {
  const {
    artifacts,
    selectedArtifact,
    setSelectedArtifact,
    hasArtifacts,
    copyContent,
    downloadArtifact,
  } = useArtifacts({ messages });

  if (!hasArtifacts) return null;

  return (
    <div>
      {artifacts.map((a) => (
        <button key={a.artifactId} onClick={() => setSelectedArtifact(a)}>
          {a.artifactId}
        </button>
      ))}
      {selectedArtifact && <pre>{selectedArtifact.content}</pre>}
    </div>
  );
}
```

## Backend Setup

Register an `ArtifactProcessor` with the provider:

```typescript
import { createSandAgent, LocalSandbox } from "@sandagent/sdk";
import { createUIMessageStream, createUIMessageStreamResponse, streamText } from "ai";

export async function POST(request: Request) {
  const { messages } = await request.json();
  const sandbox = new LocalSandbox({ workdir: process.cwd() });

  const stream = createUIMessageStream({
    execute: async ({ writer }) => {
      const artifactProcessor = new TaskDrivenArtifactProcessor({
        sandbox,
        workdir: sandbox.getWorkdir?.() || "/sandagent",
        writer,
      });

      const sandagent = createSandAgent({
        sandbox,
        artifactProcessors: [artifactProcessor],
      });

      const result = streamText({ model: sandagent("sonnet"), messages });
      writer.merge(result.toUIMessageStream());
      await result.response;
    },
  });

  return createUIMessageStreamResponse({ stream });
}
```

## Artifact Manifest

The agent writes a manifest at `tasks/{sessionId}/artifact.json`:

```json
{
  "artifacts": [
    {
      "id": "report",
      "path": "tasks/{sessionId}/reports/report.md",
      "mimeType": "text/markdown",
      "description": "Main report"
    }
  ]
}
```

| Field | Required | Description |
|-------|----------|-------------|
| `id` | No | Unique identifier for UI de-duplication |
| `path` | Yes | Relative or absolute file path |
| `mimeType` | No | e.g. `text/markdown`, `text/html` |
| `description` | No | Short description |

## Artifact Skill (Recommended)

Add an `artifact` skill to your template so the agent reliably writes the manifest:

```
templates/your-template/
  .claude/
    skills/
      artifact/
        SKILL.md
```

The skill should instruct the agent to:
1. Create `tasks/${CLAUDE_SESSION_ID}/artifact.json`
2. Add entries whenever a new output file is created
3. Keep paths consistent with the workdir

A reference implementation exists at `templates/researcher/.claude/skills/artifact/SKILL.md`.

## Rendering Tips

- Use Markdown rendering for `text/markdown`
- Render `text/html` in a sandboxed iframe
- For large text, show a scrollable `<pre>`

## Troubleshooting

- No artifacts showing → confirm `artifact.json` exists and paths are valid
- Duplicate artifacts → ensure `id` is unique and stable
- Infinite render loop → memoize extracted artifacts when using custom hooks
